diff --git a/lib/prom_ex/plugins/absinthe.ex b/lib/prom_ex/plugins/absinthe.ex index 853c5102..4833c784 100644 --- a/lib/prom_ex/plugins/absinthe.ex +++ b/lib/prom_ex/plugins/absinthe.ex @@ -20,6 +20,9 @@ if Code.ensure_loaded?(Absinthe) do in your `dashboard_assigns` to the snakecase version of your prefix, the default `absinthe_metric_prefix` is `{otp_app}_prom_ex_absinthe`. + - `duration_unit`: This is an OPTIONAL option and is a `Telemetry.Metrics.time_unit()`. It can be one of: + `:second | :millisecond | :microsecond | :nanosecond`. It is `:millisecond` by default. + This plugin exposes the following metric groups: - `:absinthe_execute_event_metrics` - `:absinthe_subscription_event_metrics` @@ -50,6 +53,8 @@ if Code.ensure_loaded?(Absinthe) do use PromEx.Plugin + alias PromEx.Utils + # @operation_execute_start_event [:absinthe, :execute, :operation, :start] @operation_execute_stop_event [:absinthe, :execute, :operation, :stop] # @subscription_publish_start_event [:absinthe, :subscription, :publish, :start] @@ -78,6 +83,9 @@ if Code.ensure_loaded?(Absinthe) do |> Keyword.get(:ignored_entrypoints, []) |> MapSet.new() + duration_unit = Keyword.get(opts, :duration_unit, :millisecond) + duration_unit_plural = Utils.make_plural_atom(duration_unit) + event_tags = [:schema, :operation_type, :entrypoint] Event.build( @@ -85,7 +93,7 @@ if Code.ensure_loaded?(Absinthe) do [ # Capture GraphQL request duration information distribution( - metric_prefix ++ [:subscription, :duration, :milliseconds], + metric_prefix ++ [:subscription, :duration, duration_unit_plural], event_name: @subscription_publish_stop_event, measurement: :duration, description: "The time it takes for the Absinthe to publish subscription data.", @@ -94,7 +102,7 @@ if Code.ensure_loaded?(Absinthe) do ], tag_values: &subscription_stop_tag_values/1, tags: event_tags, - unit: {:native, :millisecond}, + unit: {:native, duration_unit}, drop: entrypoint_in_ignore_set?(ignored_entrypoints) ) ] @@ -108,6 +116,9 @@ if Code.ensure_loaded?(Absinthe) do |> Keyword.get(:ignored_entrypoints, []) |> MapSet.new() + duration_unit = Keyword.get(opts, :duration_unit, :millisecond) + duration_unit_plural = Utils.make_plural_atom(duration_unit) + event_tags = [:schema, :operation_type, :entrypoint] Event.build( @@ -115,7 +126,7 @@ if Code.ensure_loaded?(Absinthe) do [ # Capture GraphQL request duration information distribution( - metric_prefix ++ [:execute, :duration, :milliseconds], + metric_prefix ++ [:execute, :duration, duration_unit_plural], event_name: @operation_execute_stop_event, measurement: :duration, description: "The time it takes for the Absinthe to complete the operation.", @@ -124,7 +135,7 @@ if Code.ensure_loaded?(Absinthe) do ], tag_values: &operation_execute_stop_tag_values/1, tags: event_tags, - unit: {:native, :millisecond}, + unit: {:native, duration_unit}, drop: entrypoint_in_ignore_set?(ignored_entrypoints) ), diff --git a/lib/prom_ex/plugins/application.ex b/lib/prom_ex/plugins/application.ex index cb227479..fcd6dd18 100644 --- a/lib/prom_ex/plugins/application.ex +++ b/lib/prom_ex/plugins/application.ex @@ -24,6 +24,9 @@ defmodule PromEx.Plugins.Application do in your `dashboard_assigns` to the snakecase version of your prefix, the default `application_metric_prefix` is `{otp_app}_prom_ex_application`. + - `duration_unit`: This is an OPTIONAL option and is a `Telemetry.Metrics.time_unit()`. It can be one of: + `:second | :millisecond | :microsecond | :nanosecond`. It is `:millisecond` by default. + This plugin exposes the following metric groups: - `:application_versions_manual_metrics` @@ -119,6 +122,8 @@ defmodule PromEx.Plugins.Application do otp_app = Keyword.fetch!(opts, :otp_app) poll_rate = Keyword.get(opts, :poll_rate, 5_000) metric_prefix = Keyword.get(opts, :metric_prefix, PromEx.metric_prefix(otp_app, :application)) + duration_unit = Keyword.get(opts, :duration_unit, :millisecond) + duration_unit_plural = PromEx.Utils.make_plural_atom(duration_unit) Polling.build( :application_time_polling_metrics, @@ -126,11 +131,12 @@ defmodule PromEx.Plugins.Application do {__MODULE__, :execute_time_metrics, []}, [ last_value( - metric_prefix ++ [:uptime, :milliseconds, :count], + metric_prefix ++ [:uptime, duration_unit_plural, :count], event_name: [:prom_ex, :plugin, :application, :uptime, :count], - description: "The total number of wall clock milliseconds that have passed since the application started.", + description: + "The total number of wall clock #{duration_unit_plural} that have passed since the application started.", measurement: :count, - unit: :millisecond + unit: duration_unit ) ] ) diff --git a/lib/prom_ex/plugins/beam.ex b/lib/prom_ex/plugins/beam.ex index 0153d3e6..3acb76d8 100644 --- a/lib/prom_ex/plugins/beam.ex +++ b/lib/prom_ex/plugins/beam.ex @@ -14,6 +14,9 @@ defmodule PromEx.Plugins.Beam do in your `dashboard_assigns` to the snakecase version of your prefix, the default `beam_metric_prefix` is `{otp_app}_prom_ex_beam`. + - `duration_unit`: This is an OPTIONAL option and is a `Telemetry.Metrics.time_unit()`. It can be one of: + `:second | :millisecond | :microsecond | :nanosecond`. It is `:millisecond` by default. + This plugin exposes the following metric groups: - `:beam_memory_polling_metrics` - `:beam_internal_polling_metrics` @@ -57,6 +60,7 @@ defmodule PromEx.Plugins.Beam do poll_rate = Keyword.get(opts, :poll_rate, 5_000) otp_app = Keyword.fetch!(opts, :otp_app) metric_prefix = Keyword.get(opts, :metric_prefix, PromEx.metric_prefix(otp_app, :beam)) + duration_unit = Keyword.get(opts, :duration_unit, :millisecond) # TODO: Investigate Microstate accounting metrics # http://erlang.org/doc/man/erlang.html#statistics_microstate_accounting @@ -69,7 +73,7 @@ defmodule PromEx.Plugins.Beam do memory_metrics(metric_prefix, poll_rate), mnesia_metrics(metric_prefix, poll_rate), distribution_metrics(metric_prefix, poll_rate), - beam_internal_metrics(metric_prefix, poll_rate) + beam_internal_metrics(metric_prefix, poll_rate, duration_unit) ] end @@ -104,7 +108,9 @@ defmodule PromEx.Plugins.Beam do ) end - defp beam_internal_metrics(metric_prefix, poll_rate) do + defp beam_internal_metrics(metric_prefix, poll_rate, duration_unit) do + duration_unit_plural = String.to_atom("#{duration_unit}s") + Polling.build( :beam_internal_polling_metrics, poll_rate, @@ -158,11 +164,12 @@ defmodule PromEx.Plugins.Beam do unit: :byte ), last_value( - metric_prefix ++ [:stats, :uptime, :milliseconds, :count], + metric_prefix ++ [:stats, :uptime, duration_unit_plural, :count], event_name: [:prom_ex, :plugin, :beam, :uptime, :count], - description: "The total number of wall clock milliseconds that have passed since the system started.", + description: + "The total number of wall clock #{duration_unit_plural} that have passed since the system started.", measurement: :count, - unit: :millisecond + unit: duration_unit ), last_value( metric_prefix ++ [:stats, :port, :count], diff --git a/lib/prom_ex/plugins/broadway.ex b/lib/prom_ex/plugins/broadway.ex index 62e76849..b52ff55f 100644 --- a/lib/prom_ex/plugins/broadway.ex +++ b/lib/prom_ex/plugins/broadway.ex @@ -77,7 +77,6 @@ if Code.ensure_loaded?(Broadway) do alias Broadway.{BatchInfo, Options} alias PromEx.Utils - @millisecond_duration_buckets [10, 100, 500, 1_000, 10_000, 30_000, 60_000] @message_batch_size_buckets [1, 5, 10, 20, 50, 100] @init_topology_event [:broadway, :topology, :init] @@ -92,6 +91,7 @@ if Code.ensure_loaded?(Broadway) do def event_metrics(opts) do otp_app = Keyword.fetch!(opts, :otp_app) metric_prefix = Keyword.get(opts, :metric_prefix, PromEx.metric_prefix(otp_app, :broadway)) + duration_unit = Keyword.get(opts, :duration_unit, :millisecond) # Telemetry metrics will emit warnings if multiple handlers with the same names are defined. # As a result, this plugin supports gathering metrics on multiple processors and batches, but needs @@ -102,13 +102,15 @@ if Code.ensure_loaded?(Broadway) do # Event metrics definitions [ - topology_init_events(metric_prefix), - handle_message_events(metric_prefix), - handle_batch_events(metric_prefix) + topology_init_events(metric_prefix, duration_unit), + handle_message_events(metric_prefix, duration_unit), + handle_batch_events(metric_prefix, duration_unit) ] end - defp topology_init_events(metric_prefix) do + defp topology_init_events(metric_prefix, duration_unit) do + duration_unit_plural = Utils.make_plural_atom(duration_unit) + Event.build( :broadway_init_event_metrics, [ @@ -121,7 +123,7 @@ if Code.ensure_loaded?(Broadway) do tag_values: &extract_init_tag_values/1 ), last_value( - metric_prefix ++ [:init, :hibernate_after, :default, :milliseconds], + metric_prefix ++ [:init, :hibernate_after, :default, duration_unit_plural], event_name: @init_topology_event, description: "The Broadway supervisor's hibernate after default value.", measurement: extract_default_config_measurement(:hibernate_after), @@ -129,7 +131,7 @@ if Code.ensure_loaded?(Broadway) do tag_values: &extract_init_tag_values/1 ), last_value( - metric_prefix ++ [:init, :resubscribe_interval, :default, :milliseconds], + metric_prefix ++ [:init, :resubscribe_interval, :default, duration_unit_plural], event_name: @init_topology_event, description: "The Broadway supervisor's resubscribe interval default value.", measurement: extract_default_config_measurement(:resubscribe_interval), @@ -137,13 +139,13 @@ if Code.ensure_loaded?(Broadway) do tag_values: &extract_init_tag_values/1 ), last_value( - metric_prefix ++ [:init, :max, :duration, :default, :milliseconds], + metric_prefix ++ [:init, :max, :duration, :default, duration_unit_plural], event_name: @init_topology_event, - description: "The Broadway supervisor's max seconds default value (in milliseconds).", + description: "The Broadway supervisor's max seconds default value (in #{duration_unit_plural}).", measurement: extract_default_config_measurement(:max_seconds), tags: [:name], tag_values: &extract_init_tag_values/1, - unit: {:second, :millisecond} + unit: {:second, duration_unit} ), last_value( metric_prefix ++ [:init, :max_restarts, :default, :value], @@ -154,7 +156,7 @@ if Code.ensure_loaded?(Broadway) do tag_values: &extract_init_tag_values/1 ), last_value( - metric_prefix ++ [:init, :shutdown, :default, :milliseconds], + metric_prefix ++ [:init, :shutdown, :default, duration_unit_plural], event_name: @init_topology_event, description: "The Broadway supervisor's shutdown default value.", measurement: extract_default_config_measurement(:shutdown), @@ -162,7 +164,7 @@ if Code.ensure_loaded?(Broadway) do tag_values: &extract_init_tag_values/1 ), last_value( - metric_prefix ++ [:init, :processor, :hibernate_after, :milliseconds], + metric_prefix ++ [:init, :processor, :hibernate_after, duration_unit_plural], event_name: @init_topology_processors_proxy_event, description: "The Broadway processors hibernate after value.", measurement: fn _measurements, %{hibernate_after: hibernate_after} -> hibernate_after end, @@ -183,7 +185,7 @@ if Code.ensure_loaded?(Broadway) do tags: [:name, :processor] ), last_value( - metric_prefix ++ [:init, :batcher, :hibernate_after, :milliseconds], + metric_prefix ++ [:init, :batcher, :hibernate_after, duration_unit_plural], event_name: @init_topology_batchers_proxy_event, description: "The Broadway batchers hibernate after value.", measurement: fn _measurements, %{hibernate_after: hibernate_after} -> hibernate_after end, @@ -204,7 +206,7 @@ if Code.ensure_loaded?(Broadway) do tags: [:name, :batcher] ), last_value( - metric_prefix ++ [:init, :batcher, :batch_timeout, :milliseconds], + metric_prefix ++ [:init, :batcher, :batch_timeout, duration_unit_plural], event_name: @init_topology_batchers_proxy_event, description: "The Broadway batchers timeout value.", measurement: fn _measurements, %{batch_timeout: batch_timeout} -> batch_timeout end, @@ -267,53 +269,57 @@ if Code.ensure_loaded?(Broadway) do end end - defp handle_message_events(metric_prefix) do + defp handle_message_events(metric_prefix, duration_unit) do + duration_unit_plural = Utils.make_plural_atom(duration_unit) + Event.build( :broadway_message_event_metrics, [ distribution( - metric_prefix ++ [:process, :message, :duration, :milliseconds], + metric_prefix ++ [:process, :message, :duration, duration_unit_plural], event_name: @message_stop_event, measurement: :duration, description: "The time it takes Broadway to process a message.", reporter_options: [ - buckets: @millisecond_duration_buckets + buckets: buckets_for_unit(duration_unit) ], tags: [:processor_key, :name], tag_values: &extract_message_tag_values/1, - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), distribution( - metric_prefix ++ [:process, :message, :exception, :duration, :milliseconds], + metric_prefix ++ [:process, :message, :exception, :duration, duration_unit_plural], event_name: @message_exception_event, measurement: :duration, description: "The time it takes Broadway to process a message that results in an error.", reporter_options: [ - buckets: @millisecond_duration_buckets + buckets: buckets_for_unit(duration_unit) ], tags: [:processor_key, :name, :kind, :reason], tag_values: &extract_exception_tag_values/1, - unit: {:native, :millisecond} + unit: {:native, duration_unit} ) ] ) end - defp handle_batch_events(metric_prefix) do + defp handle_batch_events(metric_prefix, duration_unit) do + duration_unit_plural = Utils.make_plural_atom(duration_unit) + Event.build( :broadway_batch_event_metrics, [ distribution( - metric_prefix ++ [:process, :batch, :duration, :milliseconds], + metric_prefix ++ [:process, :batch, :duration, duration_unit_plural], event_name: @batch_stop_event, measurement: :duration, description: "The time it takes Broadway to process a batch of messages.", reporter_options: [ - buckets: @millisecond_duration_buckets + buckets: buckets_for_unit(duration_unit) ], tags: [:batcher, :name], tag_values: &extract_batcher_tag_values/1, - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), distribution( metric_prefix ++ [:process, :batch, :failure, :size], @@ -387,6 +393,15 @@ if Code.ensure_loaded?(Broadway) do name: Utils.normalize_module_name(name) } end + + defp buckets_for_unit(unit) do + case unit do + :nanosecond -> [1, 100, 1_000, 2_000, 10_000, 50_000, 1_000_000] + :microsecond -> [10_000, 100_000, 500_000, 1_000_000, 10_000_000, 30_000_000, 60_000_000] + :millisecond -> [10, 100, 500, 1_000, 10_000, 30_000, 60_000] + :second -> [1, 5, 10, 100, 500, 1_000, 10_000] + end + end end else defmodule PromEx.Plugins.Broadway do diff --git a/lib/prom_ex/plugins/ecto.ex b/lib/prom_ex/plugins/ecto.ex index 9c46598b..21785225 100644 --- a/lib/prom_ex/plugins/ecto.ex +++ b/lib/prom_ex/plugins/ecto.ex @@ -18,6 +18,9 @@ if Code.ensure_loaded?(Ecto) do If you do not provide this value, PromEx will attempt to resolve your Repo modules via the `:ecto_repos` configuration on your OTP app. + - `duration_unit`: This is an OPTIONAL option and is a `Telemetry.Metrics.time_unit()`. It can be one of: + `:second | :millisecond | :microsecond | :nanosecond`. It is `:millisecond` by default. + This plugin exposes the following metric groups: - `:ecto_init_event_metrics` - `:ecto_query_event_metrics` @@ -44,6 +47,7 @@ if Code.ensure_loaded?(Ecto) do def event_metrics(opts) do otp_app = Keyword.fetch!(opts, :otp_app) metric_prefix = Keyword.get(opts, :metric_prefix, PromEx.metric_prefix(otp_app, :ecto)) + duration_unit = Keyword.get(opts, :duration_unit, :millisecond) repo_event_prefixes = opts @@ -68,7 +72,7 @@ if Code.ensure_loaded?(Ecto) do # Event metrics definitions [ init_metrics(metric_prefix), - query_metrics(metric_prefix) + query_metrics(metric_prefix, duration_unit) ] end @@ -120,13 +124,15 @@ if Code.ensure_loaded?(Ecto) do ) end - defp query_metrics(metric_prefix) do + defp query_metrics(metric_prefix, duration_unit) do + duration_unit_plural = String.to_atom("#{duration_unit}s") + Event.build( :ecto_query_event_metrics, [ # Capture the db connection idle time distribution( - metric_prefix ++ [:repo, :query, :idle, :time, :milliseconds], + metric_prefix ++ [:repo, :query, :idle, :time, duration_unit_plural], event_name: @query_event, measurement: :idle_time, description: "The time the connection spent waiting before being checked out for the query.", @@ -135,12 +141,12 @@ if Code.ensure_loaded?(Ecto) do reporter_options: [ buckets: [10, 50, 250, 1_000, 5_000, 10_000] ], - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), # Capture the db connection queue time distribution( - metric_prefix ++ [:repo, :query, :queue, :time, :milliseconds], + metric_prefix ++ [:repo, :query, :queue, :time, duration_unit_plural], event_name: @query_event, measurement: :queue_time, description: "The time spent waiting to check out a database connection.", @@ -149,12 +155,12 @@ if Code.ensure_loaded?(Ecto) do reporter_options: [ buckets: [10, 50, 250, 1_000, 5_000, 10_000] ], - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), # Capture the db query decode time distribution( - metric_prefix ++ [:repo, :query, :decode, :time, :milliseconds], + metric_prefix ++ [:repo, :query, :decode, :time, duration_unit_plural], event_name: @query_event, measurement: :decode_time, description: "The time spent decoding the data received from the database.", @@ -163,12 +169,12 @@ if Code.ensure_loaded?(Ecto) do reporter_options: [ buckets: [5, 50, 100, 500, 2_500] ], - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), # Capture the query execution time distribution( - metric_prefix ++ [:repo, :query, :execution, :time, :milliseconds], + metric_prefix ++ [:repo, :query, :execution, :time, duration_unit_plural], event_name: @query_event, measurement: :query_time, description: "The time spent executing the query.", @@ -177,12 +183,12 @@ if Code.ensure_loaded?(Ecto) do reporter_options: [ buckets: [10, 50, 250, 2_500, 10_000, 30_000] ], - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), # Capture the total time (the sum of all other measurements) distribution( - metric_prefix ++ [:repo, :query, :total, :time, :milliseconds], + metric_prefix ++ [:repo, :query, :total, :time, duration_unit_plural], event_name: @query_event, measurement: :total_time, description: "The sum of the other time measurements.", @@ -191,7 +197,7 @@ if Code.ensure_loaded?(Ecto) do reporter_options: [ buckets: [10, 50, 250, 2_500, 10_000, 30_000] ], - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), # Capture the number of results returned diff --git a/lib/prom_ex/plugins/oban.ex b/lib/prom_ex/plugins/oban.ex index 74682359..98e4ea8c 100644 --- a/lib/prom_ex/plugins/oban.ex +++ b/lib/prom_ex/plugins/oban.ex @@ -17,6 +17,9 @@ if Code.ensure_loaded?(Oban) do - `poll_rate`: This option is OPTIONAL and is the rate at which poll metrics are refreshed (default is 5 seconds). + - `duration_unit`: This is an OPTIONAL option and is a `Telemetry.Metrics.time_unit()`. It can be one of: + `:second | :millisecond | :microsecond | :nanosecond`. It is `:millisecond` by default. + This plugin exposes the following metric groups: - `:oban_init_event_metrics` - `:oban_job_event_metrics` @@ -50,6 +53,8 @@ if Code.ensure_loaded?(Oban) do use PromEx.Plugin + alias PromEx.Utils + import Ecto.Query, only: [group_by: 3, select: 3] # Oban events @@ -68,6 +73,7 @@ if Code.ensure_loaded?(Oban) do def event_metrics(opts) do otp_app = Keyword.fetch!(opts, :otp_app) metric_prefix = Keyword.get(opts, :metric_prefix, PromEx.metric_prefix(otp_app, :oban)) + duration_unit = Keyword.get(opts, :duration_unit, :millisecond) oban_supervisors = get_oban_supervisors(opts) keep_function_filter = keep_oban_instance_metrics(oban_supervisors) @@ -76,9 +82,9 @@ if Code.ensure_loaded?(Oban) do set_up_init_proxy_event(metric_prefix) [ - oban_supervisor_init_event_metrics(metric_prefix, keep_function_filter), - oban_job_event_metrics(metric_prefix, keep_function_filter), - oban_producer_event_metrics(metric_prefix, keep_function_filter), + oban_supervisor_init_event_metrics(metric_prefix, keep_function_filter, duration_unit), + oban_job_event_metrics(metric_prefix, keep_function_filter, duration_unit), + oban_producer_event_metrics(metric_prefix, keep_function_filter, duration_unit), oban_circuit_breaker_event_metrics(metric_prefix, keep_function_filter) ] end @@ -177,15 +183,16 @@ if Code.ensure_loaded?(Oban) do } end - defp oban_job_event_metrics(metric_prefix, keep_function_filter) do + defp oban_job_event_metrics(metric_prefix, keep_function_filter, duration_unit) do job_attempt_buckets = [1, 5, 10] job_duration_buckets = [10, 100, 500, 1_000, 5_000, 20_000] + duration_unit_plural = Utils.make_plural_atom(duration_unit) Event.build( :oban_job_event_metrics, [ distribution( - metric_prefix ++ [:job, :processing, :duration, :milliseconds], + metric_prefix ++ [:job, :processing, :duration, duration_unit_plural], event_name: @job_complete_event, measurement: :duration, description: "The amount of time it takes to processes an Oban job.", @@ -194,11 +201,11 @@ if Code.ensure_loaded?(Oban) do ], tag_values: &job_complete_tag_values/1, tags: [:name, :queue, :state, :worker], - unit: {:native, :millisecond}, + unit: {:native, duration_unit}, keep: keep_function_filter ), distribution( - metric_prefix ++ [:job, :queue, :time, :milliseconds], + metric_prefix ++ [:job, :queue, :time, duration_unit_plural], event_name: @job_complete_event, measurement: :queue_time, description: "The amount of time that the Oban job was waiting in queue for processing.", @@ -207,7 +214,7 @@ if Code.ensure_loaded?(Oban) do ], tag_values: &job_complete_tag_values/1, tags: [:name, :queue, :state, :worker], - unit: {:native, :millisecond}, + unit: {:native, duration_unit}, keep: keep_function_filter ), distribution( @@ -225,7 +232,7 @@ if Code.ensure_loaded?(Oban) do keep: keep_function_filter ), distribution( - metric_prefix ++ [:job, :exception, :duration, :milliseconds], + metric_prefix ++ [:job, :exception, :duration, duration_unit_plural], event_name: @job_exception_event, measurement: :duration, description: "The amount of time it took to process a job the encountered an exception.", @@ -234,11 +241,11 @@ if Code.ensure_loaded?(Oban) do ], tag_values: &job_exception_tag_values/1, tags: [:name, :queue, :state, :worker, :kind, :error], - unit: {:native, :millisecond}, + unit: {:native, duration_unit}, keep: keep_function_filter ), distribution( - metric_prefix ++ [:job, :exception, :queue, :time, :milliseconds], + metric_prefix ++ [:job, :exception, :queue, :time, duration_unit_plural], event_name: @job_exception_event, measurement: :queue_time, description: @@ -248,7 +255,7 @@ if Code.ensure_loaded?(Oban) do ], tag_values: &job_exception_tag_values/1, tags: [:name, :queue, :state, :worker, :kind, :error], - unit: {:native, :millisecond}, + unit: {:native, duration_unit}, keep: keep_function_filter ), distribution( @@ -269,19 +276,21 @@ if Code.ensure_loaded?(Oban) do ) end - defp oban_producer_event_metrics(metric_prefix, keep_function_filter) do + defp oban_producer_event_metrics(metric_prefix, keep_function_filter, duration_unit) do + duration_unit_plural = Utils.make_plural_atom(duration_unit) + Event.build( :oban_producer_event_metrics, [ distribution( - metric_prefix ++ [:producer, :duration, :milliseconds], + metric_prefix ++ [:producer, :duration, duration_unit_plural], event_name: @producer_complete_event, measurement: :duration, description: "How long it took to dispatch the job.", reporter_options: [ buckets: [10, 100, 500, 1_000, 5_000, 10_000] ], - unit: {:native, :millisecond}, + unit: {:native, duration_unit}, tag_values: &producer_tag_values/1, tags: [:queue, :name], keep: keep_function_filter @@ -301,14 +310,14 @@ if Code.ensure_loaded?(Oban) do keep: keep_function_filter ), distribution( - metric_prefix ++ [:producer, :exception, :duration, :milliseconds], + metric_prefix ++ [:producer, :exception, :duration, duration_unit_plural], event_name: @producer_exception_event, measurement: :duration, description: "How long it took for the producer to raise an exception.", reporter_options: [ buckets: [10, 100, 500, 1_000, 5_000, 10_000] ], - unit: {:native, :millisecond}, + unit: {:native, duration_unit}, tag_values: &producer_tag_values/1, tags: [:queue, :name], keep: keep_function_filter @@ -368,7 +377,9 @@ if Code.ensure_loaded?(Oban) do } end - defp oban_supervisor_init_event_metrics(metric_prefix, keep_function_filter) do + defp oban_supervisor_init_event_metrics(metric_prefix, keep_function_filter, duration_unit) do + duration_unit_plural = Utils.make_plural_atom(duration_unit) + Event.build( :oban_init_event_metrics, [ @@ -382,7 +393,7 @@ if Code.ensure_loaded?(Oban) do keep: keep_function_filter ), last_value( - metric_prefix ++ [:init, :shutdown, :grace, :period, :milliseconds], + metric_prefix ++ [:init, :shutdown, :grace, :period, duration_unit_plural], event_name: @init_event, description: "The Oban supervisor's shutdown grace period value.", measurement: fn _measurements, %{conf: config} -> @@ -393,7 +404,7 @@ if Code.ensure_loaded?(Oban) do keep: keep_function_filter ), last_value( - metric_prefix ++ [:init, :dispatch, :cooldown, :milliseconds], + metric_prefix ++ [:init, :dispatch, :cooldown, duration_unit_plural], event_name: @init_event, description: "The Oban supervisor's dispatch cooldown value.", measurement: fn _measurements, %{conf: config} -> diff --git a/lib/prom_ex/plugins/phoenix.ex b/lib/prom_ex/plugins/phoenix.ex index c0c0e618..567a3a9d 100644 --- a/lib/prom_ex/plugins/phoenix.ex +++ b/lib/prom_ex/plugins/phoenix.ex @@ -12,6 +12,9 @@ if Code.ensure_loaded?(Phoenix) do in your `dashboard_assigns` to the snakecase version of your prefix, the default `phoenix_metric_prefix` is `{otp_app}_prom_ex_phoenix`. + - `duration_unit`: This is an OPTIONAL option and is a `Telemetry.Metrics.time_unit()`. It can be one of: + `:second | :millisecond | :microsecond | :nanosecond`. It is `:millisecond` by default. + ### Single Endpoint/Router - `router`: This option is REQUIRED and is the full module name of your Phoenix Router (e.g MyAppWeb.Router). @@ -146,6 +149,7 @@ if Code.ensure_loaded?(Phoenix) do alias Phoenix.Socket alias Plug.Conn + alias PromEx.Utils @stop_event [:prom_ex, :plugin, :phoenix, :stop] @@ -154,14 +158,15 @@ if Code.ensure_loaded?(Phoenix) do otp_app = Keyword.fetch!(opts, :otp_app) metric_prefix = Keyword.get(opts, :metric_prefix, PromEx.metric_prefix(otp_app, :phoenix)) phoenix_event_prefixes = fetch_event_prefixes!(opts) + duration_unit = Keyword.get(opts, :duration_unit, :millisecond) set_up_telemetry_proxy(phoenix_event_prefixes) # Event metrics definitions [ http_events(metric_prefix, opts), - channel_events(metric_prefix), - socket_events(metric_prefix) + channel_events(metric_prefix, duration_unit), + socket_events(metric_prefix, duration_unit) ] end @@ -244,13 +249,15 @@ if Code.ensure_loaded?(Phoenix) do routers = fetch_routers!(opts) additional_routes = fetch_additional_routes!(opts) http_metrics_tags = [:status, :method, :path, :controller, :action] + duration_unit = Keyword.get(opts, :duration_unit, :millisecond) + duration_unit_plural = Utils.make_plural_atom(duration_unit) Event.build( :phoenix_http_event_metrics, [ # Capture request duration information distribution( - metric_prefix ++ [:http, :request, :duration, :milliseconds], + metric_prefix ++ [:http, :request, :duration, duration_unit_plural], event_name: @stop_event, measurement: :duration, description: "The time it takes for the application to respond to HTTP requests.", @@ -259,7 +266,7 @@ if Code.ensure_loaded?(Phoenix) do ], tag_values: get_conn_tags(routers, additional_routes), tags: http_metrics_tags, - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), # Capture response payload size information @@ -293,7 +300,9 @@ if Code.ensure_loaded?(Phoenix) do ) end - defp channel_events(metric_prefix) do + defp channel_events(metric_prefix, duration_unit) do + duration_unit_plural = Utils.make_plural_atom(duration_unit) + Event.build( :phoenix_channel_event_metrics, [ @@ -314,7 +323,7 @@ if Code.ensure_loaded?(Phoenix) do # Capture channel handle_in duration distribution( - metric_prefix ++ [:channel, :handled_in, :duration, :milliseconds], + metric_prefix ++ [:channel, :handled_in, :duration, duration_unit_plural], event_name: [:phoenix, :channel_handled_in], measurement: :duration, description: "The time it takes for the application to respond to channel messages.", @@ -327,19 +336,21 @@ if Code.ensure_loaded?(Phoenix) do } end, tags: [:endpoint], - unit: {:native, :millisecond} + unit: {:native, duration_unit} ) ] ) end - defp socket_events(metric_prefix) do + defp socket_events(metric_prefix, duration_unit) do + duration_unit_plural = Utils.make_plural_atom(duration_unit) + Event.build( :phoenix_socket_event_metrics, [ # Capture socket connection duration distribution( - metric_prefix ++ [:socket, :connected, :duration, :milliseconds], + metric_prefix ++ [:socket, :connected, :duration, duration_unit_plural], event_name: [:phoenix, :socket_connected], measurement: :duration, description: "The time it takes for the application to establish a socket connection.", @@ -354,7 +365,7 @@ if Code.ensure_loaded?(Phoenix) do } end, tags: [:result, :transport, :endpoint], - unit: {:native, :millisecond} + unit: {:native, duration_unit} ) ] ) diff --git a/lib/prom_ex/plugins/phoenix_live_view.ex b/lib/prom_ex/plugins/phoenix_live_view.ex index 0a06816d..6a38f25c 100644 --- a/lib/prom_ex/plugins/phoenix_live_view.ex +++ b/lib/prom_ex/plugins/phoenix_live_view.ex @@ -10,6 +10,9 @@ if Code.ensure_loaded?(Phoenix.LiveView) do `phoenix_live_view_metric_prefix` in your `dashboard_assigns` to the snakecase version of your prefix, the default `phoenix_live_view_metric_prefix` is `{otp_app}_prom_ex_phoenix_live_view`. + - `duration_unit`: This is an OPTIONAL option and is a `Telemetry.Metrics.time_unit()`. It can be one of: + `:second | :millisecond | :microsecond | :nanosecond`. It is `:millisecond` by default. + This plugin exposes the following metric groups: - `:phoenix_live_view_event_metrics` - `:phoenix_live_view_component_event_metrics` @@ -61,22 +64,24 @@ if Code.ensure_loaded?(Phoenix.LiveView) do def event_metrics(opts) do otp_app = Keyword.fetch!(opts, :otp_app) metric_prefix = Keyword.get(opts, :metric_prefix, PromEx.metric_prefix(otp_app, :phoenix_live_view)) + duration_unit = Keyword.get(opts, :duration_unit, :millisecond) # Event metrics definitions [ - live_view_event_metrics(metric_prefix), + live_view_event_metrics(metric_prefix, duration_unit), live_component_event_metrics(metric_prefix) ] end - defp live_view_event_metrics(metric_prefix) do + defp live_view_event_metrics(metric_prefix, duration_unit) do bucket_intervals = [10, 100, 500, 1_000, 2_500, 5_000, 10_000] + duration_unit_plural = String.to_atom("#{duration_unit}s") Event.build( :phoenix_live_view_event_metrics, [ distribution( - metric_prefix ++ [:mount, :duration, :milliseconds], + metric_prefix ++ [:mount, :duration, duration_unit_plural], event_name: @live_view_mount_stop, measurement: :duration, description: "The time it takes for the live view to complete the mount callback.", @@ -85,10 +90,10 @@ if Code.ensure_loaded?(Phoenix.LiveView) do ], tag_values: &get_mount_socket_tags/1, tags: [:action, :module], - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), distribution( - metric_prefix ++ [:mount, :exception, :duration, :milliseconds], + metric_prefix ++ [:mount, :exception, :duration, duration_unit_plural], event_name: @live_view_mount_exception, measurement: :duration, description: @@ -98,10 +103,10 @@ if Code.ensure_loaded?(Phoenix.LiveView) do ], tag_values: &get_mount_exception_tags/1, tags: [:action, :module, :kind, :reason], - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), distribution( - metric_prefix ++ [:handle_event, :duration, :milliseconds], + metric_prefix ++ [:handle_event, :duration, duration_unit_plural], event_name: @live_view_handle_event_stop, measurement: :duration, description: "The time it takes for the live view to complete the handle_event callback.", @@ -110,10 +115,10 @@ if Code.ensure_loaded?(Phoenix.LiveView) do ], tag_values: &get_handle_event_socket_tags/1, tags: [:event, :action, :module], - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), distribution( - metric_prefix ++ [:handle_event, :exception, :duration, :milliseconds], + metric_prefix ++ [:handle_event, :exception, :duration, duration_unit_plural], event_name: @live_view_handle_event_exception, measurement: :duration, description: @@ -123,7 +128,7 @@ if Code.ensure_loaded?(Phoenix.LiveView) do ], tag_values: &get_handle_event_exception_socket_tags/1, tags: [:event, :action, :module, :kind, :reason], - unit: {:native, :millisecond} + unit: {:native, duration_unit} ) ] ) diff --git a/lib/prom_ex/plugins/plug_cowboy.ex b/lib/prom_ex/plugins/plug_cowboy.ex index 794700bb..e9590b29 100644 --- a/lib/prom_ex/plugins/plug_cowboy.ex +++ b/lib/prom_ex/plugins/plug_cowboy.ex @@ -16,7 +16,8 @@ if Code.ensure_loaded?(Plug.Cowboy) do in your `dashboard_assigns` to the snakecase version of your prefix, the default `plug_cowboy_metric_prefix` is `{otp_app}_prom_ex_plug_cowboy`. - + - `duration_unit`: This is an OPTIONAL option and is a `Telemetry.Metrics.time_unit()`. It can be one of: + `:second | :millisecond | :microsecond | :nanosecond`. It is `:millisecond` by default. To use plugin in your application, add the following to your PromEx module: @@ -102,12 +103,15 @@ if Code.ensure_loaded?(Plug.Cowboy) do |> Keyword.fetch!(:routers) |> MapSet.new() + duration_unit = Keyword.get(opts, :duration_unit, :millisecond) + duration_unit_plural = String.to_atom("#{duration_unit}s") + Event.build( :plug_cowboy_http_event_metrics, [ # Capture request duration information distribution( - metric_prefix ++ [:http, :request, :duration, :milliseconds], + metric_prefix ++ [:http, :request, :duration, duration_unit_plural], event_name: cowboy_stop_event, measurement: :duration, description: "The time it takes for the application to process HTTP requests.", @@ -117,10 +121,10 @@ if Code.ensure_loaded?(Plug.Cowboy) do drop: drop_ignored(ignore_routes), tag_values: &get_tags(&1, routers), tags: http_metrics_tags, - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), distribution( - metric_prefix ++ [:http, :response, :duration, :milliseconds], + metric_prefix ++ [:http, :response, :duration, duration_unit_plural], event_name: cowboy_stop_event, measurement: :resp_duration, description: "The time it takes for the application to send the HTTP response.", @@ -130,10 +134,10 @@ if Code.ensure_loaded?(Plug.Cowboy) do drop: drop_ignored(ignore_routes), tag_values: &get_tags(&1, routers), tags: http_metrics_tags, - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), distribution( - metric_prefix ++ [:http, :request_body, :duration, :milliseconds], + metric_prefix ++ [:http, :request_body, :duration, duration_unit_plural], event_name: cowboy_stop_event, measurement: :req_body_duration, description: "The time it takes for the application to receive the HTTP request body.", @@ -143,7 +147,7 @@ if Code.ensure_loaded?(Plug.Cowboy) do drop: drop_ignored(ignore_routes), tag_values: &get_tags(&1, routers), tags: http_metrics_tags, - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), # Capture response payload size information diff --git a/lib/prom_ex/plugins/plug_router.ex b/lib/prom_ex/plugins/plug_router.ex index c1aea605..e54e6d6a 100644 --- a/lib/prom_ex/plugins/plug_router.ex +++ b/lib/prom_ex/plugins/plug_router.ex @@ -13,6 +13,9 @@ if Code.ensure_loaded?(Plug.Router) do - `event_prefix`: **Required**, allows you to set the event prefix defined in your `Plug.Telemetry` configuration: + - `duration_unit`: This is an OPTIONAL option and is a `Telemetry.Metrics.time_unit()`. It can be one of: + `:second | :millisecond | :microsecond | :nanosecond`. It is `:millisecond` by default. + ``` defmodule WebApp.Router do use Plug.Router @@ -160,12 +163,15 @@ if Code.ensure_loaded?(Plug.Router) do |> Keyword.get(:ignore_routes, []) |> MapSet.new() + duration_unit = Keyword.get(opts, :duration_unit, :millisecond) + duration_unit_plural = PromEx.Utils.make_plural_atom(duration_unit) + Event.build( :plug_router_http_event_metrics, [ # Capture request duration information distribution( - metric_prefix ++ [:http, :request, :duration, :milliseconds], + metric_prefix ++ [:http, :request, :duration, duration_unit_plural], event_name: @stop_event, measurement: :duration, description: "The time it takes for the application to process HTTP requests.", @@ -175,7 +181,7 @@ if Code.ensure_loaded?(Plug.Router) do drop: drop_ignored(ignore_routes, routers), tag_values: &get_tags(&1), tags: http_metrics_tags, - unit: {:native, :millisecond} + unit: {:native, duration_unit} ), distribution( metric_prefix ++ [:http, :response, :size, :bytes], diff --git a/lib/prom_ex/utils.ex b/lib/prom_ex/utils.ex index 07e74bfa..109bb4b6 100644 --- a/lib/prom_ex/utils.ex +++ b/lib/prom_ex/utils.ex @@ -7,6 +7,8 @@ defmodule PromEx.Utils do @typedoc "The kinds of exceptions that can occur" @type exception_kind :: :error | :exit | :throw + @type duration_unit_plural :: :seconds | :milliseconds | :microseconds | :nanoseconds + @doc """ Take a module name and normalize it for use as a metric label. @@ -54,4 +56,12 @@ defmodule PromEx.Utils do def normalize_exception(_kind, _reason, _stacktrace) do "UnknownException" end + + @doc """ + Converts a `time_unit` to its plural form. + """ + @spec make_plural_atom(System.time_unit()) :: atom() + def make_plural_atom(word) do + String.to_existing_atom("#{Atom.to_string(word)}s") + end end