Fixed dialyzer warnings across multiple files by properly handling return
values from functions that are called for side effects.
Changes:
- lib/towerops/agents.ex: Explicitly match broadcast and if-expression returns
- lib/towerops/devices.ex: Match worker start function returns in if-expressions
- lib/towerops/workers/agent_latency_evaluator.ex: Match broadcast return
- lib/towerops/workers/device_monitor_worker.ex: Match perform_check and handle_status_change returns, handle schedule_next_check errors
- lib/towerops/workers/device_poller_worker.ex: Match all PubSub broadcast returns, handle schedule_next_poll errors, remove unreachable pattern
- lib/towerops/workers/stale_agent_worker.ex: Match process_stale_agents and broadcast returns
- lib/towerops_web/channels/agent_channel.ex: Match PubSub.subscribe return, improve get_addr pattern matching, remove unreachable format_ip clause
- lib/towerops_web/live/device_live/form.ex: Match enqueue_discovery return, replace compile-time @dev_env with runtime check
Pattern applied:
- PubSub broadcasts: `_ = Phoenix.PubSub.broadcast(...)`
- If-expressions with side effects: `_ = if condition do ... end`
- Critical operations: Added error logging with case statements
All 3625 tests passing. No dialyzer warnings remaining in lib/towerops or lib/towerops_web.
🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
130 lines
3.7 KiB
Elixir
130 lines
3.7 KiB
Elixir
defmodule Towerops.Workers.AgentLatencyEvaluator do
|
|
@moduledoc """
|
|
Oban worker that periodically evaluates device-to-agent latency and performs automatic reassignments.
|
|
|
|
Runs every 5 minutes via Oban.Plugins.Cron to:
|
|
1. Find devices where an alternative agent has significantly better latency (20%+ improvement)
|
|
2. Reassign devices based on their assignment source (site, organization, global, or device-level)
|
|
3. Broadcast PubSub events for UI updates
|
|
4. Log all reassignment decisions with improvement metrics
|
|
|
|
Only considers devices with automatic assignments (site, organization, global, or none).
|
|
Manual device-level assignments are never automatically changed.
|
|
"""
|
|
use Oban.Worker, queue: :maintenance
|
|
|
|
alias Towerops.Agents
|
|
alias Towerops.Agents.Stats
|
|
alias Towerops.Devices.Device
|
|
alias Towerops.Organizations
|
|
alias Towerops.Repo
|
|
alias Towerops.Sites
|
|
|
|
require Logger
|
|
|
|
# Minimum improvement required for reassignment (20%)
|
|
@min_improvement_percent 20
|
|
|
|
# Minimum checks per agent for statistical validity
|
|
@min_checks_per_agent 10
|
|
|
|
@impl Oban.Worker
|
|
def perform(%Oban.Job{}) do
|
|
start_time = System.monotonic_time(:millisecond)
|
|
|
|
candidates =
|
|
Stats.find_reassignment_candidates(
|
|
min_improvement_percent: @min_improvement_percent,
|
|
min_checks_per_agent: @min_checks_per_agent
|
|
)
|
|
|
|
reassignment_count =
|
|
candidates
|
|
|> Enum.map(&reassign_device/1)
|
|
|> Enum.count(&(&1 == :ok))
|
|
|
|
duration_ms = System.monotonic_time(:millisecond) - start_time
|
|
|
|
Logger.info(
|
|
"Latency evaluation completed: #{length(candidates)} candidate(s) found, " <>
|
|
"#{reassignment_count} device(s) reassigned in #{duration_ms}ms"
|
|
)
|
|
|
|
:ok
|
|
end
|
|
|
|
defp reassign_device(candidate) do
|
|
Logger.info(
|
|
"Reassigning device '#{candidate.device_name}' " <>
|
|
"(#{candidate.improvement_percent}% improvement: " <>
|
|
"#{candidate.current_latency_ms}ms → #{candidate.best_latency_ms}ms)",
|
|
device_id: candidate.device_id,
|
|
device_name: candidate.device_name,
|
|
old_agent: candidate.current_agent_token_id,
|
|
new_agent: candidate.best_agent_token_id,
|
|
improvement_percent: candidate.improvement_percent,
|
|
assignment_source: candidate.assignment_source
|
|
)
|
|
|
|
result =
|
|
case candidate.assignment_source do
|
|
:site ->
|
|
reassign_at_site_level(candidate)
|
|
|
|
:organization ->
|
|
reassign_at_organization_level(candidate)
|
|
|
|
source when source in [:global, :none] ->
|
|
reassign_at_device_level(candidate)
|
|
end
|
|
|
|
case result do
|
|
{:ok, _} ->
|
|
_ = broadcast_reassignment(candidate)
|
|
:ok
|
|
|
|
{:error, reason} ->
|
|
Logger.error(
|
|
"Failed to reassign device '#{candidate.device_name}': #{inspect(reason)}",
|
|
device_id: candidate.device_id,
|
|
error: reason
|
|
)
|
|
|
|
:error
|
|
end
|
|
end
|
|
|
|
defp reassign_at_site_level(candidate) do
|
|
device = Device |> Repo.get!(candidate.device_id) |> Repo.preload(:site)
|
|
|
|
Sites.update_site(device.site, %{agent_token_id: candidate.best_agent_token_id})
|
|
end
|
|
|
|
defp reassign_at_organization_level(candidate) do
|
|
device =
|
|
Device
|
|
|> Repo.get!(candidate.device_id)
|
|
|> Repo.preload(site: :organization)
|
|
|
|
organization = device.site.organization
|
|
|
|
Organizations.update_organization(organization, %{
|
|
default_agent_token_id: candidate.best_agent_token_id
|
|
})
|
|
end
|
|
|
|
defp reassign_at_device_level(candidate) do
|
|
Agents.update_device_assignment(
|
|
candidate.device_id,
|
|
candidate.best_agent_token_id
|
|
)
|
|
end
|
|
|
|
defp broadcast_reassignment(candidate) do
|
|
Phoenix.PubSub.broadcast(
|
|
Towerops.PubSub,
|
|
"devices:latency",
|
|
{:device_reassigned, candidate}
|
|
)
|
|
end
|
|
end
|