feat: Add comprehensive agent polling for all discoverable data
Extended agent polling to collect all available SNMP data during regular polls, not just sensors and interfaces. This ensures data stays current and reduces reliance on periodic discovery runs. Changes: - Added polling queries for: neighbors (LLDP/CDP), ARP tables, MAC tables, IP addresses, HOST-RESOURCES processors and storage - Process additional data using Replay adapter pattern to reuse existing parsing logic from discovery modules - Made discovery helper functions public for reuse in polling context: save_neighbors/2, save_arp_entries/3, sync_ip_addresses/2, sync_processors/2, sync_storage/2 - Optimized by fetching interfaces once and reusing across operations - Fixed compilation warnings by using proper guard syntax (list != []) Technical details: - Detection threshold: polls with more OIDs than sensors+interfaces are processed for additional data - Background processing: spawns async process to avoid blocking metric recording - Replay adapter: allows reusing discovery parsing logic without re-querying SNMP device
This commit is contained in:
parent
5f0549908e
commit
bf4fee4846
2 changed files with 139 additions and 11 deletions
|
|
@ -825,7 +825,10 @@ defmodule Towerops.Snmp.Discovery do
|
|||
end
|
||||
|
||||
@spec sync_ip_addresses(Device.t(), [map()]) :: :ok
|
||||
defp sync_ip_addresses(device, discovered_ip_addresses) do
|
||||
@doc """
|
||||
Sync IP addresses from discovery/polling results to database.
|
||||
"""
|
||||
def sync_ip_addresses(device, discovered_ip_addresses) do
|
||||
# Build a map of if_index to interface ID from the device's interfaces
|
||||
if_index_to_interface =
|
||||
Map.new(device.interfaces, fn iface -> {iface.if_index, iface.id} end)
|
||||
|
|
@ -912,7 +915,10 @@ defmodule Towerops.Snmp.Discovery do
|
|||
end
|
||||
|
||||
@spec sync_processors(Device.t(), [map()]) :: :ok
|
||||
defp sync_processors(device, discovered_processors) do
|
||||
@doc """
|
||||
Sync processors from discovery/polling results to database.
|
||||
"""
|
||||
def sync_processors(device, discovered_processors) do
|
||||
# Get existing processors for this device, indexed by processor_index
|
||||
existing_processors =
|
||||
from(p in Processor, where: p.snmp_device_id == ^device.id)
|
||||
|
|
@ -959,7 +965,10 @@ defmodule Towerops.Snmp.Discovery do
|
|||
end
|
||||
|
||||
@spec sync_storage(Device.t(), [map()]) :: :ok
|
||||
defp sync_storage(device, discovered_storage) do
|
||||
@doc """
|
||||
Sync storage from discovery/polling results to database.
|
||||
"""
|
||||
def sync_storage(device, discovered_storage) do
|
||||
# Get existing storage for this device, indexed by storage_index
|
||||
existing_storage =
|
||||
from(s in Storage, where: s.snmp_device_id == ^device.id)
|
||||
|
|
@ -1024,7 +1033,10 @@ defmodule Towerops.Snmp.Discovery do
|
|||
end
|
||||
|
||||
@spec save_neighbors(binary(), [map()]) :: :ok
|
||||
defp save_neighbors(device_id, neighbors) do
|
||||
@doc """
|
||||
Save discovered neighbors to database.
|
||||
"""
|
||||
def save_neighbors(device_id, neighbors) do
|
||||
# Delete stale neighbors (not seen in last 5 minutes)
|
||||
cutoff = DateTime.add(DateTime.utc_now(), -5, :minute)
|
||||
Towerops.Snmp.delete_stale_neighbors(device_id, cutoff)
|
||||
|
|
@ -1039,7 +1051,10 @@ defmodule Towerops.Snmp.Discovery do
|
|||
end
|
||||
|
||||
@spec save_arp_entries(binary(), [map()], [Interface.t()]) :: :ok
|
||||
defp save_arp_entries(device_id, arp_entries, interfaces) do
|
||||
@doc """
|
||||
Save discovered ARP entries to database.
|
||||
"""
|
||||
def save_arp_entries(device_id, arp_entries, interfaces) do
|
||||
# Delete stale ARP entries (not seen in last 30 minutes)
|
||||
cutoff = DateTime.add(DateTime.utc_now(), -30, :minute)
|
||||
Towerops.Snmp.delete_stale_arp_entries(device_id, cutoff)
|
||||
|
|
|
|||
|
|
@ -719,6 +719,36 @@ defmodule ToweropsWeb.AgentChannel do
|
|||
"1.3.6.1.4.1.9.9.23"
|
||||
]
|
||||
|
||||
# ARP table OIDs
|
||||
arp_oids = [
|
||||
# IP-MIB ipNetToMediaTable (IPv4 ARP)
|
||||
"1.3.6.1.2.1.4.22",
|
||||
# IP-MIB ipNetToPhysicalTable (RFC 4293 - unified IPv4/IPv6)
|
||||
"1.3.6.1.2.1.4.35"
|
||||
]
|
||||
|
||||
# MAC address table OIDs
|
||||
mac_oids = [
|
||||
# BRIDGE-MIB dot1dTpFdbTable
|
||||
"1.3.6.1.2.1.17.4.3"
|
||||
]
|
||||
|
||||
# IP address OIDs (for polling changes)
|
||||
ip_address_oids = [
|
||||
# IP-MIB ipAddrTable (IPv4)
|
||||
"1.3.6.1.2.1.4.20",
|
||||
# IP-MIB ipAddressTable (IPv6)
|
||||
"1.3.6.1.2.1.4.34"
|
||||
]
|
||||
|
||||
# HOST-RESOURCES-MIB for processors and storage
|
||||
host_resources_oids = [
|
||||
# hrProcessorTable
|
||||
"1.3.6.1.2.1.25.3.3",
|
||||
# hrStorageTable
|
||||
"1.3.6.1.2.1.25.2.3"
|
||||
]
|
||||
|
||||
[
|
||||
%SnmpQuery{
|
||||
query_type: :GET,
|
||||
|
|
@ -726,7 +756,7 @@ defmodule ToweropsWeb.AgentChannel do
|
|||
},
|
||||
%SnmpQuery{
|
||||
query_type: :WALK,
|
||||
oids: neighbor_oids
|
||||
oids: neighbor_oids ++ arp_oids ++ mac_oids ++ ip_address_oids ++ host_resources_oids
|
||||
}
|
||||
]
|
||||
end
|
||||
|
|
@ -887,17 +917,100 @@ defmodule ToweropsWeb.AgentChannel do
|
|||
timestamp = DateTime.from_unix!(result.timestamp, :second)
|
||||
|
||||
Logger.info(
|
||||
"Processing polling result for #{device.name}: #{map_size(oid_values)} OIDs, #{length(snmp_device.sensors)} sensors"
|
||||
"Processing polling result for #{device.name}: #{map_size(oid_values)} OIDs, #{length(snmp_device.sensors)} sensors, #{length(snmp_device.interfaces)} interfaces"
|
||||
)
|
||||
|
||||
# Process sensor readings
|
||||
# Process sensor readings (quick path - already in memory)
|
||||
Enum.each(snmp_device.sensors, &process_sensor_reading(&1, oid_values, timestamp))
|
||||
|
||||
# Process interface stats
|
||||
# Process interface stats (quick path - already in memory)
|
||||
Enum.each(device.snmp_device.interfaces, &process_interface_stats(&1, oid_values, timestamp))
|
||||
|
||||
# Note: Neighbor discovery is handled by separate polling worker
|
||||
# since it requires complex LLDP/CDP parsing
|
||||
# Process complex SNMP data (neighbors, ARP, MAC, IP addresses, processors, storage)
|
||||
# using Replay adapter to reuse existing parsing logic
|
||||
if map_size(oid_values) > length(snmp_device.sensors) + (length(snmp_device.interfaces) * 6) do
|
||||
process_additional_polling_data(device, oid_values)
|
||||
end
|
||||
end
|
||||
|
||||
# Process neighbors, ARP, MAC, IP addresses, processors, storage from agent polling
|
||||
defp process_additional_polling_data(device, oid_values) do
|
||||
# Build Replay adapter opts to reuse existing parsing logic
|
||||
client_opts = [
|
||||
ip: device.ip_address,
|
||||
adapter: Towerops.Snmp.Adapters.Replay,
|
||||
oid_map: oid_values
|
||||
]
|
||||
|
||||
# Reuse existing polling functions with Replay adapter
|
||||
spawn(fn ->
|
||||
alias Towerops.Snmp.Profiles.Base
|
||||
alias Towerops.Snmp.NeighborDiscovery
|
||||
|
||||
# Fetch interfaces once for reuse across multiple operations
|
||||
interfaces = Towerops.Snmp.list_interfaces(device.snmp_device.id)
|
||||
|
||||
# Process neighbors (LLDP/CDP)
|
||||
case NeighborDiscovery.discover_neighbors(client_opts, interfaces) do
|
||||
{:ok, neighbors} when neighbors != [] ->
|
||||
Towerops.Snmp.Discovery.save_neighbors(device.id, neighbors)
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
# Process ARP entries
|
||||
case Towerops.Snmp.ArpDiscovery.discover_arp_table(client_opts) do
|
||||
{:ok, arp_entries} when arp_entries != [] ->
|
||||
Towerops.Snmp.Discovery.save_arp_entries(device.id, arp_entries, interfaces)
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
# Process MAC addresses
|
||||
case Towerops.Snmp.MacDiscovery.discover_mac_table(client_opts) do
|
||||
{:ok, mac_addresses} when mac_addresses != [] ->
|
||||
Towerops.Snmp.upsert_mac_addresses(device.id, mac_addresses, interfaces)
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
# Process IP addresses
|
||||
case Base.discover_all_ip_addresses(client_opts) do
|
||||
{:ok, ip_addresses} when ip_addresses != [] ->
|
||||
discovered_device = %{
|
||||
device_id: device.id,
|
||||
interfaces: interfaces
|
||||
}
|
||||
|
||||
Towerops.Snmp.Discovery.sync_ip_addresses(discovered_device, ip_addresses)
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
# Process processors
|
||||
case Base.discover_processors(client_opts) do
|
||||
{:ok, processors} when processors != [] ->
|
||||
discovered_device = %{device_id: device.id}
|
||||
Towerops.Snmp.Discovery.sync_processors(discovered_device, processors)
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
# Process storage
|
||||
case Base.discover_storage(client_opts) do
|
||||
{:ok, storage} when storage != [] ->
|
||||
discovered_device = %{device_id: device.id}
|
||||
Towerops.Snmp.Discovery.sync_storage(discovered_device, storage)
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp process_sensor_reading(sensor, oid_values, timestamp) do
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue