fix: correct capacity calculation and wireless sensor display (#116)
- Capacity: Sensor values (Rx/Tx Capacity) now represent total device capacity, not per-interface. Previous bug applied the same sensor value to all interfaces and summed them (e.g., 773.6 Mbps × 5 = 3.87 Gbps). - Wireless sensors: Changed Float.round/2 to :erlang.float_to_binary/2 to prevent scientific notation in frequency display (was showing "2.41e4 MHz" instead of "24100.0 MHz"). Files: - lib/towerops_web/live/device_live/show.ex - lib/towerops_web/live/device_live/show.html.heex - CHANGELOG.txt - priv/static/changelog.txt Reviewed-on: graham/towerops-web#116
This commit is contained in:
parent
6c82a5cfbf
commit
2b24626baa
4 changed files with 89 additions and 46 deletions
|
|
@ -1,3 +1,20 @@
|
|||
2026-03-22
|
||||
fix: correct capacity calculation for backhaul devices with sensor-based capacity
|
||||
- Sensor capacity (Rx/Tx Capacity) now represents total device capacity, not per-interface
|
||||
- Previous bug: applied same sensor value to all interfaces, then summed them (e.g. 773.6 Mbps * 5 interfaces = 3.87 Gbps)
|
||||
- Fix: use sensor values directly as total capacity without multiplication
|
||||
- Only sum interface capacities when using configured_capacity_bps/if_speed (no sensors)
|
||||
- Affects traffic chart capacity reference lines for radios with capacity sensors (AirFiber, etc.)
|
||||
Files: lib/towerops_web/live/device_live/show.ex
|
||||
|
||||
2026-03-22
|
||||
fix: prevent scientific notation in wireless sensor display (frequency)
|
||||
- Wireless sensor values (especially frequency) were showing in scientific notation (2.41e4 MHz)
|
||||
- Changed Float.round/2 to :erlang.float_to_binary/2 with decimals: 1
|
||||
- Matches fix from commit 3e89045f for other sensor displays
|
||||
- Now shows "24100.0 MHz" instead of "2.41e4 MHz"
|
||||
Files: lib/towerops_web/live/device_live/show.html.heex
|
||||
|
||||
2026-03-22
|
||||
feat: simplify device type to manual-only with 6 options
|
||||
- Reduced device role dropdown from 10 options to 6: server, switch, router, access_point, backhaul, other
|
||||
|
|
|
|||
|
|
@ -1273,57 +1273,20 @@ defmodule ToweropsWeb.DeviceLive.Show do
|
|||
|
||||
# Add capacity reference lines for backhaul devices
|
||||
# Calculates capacity per timestamp based on which interfaces have data at that point
|
||||
# For radios with Rx/Tx Capacity sensors, uses those values instead of if_speed
|
||||
# For radios with Rx/Tx Capacity sensors, uses those values as total device capacity
|
||||
defp add_capacity_datasets(datasets, all_stats, device) do
|
||||
# Get capacity sensors if available (for radios like AirFiber)
|
||||
rx_capacity_sensor = Enum.find(device.sensors, &(&1.sensor_descr == "Rx Capacity"))
|
||||
tx_capacity_sensor = Enum.find(device.sensors, &(&1.sensor_descr == "Tx Capacity"))
|
||||
|
||||
# Build a map of interface_id -> {rx_capacity, tx_capacity} for quick lookup
|
||||
interface_capacity_map =
|
||||
device.interfaces
|
||||
|> Enum.filter(& &1.monitored)
|
||||
|> Map.new(fn interface ->
|
||||
# Use sensor values if available (converted to bps), otherwise fall back to configured/if_speed
|
||||
rx_cap = get_capacity_bps(rx_capacity_sensor) || interface.configured_capacity_bps || interface.if_speed || 0
|
||||
tx_cap = get_capacity_bps(tx_capacity_sensor) || interface.configured_capacity_bps || interface.if_speed || 0
|
||||
{interface.id, {rx_cap, tx_cap}}
|
||||
end)
|
||||
|
||||
# Get the raw interface stats to determine which interfaces were active at each timestamp
|
||||
twenty_four_hours_ago = DateTime.add(DateTime.utc_now(), -24, :hour)
|
||||
|
||||
# Track which interfaces have data at each timestamp
|
||||
interface_activity_by_timestamp =
|
||||
device.interfaces
|
||||
|> Enum.flat_map(fn interface ->
|
||||
interface.id
|
||||
|> Snmp.get_interface_stats(since: twenty_four_hours_ago, limit: 1000)
|
||||
|> Enum.map(fn stat ->
|
||||
ts_seconds = DateTime.to_unix(stat.checked_at)
|
||||
rounded_ts_ms = div(ts_seconds, 60) * 60 * 1000
|
||||
{rounded_ts_ms, interface.id}
|
||||
end)
|
||||
end)
|
||||
|> Enum.group_by(fn {ts, _} -> ts end, fn {_, iface_id} -> iface_id end)
|
||||
|
||||
# Calculate capacity at each timestamp based on active interfaces
|
||||
# For backhaul devices with Rx/Tx sensors, track separately
|
||||
# If we have capacity sensors, use them as the total device capacity
|
||||
# Otherwise, calculate from interface speeds
|
||||
capacity_data =
|
||||
Enum.map(all_stats, fn stat ->
|
||||
# Get interfaces that had data at this timestamp
|
||||
active_interfaces = Map.get(interface_activity_by_timestamp, stat.timestamp_ms, [])
|
||||
# Sum capacity of active interfaces
|
||||
{total_rx, total_tx} =
|
||||
active_interfaces
|
||||
|> Enum.uniq()
|
||||
|> Enum.reduce({0, 0}, fn iface_id, {rx_acc, tx_acc} ->
|
||||
{rx_cap, tx_cap} = Map.get(interface_capacity_map, iface_id, {0, 0})
|
||||
{rx_acc + rx_cap, tx_acc + tx_cap}
|
||||
end)
|
||||
|
||||
{stat.timestamp_ms, total_rx, total_tx}
|
||||
end)
|
||||
if rx_capacity_sensor || tx_capacity_sensor do
|
||||
calculate_sensor_capacity_data(all_stats, rx_capacity_sensor, tx_capacity_sensor)
|
||||
else
|
||||
calculate_interface_capacity_data(all_stats, device)
|
||||
end
|
||||
|
||||
# Only add capacity datasets if we have at least some capacity data
|
||||
max_rx = capacity_data |> Enum.map(fn {_, rx, _} -> rx end) |> Enum.max(fn -> 0 end)
|
||||
|
|
@ -1365,6 +1328,63 @@ defmodule ToweropsWeb.DeviceLive.Show do
|
|||
end
|
||||
end
|
||||
|
||||
# Calculate capacity data using sensor values (for radios with Rx/Tx Capacity sensors)
|
||||
# Sensor values represent total device capacity, not per-interface
|
||||
defp calculate_sensor_capacity_data(all_stats, rx_capacity_sensor, tx_capacity_sensor) do
|
||||
rx_cap_bps = get_capacity_bps(rx_capacity_sensor) || 0
|
||||
tx_cap_bps = get_capacity_bps(tx_capacity_sensor) || 0
|
||||
|
||||
# Use the same capacity for all timestamps
|
||||
Enum.map(all_stats, fn stat ->
|
||||
{stat.timestamp_ms, rx_cap_bps, tx_cap_bps}
|
||||
end)
|
||||
end
|
||||
|
||||
# Calculate capacity data from interface configured_capacity_bps or if_speed
|
||||
# Sums capacity of active interfaces at each timestamp
|
||||
defp calculate_interface_capacity_data(all_stats, device) do
|
||||
# Build a map of interface_id -> {rx_capacity, tx_capacity} for quick lookup
|
||||
interface_capacity_map =
|
||||
device.interfaces
|
||||
|> Enum.filter(& &1.monitored)
|
||||
|> Map.new(fn interface ->
|
||||
cap = interface.configured_capacity_bps || interface.if_speed || 0
|
||||
{interface.id, {cap, cap}}
|
||||
end)
|
||||
|
||||
# Get the raw interface stats to determine which interfaces were active at each timestamp
|
||||
twenty_four_hours_ago = DateTime.add(DateTime.utc_now(), -24, :hour)
|
||||
|
||||
# Track which interfaces have data at each timestamp
|
||||
interface_activity_by_timestamp =
|
||||
device.interfaces
|
||||
|> Enum.flat_map(fn interface ->
|
||||
interface.id
|
||||
|> Snmp.get_interface_stats(since: twenty_four_hours_ago, limit: 1000)
|
||||
|> Enum.map(fn stat ->
|
||||
ts_seconds = DateTime.to_unix(stat.checked_at)
|
||||
rounded_ts_ms = div(ts_seconds, 60) * 60 * 1000
|
||||
{rounded_ts_ms, interface.id}
|
||||
end)
|
||||
end)
|
||||
|> Enum.group_by(fn {ts, _} -> ts end, fn {_, iface_id} -> iface_id end)
|
||||
|
||||
# Calculate capacity at each timestamp based on active interfaces
|
||||
Enum.map(all_stats, fn stat ->
|
||||
active_interfaces = Map.get(interface_activity_by_timestamp, stat.timestamp_ms, [])
|
||||
|
||||
{total_rx, total_tx} =
|
||||
active_interfaces
|
||||
|> Enum.uniq()
|
||||
|> Enum.reduce({0, 0}, fn iface_id, {rx_acc, tx_acc} ->
|
||||
{rx_cap, tx_cap} = Map.get(interface_capacity_map, iface_id, {0, 0})
|
||||
{rx_acc + rx_cap, tx_acc + tx_cap}
|
||||
end)
|
||||
|
||||
{stat.timestamp_ms, total_rx, total_tx}
|
||||
end)
|
||||
end
|
||||
|
||||
# Get capacity in bps from a sensor (sensor values are in Mbps, convert to bps)
|
||||
defp get_capacity_bps(nil), do: nil
|
||||
|
||||
|
|
|
|||
|
|
@ -1167,7 +1167,9 @@
|
|||
<%= if sensor.sensor_type in ["clients", "ccq", "utilization"] do %>
|
||||
{trunc(sensor.latest_reading.value)}
|
||||
<% else %>
|
||||
{Float.round(sensor.latest_reading.value, 1)}
|
||||
{:erlang.float_to_binary(sensor.latest_reading.value * 1.0,
|
||||
decimals: 1
|
||||
)}
|
||||
<% end %>
|
||||
<%= if sensor.sensor_unit && sensor.sensor_unit != "" do %>
|
||||
{sensor.sensor_unit}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
2026-03-22 — Bug Fixes
|
||||
* Fixed capacity reference lines showing inflated values (5-6x higher than actual) on backhaul device traffic charts
|
||||
* Fixed wireless sensor values (frequency, power, etc.) displaying in scientific notation instead of standard format
|
||||
|
||||
2026-03-22 — Simplified Device Types
|
||||
* Device type selection simplified from 10 options to 6: Server, Switch, Router, Access Point, Backhaul, and Other
|
||||
* Field renamed from "Device Role" to "Device Type" for clarity
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue