From 9674794e123a0ea6a7ef47ed951323554431d826 Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Wed, 6 Aug 2025 15:51:39 +0000 Subject: [PATCH 1/4] Clear the rates table on each report, even if there is no threshold set --- lib/rate_tracker.ex | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/lib/rate_tracker.ex b/lib/rate_tracker.ex index 68a6b0d..0cf9403 100644 --- a/lib/rate_tracker.ex +++ b/lib/rate_tracker.ex @@ -125,8 +125,9 @@ defmodule Instruments.RateTracker do # Extraordinarily unlikely to be zero, but if it is for some reason, we'll just skip this # and let the next report get it - if threshold != nil and time_since_report > 0 do - do_report(state, time_since_report, threshold) + if time_since_report > 0 do + counts = dump_and_clear_counts(state) + do_report(state, counts, time_since_report, threshold) end schedule_report() @@ -173,14 +174,24 @@ defmodule Instruments.RateTracker do table_name(:erlang.system_info(:scheduler_id)) end - defp aggregate_stats(table_data) do - Enum.reduce(table_data, %{}, fn {key, val}, acc -> - Map.update(acc, key, val, &(&1 + val)) + defp do_report(%__MODULE__{} = _state, _aggregated_counts, _time_since_report, nil) do + nil + end + + defp do_report(%__MODULE__{} = state, aggregated_counts, time_since_report, threshold) do + Enum.each(aggregated_counts, fn {key, num_tracked} -> + # Sampling correction is technically approximate (we don't know if Statix or another underlying lib will report this differently) + tracked_per_second = num_tracked / time_since_report * sample_rate_for_key(key) + + if tracked_per_second > threshold do + Enum.each(state.callbacks, fn callback -> callback.(key, tracked_per_second) end) + end end) end - defp do_report(%__MODULE__{} = state, time_since_report, threshold) do - dump_and_clear_data = fn scheduler_id -> + defp dump_and_clear_counts(%__MODULE__{} = state) do + 1..state.table_count + |> Enum.flat_map(fn scheduler_id -> table_name = table_name(scheduler_id) table_data = :ets.tab2list(table_name) @@ -189,18 +200,13 @@ defmodule Instruments.RateTracker do end) table_data - end - - 1..state.table_count - |> Enum.flat_map(dump_and_clear_data) + end) |> aggregate_stats() - |> Enum.each(fn {key, num_tracked} -> - # Sampling correction is technically approximate (we don't know if Statix or another underlying lib will report this differently) - tracked_per_second = num_tracked / time_since_report * sample_rate_for_key(key) + end - if tracked_per_second > threshold do - Enum.each(state.callbacks, fn callback -> callback.(key, tracked_per_second) end) - end + defp aggregate_stats(table_data) do + Enum.reduce(table_data, %{}, fn {key, val}, acc -> + Map.update(acc, key, val, &(&1 + val)) end) end From 70adf034c7fc666f7af5af2f56f061043261e95d Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Wed, 6 Aug 2025 15:52:49 +0000 Subject: [PATCH 2/4] Report rates in dump_rates as rates --- lib/rate_tracker.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/rate_tracker.ex b/lib/rate_tracker.ex index 0cf9403..fa11c51 100644 --- a/lib/rate_tracker.ex +++ b/lib/rate_tracker.ex @@ -107,6 +107,10 @@ defmodule Instruments.RateTracker do {_key, 0} -> false {_key, _rate} -> true end) + |> Enum.map(fn {key, count} -> + report_interval_seconds = @report_interval_ms / 1000 + {key, count / report_interval_seconds} + end) |> Enum.to_list() end From 1c517ee94b10fc5cfe4aa3f8cfd5d1c713226817 Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Wed, 6 Aug 2025 16:40:15 +0000 Subject: [PATCH 3/4] Handle dump_rates within the genserver --- lib/rate_tracker.ex | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/rate_tracker.ex b/lib/rate_tracker.ex index fa11c51..b083515 100644 --- a/lib/rate_tracker.ex +++ b/lib/rate_tracker.ex @@ -92,11 +92,17 @@ defmodule Instruments.RateTracker do @doc """ Dump the currently tracked rates """ - @spec dump_rates() :: [{{String.t(), Keyword.t()}, non_neg_integer()}] + @spec dump_rates() :: [{{String.t(), Keyword.t()}, non_neg_integer() | :infinity}] def dump_rates() do - table_count = :erlang.system_info(:schedulers) + GenServer.call(__MODULE__, :dump_rates) + end + + ## GenServer callbacks - 1..table_count + def handle_call(:dump_rates, _from, %__MODULE__{} = state) do + time_since_report = time() - state.last_update_time + + rates = 1..state.table_count |> Enum.flat_map(fn scheduler_id -> scheduler_id |> table_name() @@ -107,14 +113,14 @@ defmodule Instruments.RateTracker do {_key, 0} -> false {_key, _rate} -> true end) - |> Enum.map(fn {key, count} -> - report_interval_seconds = @report_interval_ms / 1000 - {key, count / report_interval_seconds} + |> Enum.map(fn + {key, _count} when time_since_report == 0 -> {key, :infinity} + {key, count} -> {key, count / time_since_report} end) |> Enum.to_list() - end - ## GenServer callbacks + {:reply, rates, state} + end def handle_cast({:subscribe, callback}, %__MODULE__{} = state) do state = %__MODULE__{state | callbacks: [callback | state.callbacks]} From 418b17fad7baf57a9ae5ae76a22e086e6378ef82 Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Wed, 6 Aug 2025 16:54:38 +0000 Subject: [PATCH 4/4] remove :infinity --- lib/rate_tracker.ex | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/lib/rate_tracker.ex b/lib/rate_tracker.ex index b083515..b13e368 100644 --- a/lib/rate_tracker.ex +++ b/lib/rate_tracker.ex @@ -92,7 +92,7 @@ defmodule Instruments.RateTracker do @doc """ Dump the currently tracked rates """ - @spec dump_rates() :: [{{String.t(), Keyword.t()}, non_neg_integer() | :infinity}] + @spec dump_rates() :: [{{String.t(), Keyword.t()}, non_neg_integer()}] def dump_rates() do GenServer.call(__MODULE__, :dump_rates) end @@ -101,24 +101,7 @@ defmodule Instruments.RateTracker do def handle_call(:dump_rates, _from, %__MODULE__{} = state) do time_since_report = time() - state.last_update_time - - rates = 1..state.table_count - |> Enum.flat_map(fn scheduler_id -> - scheduler_id - |> table_name() - |> :ets.tab2list() - end) - |> aggregate_stats() - |> Enum.filter(fn - {_key, 0} -> false - {_key, _rate} -> true - end) - |> Enum.map(fn - {key, _count} when time_since_report == 0 -> {key, :infinity} - {key, count} -> {key, count / time_since_report} - end) - |> Enum.to_list() - + rates = do_dump_rates(state, time_since_report) {:reply, rates, state} end @@ -146,6 +129,28 @@ defmodule Instruments.RateTracker do ## Private + defp do_dump_rates(_state, 0) do + [] + end + + defp do_dump_rates(state, time_since_report) do + 1..state.table_count + |> Enum.flat_map(fn scheduler_id -> + scheduler_id + |> table_name() + |> :ets.tab2list() + end) + |> aggregate_stats() + |> Enum.filter(fn + {_key, 0} -> false + {_key, _rate} -> true + end) + |> Enum.map(fn {key, count} -> + {key, count / time_since_report} + end) + |> Enum.to_list() + end + defp get_table_key(name, []) do {name, []} end