Add per-interface capacity tracking with manual override and automatic detection from SNMP sensors. Includes capacity resolver with priority cascade (manual > sensor > if_speed), throughput calculation from interface stats, and site/organization-level capacity summaries. - Migration: add configured_capacity_bps and capacity_source to snmp_interfaces - Capacity.Resolver: multi-source capacity detection with vendor unit normalization - Capacity context: throughput calculation, utilization percentages, site/org summaries - Snmp context: set_manual_capacity/2 and clear_manual_capacity/1 functions
98 lines
2.7 KiB
Elixir
98 lines
2.7 KiB
Elixir
defmodule Towerops.Capacity.Resolver do
|
|
@moduledoc """
|
|
Resolves the effective capacity for an interface using a priority cascade:
|
|
|
|
1. Manual override (capacity_source = "manual")
|
|
2. Sensor-based (capacity/rate sensors from vendor profiles)
|
|
3. if_speed (SNMP link speed)
|
|
"""
|
|
|
|
import Ecto.Query
|
|
|
|
alias Towerops.Repo
|
|
alias Towerops.Snmp.Sensor
|
|
|
|
@capacity_sensor_types ~w(capacity rate)
|
|
|
|
@doc """
|
|
Resolves the capacity for an interface, returning `{capacity_bps, source}` or `nil`.
|
|
"""
|
|
@spec resolve_capacity(map(), map()) :: {pos_integer(), String.t()} | nil
|
|
def resolve_capacity(interface, snmp_device) do
|
|
resolve_manual(interface) ||
|
|
resolve_sensor(snmp_device) ||
|
|
resolve_if_speed(interface)
|
|
end
|
|
|
|
@doc """
|
|
Finds the highest capacity/rate sensor value for a device, normalized to bps.
|
|
Returns the value in bps or nil.
|
|
"""
|
|
@spec resolve_from_sensors(map()) :: pos_integer() | nil
|
|
def resolve_from_sensors(snmp_device) do
|
|
sensors =
|
|
Sensor
|
|
|> where([s], s.snmp_device_id == ^snmp_device.id)
|
|
|> where([s], s.sensor_type in ^@capacity_sensor_types)
|
|
|> where([s], not is_nil(s.last_value) and s.last_value > 0.0)
|
|
|> Repo.all()
|
|
|
|
sensors
|
|
|> Enum.map(&normalize_sensor_to_bps/1)
|
|
|> Enum.reject(&is_nil/1)
|
|
|> Enum.max(fn -> nil end)
|
|
end
|
|
|
|
@doc """
|
|
Normalizes a sensor's last_value to bits per second based on its unit.
|
|
"""
|
|
@spec normalize_sensor_to_bps(Sensor.t()) :: pos_integer() | nil
|
|
def normalize_sensor_to_bps(%Sensor{last_value: nil}), do: nil
|
|
def normalize_sensor_to_bps(%Sensor{last_value: v}) when v <= 0, do: nil
|
|
|
|
def normalize_sensor_to_bps(%Sensor{last_value: value, sensor_unit: unit, sensor_divisor: divisor}) do
|
|
adjusted = value / (divisor || 1)
|
|
|
|
bps =
|
|
case normalize_unit(unit) do
|
|
:bps -> adjusted
|
|
:kbps -> adjusted * 1_000
|
|
:mbps -> adjusted * 1_000_000
|
|
:gbps -> adjusted * 1_000_000_000
|
|
_ -> adjusted
|
|
end
|
|
|
|
trunc(bps)
|
|
end
|
|
|
|
defp normalize_unit(nil), do: :bps
|
|
|
|
defp normalize_unit(unit) when is_binary(unit) do
|
|
case String.downcase(unit) do
|
|
"bps" -> :bps
|
|
"kbps" -> :kbps
|
|
"mbps" -> :mbps
|
|
"gbps" -> :gbps
|
|
_ -> :bps
|
|
end
|
|
end
|
|
|
|
defp resolve_manual(%{capacity_source: "manual", configured_capacity_bps: bps}) when is_integer(bps) and bps > 0 do
|
|
{bps, "manual"}
|
|
end
|
|
|
|
defp resolve_manual(_), do: nil
|
|
|
|
defp resolve_sensor(snmp_device) do
|
|
case resolve_from_sensors(snmp_device) do
|
|
nil -> nil
|
|
bps -> {bps, "sensor"}
|
|
end
|
|
end
|
|
|
|
defp resolve_if_speed(%{if_speed: speed}) when is_integer(speed) and speed > 0 do
|
|
{speed, "if_speed"}
|
|
end
|
|
|
|
defp resolve_if_speed(_), do: nil
|
|
end
|