From 757c2b696aba36cbca1de405894f2df212195f27 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 10 Apr 2021 15:37:13 -0400 Subject: [PATCH] Add basic metrics --- lib/wiki/application.ex | 4 ++- lib/wiki/metric_generator.ex | 34 +++++++++++++++++++ lib/wiki/metric_storage.ex | 66 ++++++++++++++++++++++++++++++++++++ lib/wiki_web/router.ex | 4 ++- lib/wiki_web/telemetry.ex | 5 +++ mix.exs | 3 +- mix.lock | 1 + 7 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 lib/wiki/metric_generator.ex create mode 100644 lib/wiki/metric_storage.ex diff --git a/lib/wiki/application.ex b/lib/wiki/application.ex index 8638c44..8251cbc 100644 --- a/lib/wiki/application.ex +++ b/lib/wiki/application.ex @@ -14,9 +14,11 @@ defmodule Wiki.Application do # Start the PubSub system {Phoenix.PubSub, name: Wiki.PubSub}, # Start the Endpoint (http/https) - WikiWeb.Endpoint + WikiWeb.Endpoint, # Start a worker by calling: Wiki.Worker.start_link(arg) # {Wiki.Worker, arg} + {Wiki.MetricStorage, WikiWeb.Telemetry.metrics()}, + Wiki.MetricGenerator ] # See https://hexdocs.pm/elixir/Supervisor.html diff --git a/lib/wiki/metric_generator.ex b/lib/wiki/metric_generator.ex new file mode 100644 index 0000000..9a4d852 --- /dev/null +++ b/lib/wiki/metric_generator.ex @@ -0,0 +1,34 @@ +defmodule Wiki.MetricGenerator do + use GenServer + alias Wiki.Repo + alias Wiki.Content.{Page, PageLink, Upload} + + def start_link(state) do + GenServer.start_link(__MODULE__, state, name: __MODULE__) + end + + @impl true + def init(state) do + update_metrics() + + # 5 minutes + Process.send_after(self(), :update, 5 * 60 * 1000) + + {:ok, state} + end + + @impl true + def handle_info(:update, state) do + update_metrics() + {:noreply, state} + end + + def update_metrics do + pages = Repo.aggregate(Page, :count) + uploads = Repo.aggregate(Upload, :count) + links = Repo.aggregate(PageLink, :count) + :telemetry.execute([:wiki, :pages], %{count: pages}) + :telemetry.execute([:wiki, :uploads], %{count: uploads}) + :telemetry.execute([:wiki, :links], %{count: links}) + end +end diff --git a/lib/wiki/metric_storage.ex b/lib/wiki/metric_storage.ex new file mode 100644 index 0000000..98ab73e --- /dev/null +++ b/lib/wiki/metric_storage.ex @@ -0,0 +1,66 @@ +defmodule Wiki.MetricStorage 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/lib/wiki_web/router.ex b/lib/wiki_web/router.ex index e7af0c3..7209e23 100644 --- a/lib/wiki_web/router.ex +++ b/lib/wiki_web/router.ex @@ -56,7 +56,9 @@ defmodule WikiWeb.Router do scope "/", WikiWeb do pipe_through [:browser, :require_authenticated_user, :require_admin_user] - live_dashboard "/dashboard", metrics: WikiWeb.Telemetry + live_dashboard "/dashboard", + metrics: WikiWeb.Telemetry, + metrics_history: {Wiki.MetricStorage, :metrics_history, []} end scope "/", WikiWeb do diff --git a/lib/wiki_web/telemetry.ex b/lib/wiki_web/telemetry.ex index bf1c350..673d11e 100644 --- a/lib/wiki_web/telemetry.ex +++ b/lib/wiki_web/telemetry.ex @@ -21,6 +21,11 @@ defmodule WikiWeb.Telemetry do def metrics do [ + # Wiki Metrics + last_value("wiki.pages.count"), + last_value("wiki.uploads.count"), + last_value("wiki.links.count"), + # Phoenix Metrics summary("phoenix.endpoint.stop.duration", unit: {:native, :millisecond} diff --git a/mix.exs b/mix.exs index cfe7c24..cfd75a6 100644 --- a/mix.exs +++ b/mix.exs @@ -47,7 +47,8 @@ defmodule Wiki.MixProject do {:jason, "~> 1.0"}, {:plug_cowboy, "~> 2.0"}, {:phx_gen_auth, "~> 0.4.0", only: :dev, runtime: false}, - {:earmark, "~> 1.4.10"} + {:earmark, "~> 1.4.10"}, + {:circular_buffer, "~> 0.3.0"} ] end diff --git a/mix.lock b/mix.lock index 8c8fc92..a23b2de 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,7 @@ %{ "argon2_elixir": {:hex, :argon2_elixir, "2.3.0", "e251bdafd69308e8c1263e111600e6d68bd44f23d2cccbe43fcb1a417a76bc8e", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "28ccb63bff213aecec1f7f3dde9648418b031f822499973281d8f494b9d5a3b3"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.2.0", "3df902b81ce7fa8867a2ae30d20a1da6877a2c056bfb116fd0bc8a5f0190cea4", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "762be3fcb779f08207531bc6612cca480a338e4b4357abb49f5ce00240a77d1e"}, + "circular_buffer": {:hex, :circular_buffer, "0.3.0", "3eaa7e349ab07cff53dd1c25eb179ac14913fe1e407762260402a291dac4af96", [:mix], [], "hexpm", "f582b2ce394f8965cadf43c5b140f399b83dc8404aef701cb41c928c4bac1943"}, "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},