fix: comprehensive ePMP/dynamic profile discovery improvements
- Fix divisor calculation in importer: use precision value directly instead of 10^precision - Fix Dynamic profile to handle map format from Client.walk (was expecting list) - Add processor discovery to Dynamic profile (converts CPU to percent sensors) - Fix sensor index extraction to clean template variables - Make sensor indices unique using descriptive names for scalar sensors - Add status determination for CPU sensors (ok/warning/critical thresholds) This enables proper discovery and monitoring of ePMP devices with: - DFS count sensors - GPS/DFS/antenna state sensors - CPU usage monitoring All sensors now discover correctly without unique constraint violations.
This commit is contained in:
parent
143d9dceb3
commit
28e9be773d
2 changed files with 117 additions and 14 deletions
|
|
@ -333,7 +333,9 @@ defmodule Towerops.DeviceProfiles.Importer do
|
|||
defp calculate_divisor(nil), do: 1
|
||||
|
||||
defp calculate_divisor(precision) when is_integer(precision) and precision > 0 do
|
||||
10 |> :math.pow(precision) |> trunc()
|
||||
# Precision in device profiles is the divisor value itself, not an exponent
|
||||
# For example: precision: 10 means divide by 10, precision: 1024 means divide by 1024
|
||||
precision
|
||||
end
|
||||
|
||||
defp calculate_divisor(_), do: 1
|
||||
|
|
|
|||
|
|
@ -51,25 +51,37 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
|
|||
|
||||
@doc """
|
||||
Discovers sensors dynamically using database profile definitions.
|
||||
Walks SNMP OIDs defined in the profile's sensor_definitions.
|
||||
Walks SNMP OIDs defined in the profile's sensor_definitions and processor_definitions.
|
||||
"""
|
||||
@spec discover_sensors(DeviceProfile.t(), Client.connection_opts()) ::
|
||||
{:ok, [Discovery.sensor_data()]}
|
||||
def discover_sensors(profile, client_opts) do
|
||||
# Load profile with sensor definitions
|
||||
# Load profile with sensor and processor definitions
|
||||
profile = DeviceProfiles.get_profile_with_associations(profile.os)
|
||||
|
||||
if profile && length(profile.sensor_definitions) > 0 do
|
||||
Logger.debug("Discovering sensors for #{profile.os} using #{length(profile.sensor_definitions)} definitions")
|
||||
sensor_count = if profile, do: length(profile.sensor_definitions), else: 0
|
||||
processor_count = if profile, do: length(profile.processor_definitions), else: 0
|
||||
|
||||
if profile && (sensor_count > 0 || processor_count > 0) do
|
||||
Logger.debug(
|
||||
"Discovering sensors for #{profile.os} (#{sensor_count} sensor defs, #{processor_count} processor defs)"
|
||||
)
|
||||
|
||||
# Discover regular sensors
|
||||
sensors =
|
||||
Enum.flat_map(profile.sensor_definitions, fn sensor_def ->
|
||||
discover_sensor_from_definition(client_opts, sensor_def)
|
||||
end)
|
||||
|
||||
{:ok, sensors}
|
||||
# Discover processors as sensors
|
||||
cpu_sensors =
|
||||
Enum.flat_map(profile.processor_definitions, fn proc_def ->
|
||||
discover_processor_as_sensor(client_opts, proc_def)
|
||||
end)
|
||||
|
||||
{:ok, sensors ++ cpu_sensors}
|
||||
else
|
||||
Logger.debug("No sensor definitions found for profile #{profile.os}")
|
||||
Logger.debug("No sensor or processor definitions found for profile #{profile.os}")
|
||||
{:ok, []}
|
||||
end
|
||||
end
|
||||
|
|
@ -99,6 +111,35 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
|
|||
|
||||
defp extract_manufacturer(_), do: "Unknown"
|
||||
|
||||
defp discover_processor_as_sensor(client_opts, proc_def) do
|
||||
# Try numeric OID first, fall back to named OID
|
||||
oid = proc_def.num_oid || proc_def.oid
|
||||
|
||||
if oid do
|
||||
# Remove template variables from OID
|
||||
walk_oid = clean_oid_template(oid)
|
||||
|
||||
case Client.walk(client_opts, walk_oid) do
|
||||
{:ok, results} when is_map(results) and map_size(results) > 0 ->
|
||||
# Convert walk results (map) to CPU sensor data
|
||||
Enum.map(results, fn {result_oid, value} ->
|
||||
build_processor_sensor_data(proc_def, %{oid: result_oid, value: value})
|
||||
end)
|
||||
|
||||
{:ok, _empty} ->
|
||||
Logger.debug("No data found for processor OID: #{walk_oid}")
|
||||
[]
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.warning("Failed to walk processor OID #{walk_oid}: #{inspect(reason)}")
|
||||
[]
|
||||
end
|
||||
else
|
||||
Logger.warning("Processor definition missing OID: #{inspect(proc_def)}")
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp discover_sensor_from_definition(client_opts, sensor_def) do
|
||||
# Try numeric OID first, fall back to named OID
|
||||
oid = sensor_def.num_oid || sensor_def.oid
|
||||
|
|
@ -109,10 +150,10 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
|
|||
walk_oid = clean_oid_template(oid)
|
||||
|
||||
case Client.walk(client_opts, walk_oid) do
|
||||
{:ok, results} when is_list(results) and results != [] ->
|
||||
# Convert walk results to sensor data
|
||||
Enum.map(results, fn result ->
|
||||
build_sensor_data(sensor_def, result)
|
||||
{:ok, results} when is_map(results) and map_size(results) > 0 ->
|
||||
# Convert walk results (map) to sensor data
|
||||
Enum.map(results, fn {result_oid, value} ->
|
||||
build_sensor_data(sensor_def, %{oid: result_oid, value: value})
|
||||
end)
|
||||
|
||||
{:ok, _empty} ->
|
||||
|
|
@ -141,7 +182,11 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
|
|||
|
||||
defp build_sensor_data(sensor_def, %{oid: oid, value: value}) do
|
||||
# Build sensor index from OID (extract last component)
|
||||
sensor_index = extract_index_from_oid(oid, sensor_def.index)
|
||||
oid_index = extract_index_from_oid(oid, sensor_def.index)
|
||||
|
||||
# Make index unique by combining sensor class with OID index
|
||||
# This prevents conflicts when multiple sensors have the same OID index (e.g., ".0")
|
||||
sensor_index = build_unique_sensor_index(sensor_def.sensor_class, sensor_def.descr, oid_index)
|
||||
|
||||
# Apply divisor to value if it's numeric
|
||||
last_value =
|
||||
|
|
@ -168,15 +213,54 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
|
|||
}
|
||||
end
|
||||
|
||||
defp build_processor_sensor_data(proc_def, %{oid: oid, value: value}) do
|
||||
# Build sensor index from OID (extract last component)
|
||||
sensor_index = extract_index_from_oid(oid, proc_def.index)
|
||||
|
||||
# Apply precision (divisor) to value if it's numeric
|
||||
last_value =
|
||||
cond do
|
||||
is_number(value) && proc_def.precision && proc_def.precision > 1 ->
|
||||
value / proc_def.precision
|
||||
|
||||
is_number(value) ->
|
||||
value * 1.0
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
|
||||
# Determine status based on CPU load percentage
|
||||
status =
|
||||
cond do
|
||||
is_nil(last_value) -> "unknown"
|
||||
last_value < 70 -> "ok"
|
||||
last_value < 90 -> "warning"
|
||||
true -> "critical"
|
||||
end
|
||||
|
||||
%{
|
||||
sensor_type: "percent",
|
||||
sensor_index: "cpu#{sensor_index}",
|
||||
sensor_oid: oid,
|
||||
sensor_descr: proc_def.descr || "CPU #{sensor_index}",
|
||||
sensor_unit: "%",
|
||||
sensor_divisor: proc_def.precision || 1,
|
||||
last_value: last_value,
|
||||
status: status
|
||||
}
|
||||
end
|
||||
|
||||
defp extract_index_from_oid(oid, configured_index) when is_binary(configured_index) do
|
||||
# If index is pre-configured, use it
|
||||
if configured_index == "" do
|
||||
# If index contains template variable or is empty, extract from OID
|
||||
if configured_index == "" or String.contains?(configured_index, "{{") do
|
||||
# Extract last component of OID as index
|
||||
oid
|
||||
|> String.split(".")
|
||||
|> List.last()
|
||||
|> to_string()
|
||||
else
|
||||
# Use pre-configured static index
|
||||
configured_index
|
||||
end
|
||||
end
|
||||
|
|
@ -189,6 +273,23 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
|
|||
|> to_string()
|
||||
end
|
||||
|
||||
defp build_unique_sensor_index(sensor_class, descr, oid_index) do
|
||||
# For scalar sensors (index 0), use a descriptive name from the description
|
||||
# For table sensors, use the OID index
|
||||
if oid_index == "0" && descr do
|
||||
# Convert description to a safe index string
|
||||
# E.g., "DFS Detected Count" -> "dfs_detected_count"
|
||||
# E.g., "GPS Status" -> "gps_status"
|
||||
descr
|
||||
|> String.downcase()
|
||||
|> String.replace(~r/[^a-z0-9]+/, "_")
|
||||
|> String.trim("_")
|
||||
else
|
||||
# For table entries, prepend sensor class to OID index
|
||||
"#{sensor_class}_#{oid_index}"
|
||||
end
|
||||
end
|
||||
|
||||
defp determine_unit("temperature"), do: "°C"
|
||||
defp determine_unit("voltage"), do: "V"
|
||||
defp determine_unit("current"), do: "A"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue