- k8s: 2 replicas, POOL_SIZE 25, cpu 2000m, mem 1Gi (was 1 replica, 5, 500m, 512Mi) - telemetry poller 10s -> 60s; drop pg_database_size, pg_stat_statements, pg_table_size and pg_indexes_size (these were ~7100s of cumulative DB time on their own) - get_latest_packet_for_callsign: bound by :packet_retention_days so partition pruning kicks in - get_nearby_stations_knn: ST_DWithin spatial pre-filter (default 500km) so the GiST geography index cuts candidates before DISTINCT ON sort (EXPLAIN: 5793ms -> 400ms) - New migration: partial functional indexes on upper(object_name) and upper(item_name), built per-partition CONCURRENTLY then attached to parent. Lets get_latest_packet_for_callsign use BitmapOr across all 3 upper() indexes instead of falling back to a scan
131 lines
4.5 KiB
Elixir
131 lines
4.5 KiB
Elixir
defmodule Aprsme.Telemetry.DatabaseMetricsTest do
|
|
# Uses DataCase for a sandbox DB checkout — some metrics run live queries
|
|
# against pg_stat_* views. async: false because we mutate :aprsme :env
|
|
# in one test.
|
|
use Aprsme.DataCase, async: false
|
|
|
|
alias Aprsme.Telemetry.DatabaseMetrics
|
|
|
|
defp attach_collector(event, tag \\ to_string(System.unique_integer([:positive]))) do
|
|
test_pid = self()
|
|
handler_id = "test-#{tag}-#{Enum.join(event, ",")}"
|
|
|
|
:telemetry.attach(
|
|
handler_id,
|
|
event,
|
|
fn ^event, measurements, metadata, _ ->
|
|
send(test_pid, {:telemetry, event, measurements, metadata})
|
|
end,
|
|
nil
|
|
)
|
|
|
|
on_exit(fn -> :telemetry.detach(handler_id) end)
|
|
handler_id
|
|
end
|
|
|
|
describe "collect_postgres_metrics/0 in :test env" do
|
|
test "is a no-op returning :ok" do
|
|
original = Application.get_env(:aprsme, :env)
|
|
Application.put_env(:aprsme, :env, :test)
|
|
|
|
try do
|
|
assert DatabaseMetrics.collect_postgres_metrics() == :ok
|
|
after
|
|
Application.put_env(:aprsme, :env, original)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "collect_db_pool_metrics/0" do
|
|
test "emits [:aprsme, :repo, :pool] telemetry with pool_size from config" do
|
|
attach_collector([:aprsme, :repo, :pool])
|
|
DatabaseMetrics.collect_db_pool_metrics()
|
|
|
|
assert_receive {:telemetry, [:aprsme, :repo, :pool], measurements, _}, 1_000
|
|
assert Map.has_key?(measurements, :size)
|
|
assert Map.has_key?(measurements, :idle)
|
|
assert Map.has_key?(measurements, :busy)
|
|
assert Map.has_key?(measurements, :available)
|
|
assert Map.has_key?(measurements, :queue_length)
|
|
assert Map.has_key?(measurements, :total)
|
|
end
|
|
end
|
|
|
|
describe "collect_pgbouncer_metrics/0" do
|
|
test "is a no-op returning :ok" do
|
|
assert DatabaseMetrics.collect_pgbouncer_metrics() == :ok
|
|
end
|
|
end
|
|
|
|
describe "collect_db_pool_metrics/0 when Repo is not running" do
|
|
test "emits unavailable metrics when no Repo is registered" do
|
|
# Stop the Repo for this test — we need to restart it afterward to avoid
|
|
# breaking downstream tests.
|
|
repo_pid = Process.whereis(Aprsme.Repo)
|
|
|
|
if repo_pid do
|
|
# Temporarily unregister without stopping to avoid disrupting other tests.
|
|
:erlang.unregister(Aprsme.Repo)
|
|
|
|
on_exit(fn ->
|
|
if !Process.whereis(Aprsme.Repo) do
|
|
true = Process.register(repo_pid, Aprsme.Repo)
|
|
end
|
|
end)
|
|
end
|
|
|
|
attach_collector([:aprsme, :repo, :pool])
|
|
DatabaseMetrics.collect_db_pool_metrics()
|
|
|
|
assert_receive {:telemetry, [:aprsme, :repo, :pool], measurements, _}, 1_000
|
|
# The "unavailable" helper reports mostly zeros.
|
|
assert measurements.size == Application.get_env(:aprsme, Aprsme.Repo)[:pool_size] || 10
|
|
assert measurements.idle == 0
|
|
assert measurements.busy == 0
|
|
end
|
|
end
|
|
|
|
describe "to_number/1 and to_number/2 helpers (private — exercised through emit)" do
|
|
# to_number/1 has 4 heads (nil, %Decimal{}, number, fallback). to_number/2 has
|
|
# the same shape for the :integer overload. We exercise these via the public
|
|
# functions that build telemetry payloads from query results.
|
|
|
|
test "emits packets_table telemetry with Decimal values converted via to_number/1" do
|
|
attach_collector([:aprsme, :postgres, :packets_table])
|
|
|
|
original = Application.get_env(:aprsme, :env)
|
|
Application.put_env(:aprsme, :env, :prod)
|
|
|
|
try do
|
|
DatabaseMetrics.collect_postgres_metrics()
|
|
after
|
|
Application.put_env(:aprsme, :env, original)
|
|
end
|
|
|
|
# Values should always be plain numbers, not Decimals.
|
|
assert_receive {:telemetry, [:aprsme, :postgres, :packets_table], measurements, _}, 2_000
|
|
Enum.each(measurements, fn {_k, v} -> assert is_number(v) end)
|
|
end
|
|
end
|
|
|
|
describe "collect_postgres_metrics/0 outside :test env" do
|
|
test "emits connections and packets_table telemetry events" do
|
|
original = Application.get_env(:aprsme, :env)
|
|
Application.put_env(:aprsme, :env, :prod)
|
|
|
|
attach_collector([:aprsme, :postgres, :connections])
|
|
attach_collector([:aprsme, :postgres, :packets_table])
|
|
|
|
try do
|
|
DatabaseMetrics.collect_postgres_metrics()
|
|
after
|
|
Application.put_env(:aprsme, :env, original)
|
|
end
|
|
|
|
assert_receive {:telemetry, [:aprsme, :postgres, :connections], conn_metrics, _}, 2_000
|
|
assert Map.has_key?(conn_metrics, :total)
|
|
assert Map.has_key?(conn_metrics, :active)
|
|
assert Map.has_key?(conn_metrics, :idle)
|
|
end
|
|
end
|
|
end
|