defmodule Wobserver.Util.Metrics do @moduledoc ~S""" Metrics management for custom metrics and generators in wobserver. """ alias Wobserver.System.Memory @metrics_table :wobserver_metrics @doc ~S""" Lists all metrics. Every generator is called and the generated metrics are merged into the result. """ @spec overview :: keyword def overview do memory() |> Kernel.++(io()) |> Keyword.merge(custom_metrics()) end @doc ~S""" Memory metrics. See: `Wobserver.System.Memory.usage/0`. """ @spec memory :: keyword def memory do [erlang_vm_used_memory_bytes: { &Memory.usage/0, :gauge, "Memory usage of the Erlang VM." }] end @doc ~S""" IO metrics. """ @spec io :: keyword def io do [erlang_vm_used_io_bytes: { "Tuple.to_list(:erlang.statistics(:io))", :counter, "IO counter for the Erlang VM." }] end @doc ~S""" Registers a metrics or metric generators with `:wobserver`. The `metrics` parameter must always be a list of metrics or metric generators. Returns true if succesfully added. (otherwise false) The following inputs are accepted for metrics: - `keyword` list, the key is the name of the metric and the value is the metric data. The following inputs are accepted for metric generators: - `list` of callable functions. Every function should return a keyword list with as key the name of the metric and as value the metric data. For more information about how to format metric data see: `Wobserver.Util.Metrics.Formatter.format_all/1`. """ @spec register(metrics :: list) :: boolean def register(metrics) when is_list(metrics) do ensure_storage() case Keyword.keyword?(metrics) do true -> @metrics_table |> Agent.update(fn %{generators: g, metrics: m} -> %{generators: g, metrics: Keyword.merge(m, metrics)} end) false -> @metrics_table |> Agent.update(fn %{generators: g, metrics: m} -> %{generators: g ++ metrics, metrics: m} end) end true end def register(_), do: false @doc ~S""" Loads custom metrics and metric generators from configuration and adds them to `:wobserver`. To add custom metrics set the `:metrics` option. The `:metrics` option must be a keyword list with the following keys: - `additional`, for a keyword list with additional metrics. - `generators`, for a list of metric generators. For more information and types see: `Wobserver.Util.Metrics.register/1`. Example: ```elixir config :wobserver, metrics: [ additional: [ example: {fn -> [red: 5] end, :gauge, "Description"}, ], generators: [ "&MyApp.generator/0", fn -> [bottles: {fn -> [wall: 8, floor: 10] end, :gauge, "Description"}] end fn -> [server: {"MyApp.Server.metrics/0", :gauge, "Description"}] end ] ] ``` """ @spec load_config :: true def load_config do ensure_storage() metrics = :wobserver |> Application.get_env(:metrics, []) |> Keyword.get(:additional, []) generators = :wobserver |> Application.get_env(:metrics, []) |> Keyword.get(:generators, []) @metrics_table |> Agent.update(fn %{generators: g, metrics: m} -> %{generators: g ++ generators, metrics: Keyword.merge(m, metrics)} end) true end # Helpers defp custom_metrics do ensure_storage() metrics = @metrics_table |> Agent.get(fn metrics -> metrics end) generators = metrics.generators |> Enum.reduce([], &generate/2) metrics.metrics |> Keyword.merge(generators) end defp generate(generator, results) when is_binary(generator) do {eval_generator, []} = Code.eval_string generator eval_generator |> generate(results) end defp generate(generator, results) do result = generator.() Keyword.merge(results, result) end defp ensure_storage do Agent.start_link( fn -> %{generators: [], metrics: []} end, name: @metrics_table ) end end