fix/overall-traffic-graph-data (#125)

Reviewed-on: graham/towerops-web#125
This commit is contained in:
Graham McIntire 2026-03-23 10:19:43 -05:00
parent fbbd3402b3
commit cdce8445e5

View file

@ -1204,8 +1204,10 @@ defmodule ToweropsWeb.DeviceLive.Show do
# Calculate BPS per interface first, then aggregate by timestamp
# This avoids issues with misaligned timestamps between interfaces
# Only processes monitored interfaces to match actual traffic being tracked
defp aggregate_interface_traffic_stats(interfaces, since) do
interfaces
|> Enum.filter(& &1.monitored)
|> Enum.flat_map(fn interface ->
interface.id
|> Snmp.get_interface_stats(since: since, limit: 1000)
@ -1329,16 +1331,78 @@ defmodule ToweropsWeb.DeviceLive.Show do
# Calculate capacity data using sensor values (for radios with Rx/Tx Capacity sensors)
# Sensor values represent total device capacity, not per-interface
# Loads time-series sensor readings to create a dynamic capacity line
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
twenty_four_hours_ago = DateTime.add(DateTime.utc_now(), -24, :hour)
# Load time-series readings for both sensors
rx_readings = load_capacity_sensor_readings(rx_capacity_sensor, twenty_four_hours_ago)
tx_readings = load_capacity_sensor_readings(tx_capacity_sensor, twenty_four_hours_ago)
# If we have sensor readings, use them directly (they have their own timestamps)
# Otherwise fall back to static values at traffic timestamps
if map_size(rx_readings) > 0 || map_size(tx_readings) > 0 do
build_capacity_data_from_sensors(rx_readings, tx_readings, rx_capacity_sensor, tx_capacity_sensor)
else
build_static_capacity_data(all_stats, rx_capacity_sensor, tx_capacity_sensor)
end
end
# Load sensor readings and convert to timestamp -> bps map
defp load_capacity_sensor_readings(nil, _since), do: %{}
defp load_capacity_sensor_readings(sensor, since) do
sensor.id
|> Snmp.get_sensor_readings(since: since, limit: 1000)
|> Map.new(&sensor_reading_to_capacity_point/1)
end
# Convert a sensor reading to {timestamp_ms, bps} tuple
# Sensor values are in Mbps, convert to bps
defp sensor_reading_to_capacity_point(reading) do
bps = capacity_value_to_bps(reading.value)
ts_ms = DateTime.to_unix(reading.checked_at, :millisecond)
{ts_ms, bps}
end
# Convert capacity sensor value (in Mbps) to bps
defp capacity_value_to_bps(nil), do: 0
defp capacity_value_to_bps(value) when is_number(value), do: round(value * 1_000_000)
defp capacity_value_to_bps(_), do: 0
# Build capacity data points from sensor readings
defp build_capacity_data_from_sensors(rx_readings, tx_readings, rx_sensor, tx_sensor) do
# Combine all unique timestamps from both sensors
all_timestamps =
(Map.keys(rx_readings) ++ Map.keys(tx_readings))
|> MapSet.new()
|> Enum.sort()
# Build capacity data points using sensor readings
Enum.map(all_timestamps, fn ts_ms ->
rx_bps = Map.get(rx_readings, ts_ms, get_fallback_capacity_bps(rx_sensor))
tx_bps = Map.get(tx_readings, ts_ms, get_fallback_capacity_bps(tx_sensor))
{ts_ms, rx_bps, tx_bps}
end)
end
# Build static capacity data at traffic timestamps
defp build_static_capacity_data(all_stats, rx_sensor, tx_sensor) do
rx_cap_bps = get_fallback_capacity_bps(rx_sensor)
tx_cap_bps = get_fallback_capacity_bps(tx_sensor)
# Use the same capacity for all timestamps
Enum.map(all_stats, fn stat ->
{stat.timestamp_ms, rx_cap_bps, tx_cap_bps}
end)
end
# Get fallback capacity in bps from sensor's last_value (already in Mbps)
defp get_fallback_capacity_bps(nil), do: 0
defp get_fallback_capacity_bps(sensor) do
capacity_value_to_bps(sensor.last_value)
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
@ -1384,15 +1448,6 @@ defmodule ToweropsWeb.DeviceLive.Show do
end)
end
# Get capacity in bps from a sensor (sensor values are in Mbps, convert to bps)
defp get_capacity_bps(nil), do: nil
defp get_capacity_bps(%{last_value: value}) when is_number(value) do
# Sensor last_value is in Mbps (already divided by sensor_divisor), convert to bps
round(value * 1_000_000)
end
defp get_capacity_bps(_sensor), do: nil
# Group interfaces by type for organized display
defp group_interfaces_by_type(interfaces) do
interfaces