From 16aac9c9bd708792668eda870ec9224b2f4a2ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Niemier?= <~@hauleth.dev> Date: Sun, 22 Feb 2026 23:16:53 +0100 Subject: [PATCH] Replace runtime codegen with functions defined in `Peep.EventHandler` --- lib/peep.ex | 17 +++++- lib/peep/codegen.ex | 113 -------------------------------------- lib/peep/event_handler.ex | 80 +++++++++++++++++++++++++-- lib/peep/persistent.ex | 16 ++++-- test/codegen_test.exs | 39 ------------- test/prometheus_test.exs | 50 +++++++++++++++++ test/statsd_test.exs | 44 +++++++++++++++ 7 files changed, 196 insertions(+), 163 deletions(-) delete mode 100644 lib/peep/codegen.ex delete mode 100644 test/codegen_test.exs diff --git a/lib/peep.ex b/lib/peep.ex index b50bd87..67126a4 100644 --- a/lib/peep.ex +++ b/lib/peep.ex @@ -141,14 +141,27 @@ defmodule Peep do """ def get_all_metrics(name) do case Peep.Persistent.fetch(name) do - Peep.Persistent.persistent(storage: {storage_mod, storage}) = p -> + Peep.Persistent.persistent(storage: {storage_mod, storage}, global_tags: global_tags) = p -> storage_mod.get_all_metrics(storage, p) + |> extend_with(global_tags) _ -> nil end end + defp extend_with(metrics, tags) when tags == %{}, do: metrics + + defp extend_with(metrics, global_tags) do + Map.new(metrics, fn {metric, measurements} -> + updated = Map.new(measurements, fn {tags, val} -> + {Map.merge(global_tags, tags), val} + end) + + {metric, updated} + end) + end + @doc """ Removes metrics whose metadata contains the specified tag patterns. @@ -245,7 +258,6 @@ defmodule Peep do Peep.Persistent.new(options) |> Peep.Persistent.store() - :ok = Peep.Codegen.create(options) handler_ids = EventHandler.attach(name) statsd_opts = options.statsd @@ -301,7 +313,6 @@ defmodule Peep do @impl true def terminate(_reason, %{name: name, handler_ids: handler_ids}) do - Peep.Codegen.purge(name) Peep.Persistent.erase(name) EventHandler.detach(handler_ids) end diff --git a/lib/peep/codegen.ex b/lib/peep/codegen.ex deleted file mode 100644 index 9151a20..0000000 --- a/lib/peep/codegen.ex +++ /dev/null @@ -1,113 +0,0 @@ -defmodule Peep.Codegen do - @moduledoc false - - alias Peep.Options - - def module(peep_name) do - :"Peep.Codegen.#{peep_name}" - end - - def create(%Options{} = peep_options) do - %Options{name: name, global_tags: global_tags} = peep_options - - module_name = module(name) - handle_event_ast = build_handle_event_ast(name) - other_funs_ast = other_funs_ast() - - module_ast = - quote do - defmodule unquote(module_name) do - import Peep.Persistent, only: [fast_fetch: 1, persistent: 1] - - @compile {:inline, global_tags: 0} - - def global_tags(), do: unquote(Macro.escape(global_tags)) - def name(), do: unquote(name) - - unquote(handle_event_ast) - unquote(other_funs_ast) - end - end - - [{_module, _bin}] = Code.compile_quoted(module_ast) - :ok - end - - def purge(peep_name) do - :code.purge(module(peep_name)) - end - - defp build_handle_event_ast(peep_name) do - quote do - def handle_event(event, measurements, metadata, _) do - global_tags = global_tags() - - persistent( - events_to_metrics: %{^event => metrics}, - storage: {storage_mod, storage} - ) = fast_fetch(unquote(peep_name)) - - :lists.foreach( - fn {metric, id} -> - %{ - measurement: measurement, - tag_values: tag_values, - tags: tags, - keep: keep - } = metric - - if keep?(keep, metadata, measurement) do - # credo:disable-for-next-line Credo.Check.Refactor.Nesting - case fetch_measurement(measurement, measurements, metadata) do - value when is_number(value) -> - tag_values = tag_values.(metadata) - tags = Map.merge(global_tags, Map.take(tag_values, tags)) - storage_mod.insert_metric(storage, id, metric, value, tags) - - _ -> - nil - end - end - end, - metrics - ) - end - end - end - - # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity - defp other_funs_ast() do - quote do - defp keep?(keep, metadata, measurement) when is_function(keep, 2), - do: keep.(metadata, measurement) - - defp keep?(keep, metadata, _measurement) when is_function(keep, 1), do: keep.(metadata) - defp keep?(_keep, _metadata, _measurement), do: true - - defp fetch_measurement(%Telemetry.Metrics.Counter{}, _measurements, _metadata) do - 1 - end - - defp fetch_measurement(measurement, measurements, metadata) do - case measurement do - nil -> - nil - - fun when is_function(fun, 1) -> - fun.(measurements) - - fun when is_function(fun, 2) -> - fun.(measurements, metadata) - - key -> - case measurements do - %{^key => nil} -> 1 - %{^key => false} -> 1 - %{^key => value} -> value - _ -> 1 - end - end - end - end - end -end diff --git a/lib/peep/event_handler.ex b/lib/peep/event_handler.ex index c73d1c4..36c9fdc 100644 --- a/lib/peep/event_handler.ex +++ b/lib/peep/event_handler.ex @@ -1,13 +1,12 @@ defmodule Peep.EventHandler do @moduledoc false - @compile :inline + @compile {:inline, keep?: 3, meta: 3, fetch_measurement: 3} import Peep.Persistent, only: [persistent: 1] def attach(name) do persistent(events_to_metrics: metrics_by_event) = Peep.Persistent.fetch(name) - module = Peep.Codegen.module(name) for {event_name, _metrics} <- metrics_by_event do handler_id = handler_id(event_name, name) @@ -16,8 +15,8 @@ defmodule Peep.EventHandler do :telemetry.attach( handler_id, event_name, - &module.handle_event/4, - [] + &__MODULE__.handle_event/4, + name ) handler_id @@ -32,4 +31,77 @@ defmodule Peep.EventHandler do defp handler_id(event_name, peep_name) do {__MODULE__, peep_name, event_name} end + + def handle_event(event, measurements, metadata, name) do + persistent( + events_to_metrics: %{^event => metrics}, + storage: {storage_mod, storage} + ) = Peep.Persistent.fetch(name) + + store_metrics(metrics, measurements, metadata, storage_mod, storage) + end + + defp store_metrics([], _measurements, _metadata, _mod, _data), do: :ok + + defp store_metrics([{metric, id} | rest], measurements, metadata, mod, data) do + %{ + measurement: measurement, + tag_values: tag_values, + tags: tags, + keep: keep + } = metric + + if keep?(keep, metadata, measurement) do + # credo:disable-for-next-line Credo.Check.Refactor.Nesting + case fetch_measurement(measurement, measurements, metadata) do + value when is_number(value) -> + mod.insert_metric( + data, + id, + metric, + value, + meta(metadata, tag_values, tags) + ) + + _ -> + nil + end + end + + store_metrics(rest, measurements, metadata, mod, data) + end + + defp keep?(keep, metadata, measurement) when is_function(keep, 2), + do: keep.(metadata, measurement) + + defp keep?(keep, metadata, _measurement) when is_function(keep, 1), do: keep.(metadata) + defp keep?(_keep, _metadata, _measurement), do: true + + # When selected list is empty, just return empty map + defp meta(_tags, _map, []), do: %{} + defp meta(meta, _map, tags) when is_function(tags, 1), do: tags.(meta) + defp meta(tags, map, keys), do: Map.take(map.(tags), keys) + + defp fetch_measurement(%Telemetry.Metrics.Counter{}, _measurements, _metadata) do + 1 + end + + defp fetch_measurement(measurement, measurements, metadata) do + case measurement do + nil -> + nil + + fun when is_function(fun, 1) -> + fun.(measurements) + + fun when is_function(fun, 2) -> + fun.(measurements, metadata) + + key -> + case measurements do + %{^key => value} -> value + _ -> 1 + end + end + end end diff --git a/lib/peep/persistent.ex b/lib/peep/persistent.ex index 862fe44..ae6af0a 100644 --- a/lib/peep/persistent.ex +++ b/lib/peep/persistent.ex @@ -8,7 +8,8 @@ defmodule Peep.Persistent do :storage, events_to_metrics: %{}, ids_to_metrics: %{}, - metrics_to_ids: %{} + metrics_to_ids: %{}, + global_tags: %{} ]) @compile {:inline, key: 1, fetch: 1} @@ -28,12 +29,18 @@ defmodule Peep.Persistent do storage: storage(), events_to_metrics: events_to_metrics(), ids_to_metrics: ids_to_metrics(), - metrics_to_ids: metrics_to_ids() + metrics_to_ids: metrics_to_ids(), + global_tags: map() ) @spec new(Peep.Options.t()) :: t() def new(%Peep.Options{} = options) do - %Peep.Options{name: name, storage: storage_impl, metrics: metrics} = options + %Peep.Options{ + name: name, + storage: storage_impl, + metrics: metrics, + global_tags: global_tags + } = options storage = case storage_impl do @@ -58,7 +65,8 @@ defmodule Peep.Persistent do storage: storage, events_to_metrics: events_to_metrics, ids_to_metrics: ids_to_metrics, - metrics_to_ids: metrics_to_ids + metrics_to_ids: metrics_to_ids, + global_tags: global_tags ) end diff --git a/test/codegen_test.exs b/test/codegen_test.exs deleted file mode 100644 index 79f964c..0000000 --- a/test/codegen_test.exs +++ /dev/null @@ -1,39 +0,0 @@ -defmodule Codegen.Test do - use ExUnit.Case - - alias Peep.Support.StorageCounter - alias Telemetry.Metrics - - defp metrics do - counter = Metrics.counter("peep.counter", event_name: [:counter]) - sum = Metrics.sum("peep.sum", event_name: [:sum], measurement: :count) - - last_value = - Metrics.last_value("peep.gauge", event_name: [:gauge], measurement: :value) - - distribution = - Metrics.distribution("peep.dist", - event_name: [:dist], - measurement: :value, - reporter_options: [max_value: 100] - ) - - [counter, sum, last_value, distribution] - end - - test "module exists after Peep starts" do - name = StorageCounter.fresh_id() - - options = [ - name: name, - metrics: metrics() - ] - - assert {:ok, _pid} = Peep.start_link(options) - module = Peep.Codegen.module(name) - - for {%{event_name: event_name} = metric, id} <- metrics() do - assert module.metrics(event_name) == [{metric, id}] - end - end -end diff --git a/test/prometheus_test.exs b/test/prometheus_test.exs index 79801de..97e3f38 100644 --- a/test/prometheus_test.exs +++ b/test/prometheus_test.exs @@ -14,6 +14,56 @@ defmodule PrometheusTest do @impls [:default, :striped] for impl <- @impls do + describe "#{impl} - global metadata" do + test "is present in formatted output" do + counter = Metrics.counter("prometheus.test.counter", description: "a counter") + name = StorageCounter.fresh_id() + + opts = [ + name: name, + metrics: [counter], + storage: unquote(impl), + global_tags: %{foo: :bar} + ] + + {:ok, _pid} = Peep.start_link(opts) + + Peep.insert_metric(name, counter, 1, %{baz: "quux"}) + + expected = [ + "# HELP prometheus_test_counter a counter", + "# TYPE prometheus_test_counter counter", + ~s(prometheus_test_counter{baz="quux",foo="bar"} 1) + ] + + assert export(name) == lines_to_string(expected) + end + + test "can be overridden by event metadata" do + counter = Metrics.counter("prometheus.test.counter", description: "a counter") + name = StorageCounter.fresh_id() + + opts = [ + name: name, + metrics: [counter], + storage: unquote(impl), + global_tags: %{foo: :bar} + ] + + {:ok, _pid} = Peep.start_link(opts) + + Peep.insert_metric(name, counter, 1, %{foo: 2137, baz: "quux"}) + + expected = [ + "# HELP prometheus_test_counter a counter", + "# TYPE prometheus_test_counter counter", + ~s(prometheus_test_counter{baz="quux",foo="2137"} 1) + ] + + assert export(name) == lines_to_string(expected) + end + end + test "#{impl} - counter formatting" do counter = Metrics.counter("prometheus.test.counter", description: "a counter") name = StorageCounter.fresh_id() diff --git a/test/statsd_test.exs b/test/statsd_test.exs index bb54a50..e9295c3 100644 --- a/test/statsd_test.exs +++ b/test/statsd_test.exs @@ -9,6 +9,50 @@ defmodule StatsdTest do @impls [:default, :striped] for impl <- @impls do + describe "#{impl} - global metadata" do + test "is present in formatted output" do + counter = Metrics.counter("statsd.test.counter", description: "a counter") + name = StorageCounter.fresh_id() + + opts = [ + name: name, + metrics: [counter], + storage: unquote(impl), + global_tags: %{foo: :bar} + ] + + {:ok, _pid} = Peep.start_link(opts) + + for _ <- 1..10 do + Peep.insert_metric(name, counter, 1, %{bar: "quuz"}) + end + + expected = ["statsd.test.counter:10|c|#foo:bar,bar:quuz"] + assert parse_packets(get_statsd_packets(name)) == parse_packets(expected) + end + + test "can be overridden by event metadata" do + counter = Metrics.counter("statsd.test.counter", description: "a counter") + name = StorageCounter.fresh_id() + + opts = [ + name: name, + metrics: [counter], + storage: unquote(impl), + global_tags: %{foo: :bar} + ] + + {:ok, _pid} = Peep.start_link(opts) + + for _ <- 1..10 do + Peep.insert_metric(name, counter, 1, %{foo: 2137, bar: "quuz"}) + end + + expected = ["statsd.test.counter:10|c|#foo:2137,bar:quuz"] + assert parse_packets(get_statsd_packets(name)) == parse_packets(expected) + end + end + test "#{impl} - a counter can be formatted" do name = StorageCounter.fresh_id()