Add metadata support for storage sensors
Storage sensors (disk/memory usage) need to fetch two OIDs to calculate percentages: the 'used' value and the 'size' value. Previously, only the 'used' OID was stored and polled, resulting in meaningless raw values. Changes: - Added metadata JSONB field to snmp_sensors table - Updated Sensor schema to include metadata field - Updated MikroTik profile to store size_oid in metadata for storage sensors - Updated PollerWorker to handle percentage calculation sensors: - poll_simple_sensor: Standard OID fetch with divisor - poll_percentage_sensor: Fetches both used and size OIDs, calculates percentage Storage sensors now properly calculate and store percentage values during polling. NOTE: Existing MikroTik devices need to re-run SNMP discovery to populate the metadata field for storage sensors.
This commit is contained in:
parent
9918f1633c
commit
bd91e2a7c3
4 changed files with 77 additions and 29 deletions
|
|
@ -99,40 +99,36 @@ defmodule Towerops.Snmp.PollerWorker do
|
|||
|
||||
defp poll_sensors(sensors, client_opts, timestamp) do
|
||||
Enum.each(sensors, fn sensor ->
|
||||
case Client.get(client_opts, sensor.sensor_oid) do
|
||||
{:ok, raw_value} ->
|
||||
decoded_value = decode_snmp_value(raw_value)
|
||||
# Check if sensor requires special calculation (e.g., storage percentage)
|
||||
result =
|
||||
if sensor.metadata["calculation"] == "percentage" do
|
||||
poll_percentage_sensor(sensor, client_opts)
|
||||
else
|
||||
poll_simple_sensor(sensor, client_opts)
|
||||
end
|
||||
|
||||
if is_number(decoded_value) do
|
||||
# Calculate actual value using divisor
|
||||
value = decoded_value / sensor.sensor_divisor
|
||||
case result do
|
||||
{:ok, value} ->
|
||||
Snmp.create_sensor_reading(%{
|
||||
sensor_id: sensor.id,
|
||||
value: value,
|
||||
status: "ok",
|
||||
checked_at: timestamp
|
||||
})
|
||||
|
||||
# Determine status based on value (could be enhanced with thresholds)
|
||||
status = "ok"
|
||||
{:error, :non_numeric} ->
|
||||
Logger.debug("Non-numeric SNMP value for sensor #{sensor.sensor_descr}")
|
||||
|
||||
# Save the reading
|
||||
Snmp.create_sensor_reading(%{
|
||||
sensor_id: sensor.id,
|
||||
value: value,
|
||||
status: status,
|
||||
checked_at: timestamp
|
||||
})
|
||||
else
|
||||
Logger.debug("Non-numeric SNMP value for sensor #{sensor.sensor_descr}")
|
||||
|
||||
# Save reading with nil value
|
||||
Snmp.create_sensor_reading(%{
|
||||
sensor_id: sensor.id,
|
||||
value: nil,
|
||||
status: "unknown",
|
||||
checked_at: timestamp
|
||||
})
|
||||
end
|
||||
Snmp.create_sensor_reading(%{
|
||||
sensor_id: sensor.id,
|
||||
value: nil,
|
||||
status: "unknown",
|
||||
checked_at: timestamp
|
||||
})
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.warning("Failed to poll sensor #{sensor.sensor_descr}: #{inspect(reason)}")
|
||||
|
||||
# Save error status
|
||||
Snmp.create_sensor_reading(%{
|
||||
sensor_id: sensor.id,
|
||||
value: nil,
|
||||
|
|
@ -143,6 +139,43 @@ defmodule Towerops.Snmp.PollerWorker do
|
|||
end)
|
||||
end
|
||||
|
||||
defp poll_simple_sensor(sensor, client_opts) do
|
||||
case Client.get(client_opts, sensor.sensor_oid) do
|
||||
{:ok, raw_value} ->
|
||||
decoded_value = decode_snmp_value(raw_value)
|
||||
|
||||
if is_number(decoded_value) do
|
||||
# Calculate actual value using divisor
|
||||
value = decoded_value / sensor.sensor_divisor
|
||||
{:ok, value}
|
||||
else
|
||||
{:error, :non_numeric}
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp poll_percentage_sensor(sensor, client_opts) do
|
||||
# For percentage sensors (like storage), fetch both used and size
|
||||
size_oid = sensor.metadata["size_oid"]
|
||||
used_oid = sensor.sensor_oid
|
||||
|
||||
with {:ok, used_raw} <- Client.get(client_opts, used_oid),
|
||||
{:ok, size_raw} <- Client.get(client_opts, size_oid),
|
||||
used when is_number(used) <- decode_snmp_value(used_raw),
|
||||
size when is_number(size) <- decode_snmp_value(size_raw),
|
||||
true <- size > 0 do
|
||||
percentage = used / size * 100
|
||||
{:ok, percentage}
|
||||
else
|
||||
{:error, reason} -> {:error, reason}
|
||||
false -> {:error, :division_by_zero}
|
||||
_ -> {:error, :non_numeric}
|
||||
end
|
||||
end
|
||||
|
||||
defp poll_interfaces(interfaces, client_opts, timestamp) do
|
||||
Enum.each(interfaces, fn interface ->
|
||||
# Poll interface stats: ifInOctets, ifOutOctets, ifInErrors, ifOutErrors
|
||||
|
|
|
|||
|
|
@ -235,7 +235,11 @@ defmodule Towerops.Snmp.Profiles.Mikrotik do
|
|||
sensor_unit: "%",
|
||||
sensor_divisor: 1,
|
||||
last_value: percent,
|
||||
status: storage_status(type, percent)
|
||||
status: storage_status(type, percent),
|
||||
metadata: %{
|
||||
size_oid: size_oid,
|
||||
calculation: "percentage"
|
||||
}
|
||||
}
|
||||
end
|
||||
end)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ defmodule Towerops.Snmp.Sensor do
|
|||
field :monitored, :boolean, default: true
|
||||
field :last_value, :float
|
||||
field :last_checked_at, :utc_datetime
|
||||
field :metadata, :map, default: %{}
|
||||
|
||||
belongs_to :snmp_device, Towerops.Snmp.Device
|
||||
|
||||
|
|
@ -37,7 +38,8 @@ defmodule Towerops.Snmp.Sensor do
|
|||
:sensor_divisor,
|
||||
:monitored,
|
||||
:last_value,
|
||||
:last_checked_at
|
||||
:last_checked_at,
|
||||
:metadata
|
||||
])
|
||||
|> validate_required([:snmp_device_id, :sensor_type, :sensor_index, :sensor_oid])
|
||||
|> unique_constraint([:snmp_device_id, :sensor_index])
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Towerops.Repo.Migrations.AddSensorMetadata do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:snmp_sensors) do
|
||||
add :metadata, :map, default: %{}
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue