-
-
Notifications
You must be signed in to change notification settings - Fork 114
feat: add metrics for auth_query and SecretChecker erpc calls #969
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c6457a9
fa67901
0b68ea8
f76e9dc
8818e8f
ea0dcec
ecbf21b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| defmodule Supavisor.ConnectionListener do | ||
Check warningCode scanning / Credo Modules should have a @moduledoc tag. Warning
Modules should have a @moduledoc tag.
|
||
|
|
||
| use GenServer | ||
|
|
||
| require Logger | ||
|
|
||
| @name __MODULE__ | ||
|
|
||
| def start_link(opts \\ []) do | ||
| GenServer.start_link(__MODULE__, nil, Keyword.put_new(opts, :name, @name)) | ||
| end | ||
|
|
||
| @impl true | ||
| def init(nil) do | ||
| Process.flag(:trap_exit, true) | ||
| {:ok, %{monitoring: %{}}} | ||
| end | ||
|
|
||
| @impl true | ||
| def handle_info({:connected, pid, connect_start_time}, state) do | ||
| {start_time, state} = | ||
| case Map.get(state.monitoring, pid) do | ||
| nil -> | ||
| ref = Process.monitor(pid) | ||
| {connect_start_time, put_in(state, [:monitoring, pid], {ref, nil})} | ||
|
|
||
| {ref, disconnect_time} -> | ||
| {disconnect_time || connect_start_time, put_in(state, [:monitoring, pid], {ref, nil})} | ||
| end | ||
|
|
||
| connect_duration = System.monotonic_time() - start_time | ||
|
|
||
| :telemetry.execute( | ||
| [:supavisor, :auth_query, :connection, :stop], | ||
| %{duration: connect_duration} | ||
| ) | ||
|
|
||
| {:noreply, state} | ||
| end | ||
|
|
||
| def handle_info({:disconnected, pid, _}, state) do | ||
| case Map.get(state.monitoring, pid) do | ||
| nil -> | ||
| {:noreply, state} | ||
|
|
||
| {_ref, prev_disconnect_ts} when not is_nil(prev_disconnect_ts) -> | ||
| Logger.warning("Duplicate disconnected event for pid #{inspect(pid)})") | ||
| {:noreply, state} | ||
|
|
||
| {ref, nil} -> | ||
| :telemetry.execute( | ||
| [:supavisor, :auth_query, :disconnection], | ||
| %{count: 1} | ||
| ) | ||
|
|
||
| {:noreply, put_in(state, [:monitoring, pid], {ref, System.monotonic_time()})} | ||
| end | ||
| end | ||
|
|
||
| def handle_info({:disconnected, pid}, state) do | ||
| handle_info({:disconnected, pid, nil}, state) | ||
| end | ||
|
|
||
| # we only get monitor message if we have seen the connected event first | ||
| def handle_info({:DOWN, _ref, :process, pid, reason}, state) do | ||
| case Map.pop(state.monitoring, pid) do | ||
| {nil, _} -> | ||
| {:noreply, state} | ||
|
|
||
| {{_ref, prev_disconnect_ts}, monitoring} when not is_nil(prev_disconnect_ts) -> | ||
| Logger.warning( | ||
| "Duplicate disconnected event for pid #{inspect(pid)} (#{inspect(reason)})" | ||
| ) | ||
|
|
||
| {:noreply, %{state | monitoring: monitoring}} | ||
|
|
||
| {{ref, nil}, monitoring} -> | ||
| Process.demonitor(ref, [:flush]) | ||
|
|
||
| :telemetry.execute( | ||
| [:supavisor, :auth_query, :disconnection], | ||
| %{count: 1}, | ||
| (reason == :normal && %{}) || %{kind: :exit, reason: reason} | ||
| ) | ||
|
|
||
| {:noreply, %{state | monitoring: monitoring}} | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ defmodule Supavisor.Monitoring.Telem do | |
| require Supavisor | ||
|
|
||
| @disabled Application.compile_env(:supavisor, :metrics_disabled, false) | ||
| @slow_auth_query_ms 1_000 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if this is the right number, we can see what the times are in the first cluster we deploy to. |
||
|
|
||
| if @disabled do | ||
| defp telemetry_execute(_name, _measurements, _meta), do: :ok | ||
|
|
@@ -117,6 +118,50 @@ defmodule Supavisor.Monitoring.Telem do | |
| ) | ||
| end | ||
|
|
||
| @spec auth_query_connection_stop(integer(), term()) :: :ok | ||
| def auth_query_connection_stop(duration, error_or_nil) do | ||
| telemetry_execute( | ||
| [:supavisor, :auth_query, :connection, :stop], | ||
| %{duration: duration}, | ||
| %{error: error_or_nil} | ||
| ) | ||
| end | ||
|
|
||
| @spec auth_query_query_stop(integer(), :ok | :error) :: :ok | ||
| def auth_query_query_stop(duration, status) do | ||
| duration_ms = System.convert_time_unit(duration, :native, :millisecond) | ||
|
|
||
| if duration_ms > @slow_auth_query_ms do | ||
| Logger.warning("auth_query took over #{@slow_auth_query_ms}ms (#{duration_ms}ms)") | ||
| end | ||
|
Comment on lines
+134
to
+136
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Really like this! |
||
|
|
||
| telemetry_execute( | ||
| [:supavisor, :auth_query, :query, :stop], | ||
| %{duration: duration}, | ||
| %{status: status} | ||
| ) | ||
| end | ||
|
|
||
| @spec secret_checker_get_secrets_stop(integer(), :ok | :error, :local | :remote) :: :ok | ||
| def secret_checker_get_secrets_stop(duration, status, locality) | ||
| when locality in [:local, :remote] do | ||
| telemetry_execute( | ||
| [:supavisor, :secret_checker, :get_secrets, :stop, locality], | ||
| %{duration: duration}, | ||
| %{status: status} | ||
| ) | ||
| end | ||
|
|
||
| @spec secret_checker_update_credentials_stop(integer(), :ok | :error, :local | :remote) :: :ok | ||
| def secret_checker_update_credentials_stop(duration, status, locality) | ||
| when locality in [:local, :remote] do | ||
| telemetry_execute( | ||
| [:supavisor, :secret_checker, :update_credentials, :stop, locality], | ||
| %{duration: duration}, | ||
| %{status: status} | ||
| ) | ||
| end | ||
|
|
||
| @spec id_to_tags(Supavisor.id()) :: map() | ||
| defp id_to_tags( | ||
| Supavisor.id( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,7 +27,8 @@ defmodule Supavisor.PromEx.Plugins.Tenant do | |
| [ | ||
| system_metrics(), | ||
| client_metrics(), | ||
| db_metrics() | ||
| db_metrics(), | ||
| client_auth_metrics() | ||
| ] | ||
| end | ||
|
|
||
|
|
@@ -191,6 +192,68 @@ defmodule Supavisor.PromEx.Plugins.Tenant do | |
| ) | ||
| end | ||
|
|
||
| defp client_auth_metrics do | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. technically, these are not per-tenant metrics but thought it's not worth creating a new module just for them |
||
| Event.build( | ||
| :supavisor_client_auth_event_metrics, | ||
| [ | ||
| distribution( | ||
| [:supavisor, :auth_query, :connection, :duration], | ||
| event_name: [:supavisor, :auth_query, :connection, :stop], | ||
| measurement: :duration, | ||
| description: "Duration of auth_query Postgrex connection setup.", | ||
| tags: [:status], | ||
| unit: {:native, :millisecond}, | ||
| reporter_options: [peep_bucket_calculator: Buckets] | ||
| ), | ||
| distribution( | ||
| [:supavisor, :auth_query, :query, :duration], | ||
| event_name: [:supavisor, :auth_query, :query, :stop], | ||
| measurement: :duration, | ||
| description: "Duration of auth_query execution including Postgrex queue wait.", | ||
| tags: [:status], | ||
| unit: {:native, :millisecond}, | ||
| reporter_options: [peep_bucket_calculator: Buckets] | ||
| ), | ||
| distribution( | ||
| [:supavisor, :secret_checker, :get_secrets, :duration, :local], | ||
| event_name: [:supavisor, :secret_checker, :get_secrets, :stop, :local], | ||
| measurement: :duration, | ||
| description: "Duration of get_secrets erpc call (same-node).", | ||
| tags: [:status], | ||
| unit: {:native, :millisecond}, | ||
| reporter_options: [peep_bucket_calculator: Buckets] | ||
| ), | ||
| distribution( | ||
| [:supavisor, :secret_checker, :get_secrets, :duration, :remote], | ||
| event_name: [:supavisor, :secret_checker, :get_secrets, :stop, :remote], | ||
| measurement: :duration, | ||
| description: "Duration of get_secrets erpc call (cross-node).", | ||
| tags: [:status], | ||
| unit: {:native, :millisecond}, | ||
| reporter_options: [peep_bucket_calculator: Buckets] | ||
| ), | ||
| distribution( | ||
| [:supavisor, :secret_checker, :update_credentials, :duration, :local], | ||
| event_name: [:supavisor, :secret_checker, :update_credentials, :stop, :local], | ||
| measurement: :duration, | ||
| description: "Duration of update_credentials erpc call (same-node).", | ||
| tags: [:status], | ||
| unit: {:native, :millisecond}, | ||
| reporter_options: [peep_bucket_calculator: Buckets] | ||
| ), | ||
| distribution( | ||
| [:supavisor, :secret_checker, :update_credentials, :duration, :remote], | ||
| event_name: [:supavisor, :secret_checker, :update_credentials, :stop, :remote], | ||
| measurement: :duration, | ||
| description: "Duration of update_credentials erpc call (cross-node).", | ||
| tags: [:status], | ||
| unit: {:native, :millisecond}, | ||
| reporter_options: [peep_bucket_calculator: Buckets] | ||
| ) | ||
| ] | ||
| ) | ||
| end | ||
|
|
||
| defp db_metrics do | ||
| Event.build( | ||
| :supavisor_tenant_db_event_metrics, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ defmodule Supavisor.SecretChecker do | |
| alias Supavisor.ClientAuthentication | ||
| alias Supavisor.ClientAuthentication.ValidationSecrets | ||
| alias Supavisor.Errors.AuthQueryError | ||
| alias Supavisor.Monitoring.Telem | ||
|
|
||
| @interval :timer.seconds(15) | ||
|
|
||
|
|
@@ -40,7 +41,10 @@ defmodule Supavisor.SecretChecker do | |
| {:error, %AuthQueryError{reason: :timeout}} | ||
|
|
||
| :exit, reason -> | ||
| Logger.error("SecretChecker: get_secrets call exited: #{inspect(reason)}") | ||
| Logger.error("SecretChecker: get_secrets call exited: #{inspect(reason)}", | ||
| project: Supavisor.id(id, :tenant) | ||
| ) | ||
|
Comment on lines
+44
to
+46
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we were missing the project md if that was an RPC call |
||
|
|
||
| {:error, :not_started} | ||
| end | ||
| end | ||
|
|
@@ -161,9 +165,12 @@ defmodule Supavisor.SecretChecker do | |
| defp jitter, do: :rand.uniform(div(@interval, 10)) | ||
|
|
||
| defp erpc_call_node(id, mod, fun, args) do | ||
| start = System.monotonic_time() | ||
|
|
||
| case Supavisor.get_global_sup(id) do | ||
| nil -> | ||
| {:error, :not_started} | ||
| |> tap(&telemetry_stop(fun, start, &1, :local)) | ||
|
Comment on lines
172
to
+173
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think |
||
|
|
||
| pid -> | ||
| try do | ||
|
|
@@ -173,6 +180,27 @@ defmodule Supavisor.SecretChecker do | |
| Logger.error("SecretChecker: erpc call exited: #{inspect(reason)}") | ||
| {:error, :not_started} | ||
| end | ||
| |> tap(&telemetry_stop(fun, start, &1, (node(pid) == node() && :local) || :remote)) | ||
| end | ||
| end | ||
|
|
||
| defp telemetry_stop(:do_get_secrets, start, result, locality) do | ||
| Telem.secret_checker_get_secrets_stop( | ||
| System.monotonic_time() - start, | ||
| telemetry_status(result), | ||
| locality | ||
| ) | ||
| end | ||
|
|
||
| defp telemetry_stop(:do_update_credentials, start, result, locality) do | ||
| Telem.secret_checker_update_credentials_stop( | ||
| System.monotonic_time() - start, | ||
| telemetry_status(result), | ||
| locality | ||
| ) | ||
| end | ||
|
|
||
| defp telemetry_status({:error, _}), do: :error | ||
| defp telemetry_status({:ok, _}), do: :ok | ||
| defp telemetry_status(:ok), do: :ok | ||
| end | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Connection in postgrex is async unless we pass the sync flag, this function returns a pid even without the connection being complete, so the measurement won't get slow connections