diff --git a/lib/music_library_web/router.ex b/lib/music_library_web/router.ex index 1cd68282..59fac56d 100644 --- a/lib/music_library_web/router.ex +++ b/lib/music_library_web/router.ex @@ -67,6 +67,7 @@ defmodule MusicLibraryWeb.Router do live_dashboard "/dashboard", metrics: MusicLibraryWeb.Telemetry, + metrics_history: {MusicLibraryWeb.Telemetry.Storage, :metrics_history, []}, ecto_repos: [MusicLibrary.Repo] end end diff --git a/lib/music_library_web/telemetry.ex b/lib/music_library_web/telemetry.ex index a579a76d..9e52ab5e 100644 --- a/lib/music_library_web/telemetry.ex +++ b/lib/music_library_web/telemetry.ex @@ -9,6 +9,7 @@ defmodule MusicLibraryWeb.Telemetry do @impl true def init(_arg) do children = [ + {MusicLibraryWeb.Telemetry.Storage, metrics()}, # Telemetry poller will execute the given period measurements # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} diff --git a/lib/music_library_web/telemetry/storage.ex b/lib/music_library_web/telemetry/storage.ex new file mode 100644 index 00000000..48f50256 --- /dev/null +++ b/lib/music_library_web/telemetry/storage.ex @@ -0,0 +1,66 @@ +defmodule MusicLibraryWeb.Telemetry.Storage do + use GenServer + + @history_buffer_size 50 + + def metrics_history(metric) do + GenServer.call(__MODULE__, {:data, metric}) + end + + def start_link(args) do + GenServer.start_link(__MODULE__, args, name: __MODULE__) + end + + @impl true + def init(metrics) do + Process.flag(:trap_exit, true) + + metric_histories_map = + metrics + |> Enum.map(fn metric -> + attach_handler(metric) + {metric, CircularBuffer.new(@history_buffer_size)} + end) + |> Map.new() + + {:ok, metric_histories_map} + end + + @impl true + def terminate(_, metrics) do + for metric <- metrics do + :telemetry.detach({__MODULE__, metric, self()}) + end + + :ok + end + + defp attach_handler(%{event_name: name_list} = metric) do + :telemetry.attach( + {__MODULE__, metric, self()}, + name_list, + &__MODULE__.handle_event/4, + metric + ) + end + + def handle_event(_event_name, data, metadata, metric) do + if data = Phoenix.LiveDashboard.extract_datapoint_for_metric(metric, data, metadata) do + GenServer.cast(__MODULE__, {:telemetry_metric, data, metric}) + end + end + + @impl true + def handle_cast({:telemetry_metric, data, metric}, state) do + {:noreply, update_in(state[metric], &CircularBuffer.insert(&1, data))} + end + + @impl true + def handle_call({:data, metric}, _from, state) do + if history = state[metric] do + {:reply, CircularBuffer.to_list(history), state} + else + {:reply, [], state} + end + end +end diff --git a/mix.exs b/mix.exs index ef6bd339..bca8efb7 100644 --- a/mix.exs +++ b/mix.exs @@ -59,6 +59,7 @@ defmodule MusicLibrary.MixProject do depth: 1}, {:telemetry_metrics, "~> 1.0"}, {:telemetry_poller, "~> 1.0"}, + {:circular_buffer, "~> 0.4.1"}, {:gettext, "~> 0.20"}, {:jason, "~> 1.2"}, {:dns_cluster, "~> 0.1.1"}, diff --git a/mix.lock b/mix.lock index fa156616..be088f19 100644 --- a/mix.lock +++ b/mix.lock @@ -2,6 +2,7 @@ "bandit": {:hex, :bandit, "1.6.0", "9cb6c67c27cecab2d0c93968cb957fa8decccb7275193c8bf33f97397b3ac25d", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "fd2491e564a7c5e11ff8496ebf530c342c742452c59de17ac0fb1f814a0ab01a"}, "castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"}, + "circular_buffer": {:hex, :circular_buffer, "0.4.1", "477f370fd8cfe1787b0a1bade6208bbd274b34f1610e41f1180ba756a7679839", [:mix], [], "hexpm", "633ef2e059dde0d7b89bbab13b1da9d04c6685e80e68fbdf41282d4fae746b72"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.2.0", "df3d06bb9517e302b1bd265c1e7f16cda51547ad9d99892049340841f3e15836", [:mix], [], "hexpm", "af8daf87384b51b7e611fb1a1f2c4d4876b65ef968fa8bd3adf44cff401c7f21"}, "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},