perf: optimize slow tests with bulk inserts and fewer iterations

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.
This commit is contained in:
Graham McIntire 2026-01-24 15:13:21 -06:00
parent 64ad63d3e3
commit fc37680931
No known key found for this signature in database
4 changed files with 108 additions and 74 deletions

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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()