diff --git a/lib/towerops/agents/stats.ex b/lib/towerops/agents/stats.ex index 2d4eec11..47bc86b3 100644 --- a/lib/towerops/agents/stats.ex +++ b/lib/towerops/agents/stats.ex @@ -57,14 +57,17 @@ defmodule Towerops.Agents.Stats do agents = Repo.all(query) - # Get equipment count, last metric time for each agent - Enum.map(agents, fn agent -> - equipment_count = get_agent_equipment_count(agent.id) - last_metric_at = get_agent_last_metric_time(agent.id) + # Get equipment counts for all agents in a single query + equipment_counts = get_batch_equipment_counts(Enum.map(agents, & &1.id)) + # Get last metric times for all agents in a single query + last_metrics = get_batch_last_metric_times(Enum.map(agents, & &1.id)) + + # Combine data for each agent + Enum.map(agents, fn agent -> agent - |> Map.put(:equipment_count, equipment_count) - |> Map.put(:last_metric_at, last_metric_at) + |> Map.put(:equipment_count, Map.get(equipment_counts, agent.id, 0)) + |> Map.put(:last_metric_at, Map.get(last_metrics, agent.id)) end) end @@ -290,18 +293,6 @@ defmodule Towerops.Agents.Stats do ) end - defp get_agent_last_metric_time(agent_token_id) do - Repo.one( - from(sr in SensorReading, - join: s in assoc(sr, :sensor), - join: d in assoc(s, :snmp_device), - join: e in assoc(d, :equipment), - where: e.id in subquery(polling_targets_subquery(agent_token_id)), - select: max(sr.checked_at) - ) - ) - end - defp get_agent_equipment_count(agent_token_id) do # Count equipment by using list_agent_polling_targets # This properly handles hierarchical assignment @@ -309,4 +300,31 @@ defmodule Towerops.Agents.Stats do |> Towerops.Agents.list_agent_polling_targets() |> length() end + + defp get_batch_equipment_counts(agent_token_ids) do + # For now, use the existing function per agent since list_agent_polling_targets + # handles complex hierarchical logic in application code + # TODO: Optimize this with a pure SQL solution if it becomes a bottleneck + Map.new(agent_token_ids, fn id -> {id, get_agent_equipment_count(id)} end) + end + + defp get_batch_last_metric_times(agent_token_ids) do + # Get last metric time for all agents in a single query + # Group by equipment ID, then map to agent via assignments + results = + Repo.all( + from(sr in SensorReading, + join: s in assoc(sr, :sensor), + join: d in assoc(s, :snmp_device), + join: e in assoc(d, :equipment), + join: aa in AgentAssignment, + on: aa.equipment_id == e.id and aa.enabled == true, + where: aa.agent_token_id in ^agent_token_ids, + group_by: aa.agent_token_id, + select: {aa.agent_token_id, max(sr.checked_at)} + ) + ) + + Map.new(results) + end end