From fc37680931f9ff3412b0ba2f48c225ec0e371e1e Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 24 Jan 2026 15:13:21 -0600 Subject: [PATCH] perf: optimize slow tests with bulk inserts and fewer iterations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Optimized the slowest tests to improve test suite performance: 1. Property-based IP validation test (6s → ~2.4s) - Reduced max_runs from 50 to 20 iterations - Still provides good coverage while being much faster 2. Database-heavy tests (~1s each) - Replaced individual create_check calls with bulk Repo.insert_all - Reduced check counts from 15 to 10 where adequate for testing - Fixed timestamp handling (truncate to :second for Ecto) - Fixed response_time_ms to use floats (schema requirement) - Removed updated_at (disabled in Check schema) - Tests affected: * get_device_latency_by_agent/2 * get_uptime_percentage/1 * get_latency_data/2 respects limit * AgentLatencyEvaluator tests Expected speedup: ~40% reduction in top 10 slowest tests (12.8s → ~7.7s) These optimizations maintain test coverage while significantly reducing database transaction overhead. --- test/towerops/agents/stats_test.exs | 57 ++++++++------ test/towerops/monitoring/ping_test.exs | 4 +- test/towerops/monitoring_test.exs | 75 +++++++++++-------- .../workers/agent_latency_evaluator_test.exs | 46 +++++++----- 4 files changed, 108 insertions(+), 74 deletions(-) diff --git a/test/towerops/agents/stats_test.exs b/test/towerops/agents/stats_test.exs index 1469b129..d26af74b 100644 --- a/test/towerops/agents/stats_test.exs +++ b/test/towerops/agents/stats_test.exs @@ -8,6 +8,7 @@ defmodule Towerops.Agents.StatsTest do alias Towerops.Agents.Stats alias Towerops.Devices.Device alias Towerops.Monitoring + alias Towerops.Monitoring.Check alias Towerops.Repo alias Towerops.Sites alias Towerops.Snmp.Device @@ -492,27 +493,39 @@ defmodule Towerops.Agents.StatsTest do snmp_version: "2c" }) - # Create checks from agent1 with avg 50ms - for _ <- 1..15 do - Monitoring.create_check(%{ - device_id: device.id, - agent_token_id: agent1.id, - status: :success, - response_time_ms: 50, - checked_at: DateTime.utc_now() - }) - end + # Create checks from agent1 with avg 50ms (bulk insert for speed) + now = DateTime.truncate(DateTime.utc_now(), :second) - # Create checks from agent2 with avg 100ms - for _ <- 1..15 do - Monitoring.create_check(%{ - device_id: device.id, - agent_token_id: agent2.id, - status: :success, - response_time_ms: 100, - checked_at: DateTime.utc_now() - }) - end + checks_agent1 = + for _ <- 1..10 do + %{ + id: Ecto.UUID.generate(), + device_id: device.id, + agent_token_id: agent1.id, + status: :success, + response_time_ms: 50.0, + checked_at: now, + inserted_at: now + } + end + + Repo.insert_all(Check, checks_agent1) + + # Create checks from agent2 with avg 100ms (bulk insert for speed) + checks_agent2 = + for _ <- 1..10 do + %{ + id: Ecto.UUID.generate(), + device_id: device.id, + agent_token_id: agent2.id, + status: :success, + response_time_ms: 100.0, + checked_at: now, + inserted_at: now + } + end + + Repo.insert_all(Check, checks_agent2) latency_by_agent = Stats.get_device_latency_by_agent(device.id) @@ -522,9 +535,9 @@ defmodule Towerops.Agents.StatsTest do agent2_latency = Enum.find(latency_by_agent, &(&1.agent_token_id == agent2.id)) assert agent1_latency.avg_latency_ms == 50.0 - assert agent1_latency.check_count == 15 + assert agent1_latency.check_count == 10 assert agent2_latency.avg_latency_ms == 100.0 - assert agent2_latency.check_count == 15 + assert agent2_latency.check_count == 10 end test "filters by successful checks only" do diff --git a/test/towerops/monitoring/ping_test.exs b/test/towerops/monitoring/ping_test.exs index a7234f8a..686f7c2a 100644 --- a/test/towerops/monitoring/ping_test.exs +++ b/test/towerops/monitoring/ping_test.exs @@ -101,7 +101,7 @@ defmodule Towerops.Monitoring.PingTest do # IPs with letters in octet position (use ?a..?z range) map(string(?a..?z, length: 3), &"192.168.#{&1}.1") ]), - max_runs: 50 + max_runs: 20 ) do # These fail at validation, before any system call result = Ping.ping(invalid_ip) @@ -115,7 +115,7 @@ defmodule Towerops.Monitoring.PingTest do octet2 <- integer(0..255), octet3 <- integer(0..255), octet4 <- integer(0..255), - max_runs: 50 + max_runs: 20 ) do ip = "#{octet1}.#{octet2}.#{octet3}.#{octet4}" # Just verify IP validation passes (don't actually ping) diff --git a/test/towerops/monitoring_test.exs b/test/towerops/monitoring_test.exs index 1bd2e917..5cfdd73b 100644 --- a/test/towerops/monitoring_test.exs +++ b/test/towerops/monitoring_test.exs @@ -208,40 +208,45 @@ defmodule Towerops.MonitoringTest do end test "get_uptime_percentage/1 returns uptime percentage", %{device: device} do - base_time = DateTime.utc_now() + base_time = DateTime.truncate(DateTime.utc_now(), :second) - # Create 3 successful and 1 failed check - {:ok, _} = - Monitoring.create_check(%{ + # Create 3 successful and 1 failed check (bulk insert for speed) + checks = [ + %{ + id: Ecto.UUID.generate(), device_id: device.id, status: :success, - response_time_ms: 50, - checked_at: DateTime.add(base_time, -60, :second) - }) - - {:ok, _} = - Monitoring.create_check(%{ + response_time_ms: 50.0, + checked_at: DateTime.add(base_time, -60, :second), + inserted_at: base_time + }, + %{ + id: Ecto.UUID.generate(), device_id: device.id, status: :success, - response_time_ms: 60, - checked_at: DateTime.add(base_time, -120, :second) - }) - - {:ok, _} = - Monitoring.create_check(%{ + response_time_ms: 60.0, + checked_at: DateTime.add(base_time, -120, :second), + inserted_at: base_time + }, + %{ + id: Ecto.UUID.generate(), device_id: device.id, status: :failure, response_time_ms: nil, - checked_at: DateTime.add(base_time, -180, :second) - }) - - {:ok, _} = - Monitoring.create_check(%{ + checked_at: DateTime.add(base_time, -180, :second), + inserted_at: base_time + }, + %{ + id: Ecto.UUID.generate(), device_id: device.id, status: :success, - response_time_ms: 70, - checked_at: DateTime.add(base_time, -240, :second) - }) + response_time_ms: 70.0, + checked_at: DateTime.add(base_time, -240, :second), + inserted_at: base_time + } + ] + + Repo.insert_all(Check, checks) result = Monitoring.get_uptime_percentage(device.id) assert result == 75.0 @@ -333,18 +338,22 @@ defmodule Towerops.MonitoringTest do end test "get_latency_data/2 respects limit option", %{device: device} do - base_time = ~U[2025-12-21 12:00:00Z] + base_time = DateTime.truncate(~U[2025-12-21 12:00:00Z], :second) - # Create 5 checks - for i <- 1..5 do - {:ok, _} = - Monitoring.create_check(%{ + # Create 5 checks (bulk insert for speed) + checks = + for i <- 1..5 do + %{ + id: Ecto.UUID.generate(), device_id: device.id, status: :success, - response_time_ms: 50 + i, - checked_at: DateTime.add(base_time, i * 60, :second) - }) - end + response_time_ms: (50 + i) * 1.0, + checked_at: DateTime.add(base_time, i * 60, :second), + inserted_at: base_time + } + end + + Repo.insert_all(Check, checks) since = DateTime.add(base_time, -3600, :second) result = Monitoring.get_latency_data(device.id, since: since, limit: 3) diff --git a/test/towerops/workers/agent_latency_evaluator_test.exs b/test/towerops/workers/agent_latency_evaluator_test.exs index e7ccaa7e..a5b36324 100644 --- a/test/towerops/workers/agent_latency_evaluator_test.exs +++ b/test/towerops/workers/agent_latency_evaluator_test.exs @@ -249,24 +249,36 @@ defmodule Towerops.Workers.AgentLatencyEvaluatorTest do snmp_version: "2c" }) - # Create checks from both cloud agents - for _ <- 1..15 do - Monitoring.create_check(%{ - device_id: device.id, - agent_token_id: slow_cloud.id, - status: :success, - response_time_ms: 100, - checked_at: DateTime.utc_now() - }) + # Create checks from both cloud agents (bulk insert for speed) + now = DateTime.truncate(DateTime.utc_now(), :second) - Monitoring.create_check(%{ - device_id: device.id, - agent_token_id: fast_cloud.id, - status: :success, - response_time_ms: 50, - checked_at: DateTime.utc_now() - }) - end + for_result = + for _ <- 1..10 do + [ + %{ + id: Ecto.UUID.generate(), + device_id: device.id, + agent_token_id: slow_cloud.id, + status: :success, + response_time_ms: 100.0, + checked_at: now, + inserted_at: now + }, + %{ + id: Ecto.UUID.generate(), + device_id: device.id, + agent_token_id: fast_cloud.id, + status: :success, + response_time_ms: 50.0, + checked_at: now, + inserted_at: now + } + ] + end + + checks = List.flatten(for_result) + + Repo.insert_all(Towerops.Monitoring.Check, checks) # Trigger evaluation AgentLatencyEvaluator.trigger_evaluation()