defmodule Ogle.Options do @statsd_opts_schema [ flush_interval_ms: [ type: :non_neg_integer, default: 5000, doc: "Time interval between StatsD metric emissions." ], host: [ type: {:custom, __MODULE__, :host, []}, default: {127, 0, 0, 1}, doc: "Hostname or IP address of the StatsD server." ], port: [ type: :non_neg_integer, default: 8125, doc: "Port of the StatsD server." ], socket_path: [ type: {:custom, __MODULE__, :socket_path, []}, doc: "Path to the Unix Domain Socket used for publishing instead of the hostname and port. Overrides `:host` and `:port` configuration if present" ], formatter: [ type: {:custom, __MODULE__, :formatter, []}, default: :standard, doc: "Determines the format of the published metrics. Can be either `:standard` or `:datadog`." ], mtu: [ type: :non_neg_integer, default: 1472, doc: """ Determine max size of statsd packets. For UDP, 1472 is a good choice. For UDS, 8192 is probably better. The rationale for these recommendations comes from [this guide](https://docs.datadoghq.com/developers/dogstatsd/high_throughput/#ensure-proper-packet-sizes). """ ] ] @schema [ name: [ type: :atom, required: true, doc: "A name for the Ogle worker process" ], metrics: [ type: {:list, :any}, required: true, doc: "A list of `Telemetry.Metrics` metric definitions to be collected and exposed." ], statsd: [ type: {:or, [nil, {:keyword_list, @statsd_opts_schema}]}, doc: "An optional keyword list of statsd configuration.\n\n" <> NimbleOptions.docs(@statsd_opts_schema, nest_level: 1) ], global_tags: [ type: :map, default: %{}, doc: "Additional tags published with every metric. " <> "Global tags are overridden by the tags specified in the metric definition." ], storage: [ type: {:in, [:default, :striped]}, default: :default, doc: "Which storage implementation to use. " <> "`:default` uses a single ETS table, with some optimizations for concurrent writing. " <> "`:striped` uses one ETS table per scheduler thread, " <> "which trades memory for less lock contention for concurrent writes." ] ] @moduledoc """ Options for a `Ogle` reporter. Validated with `NimbleOptions`. #{NimbleOptions.docs(@schema, nest_level: 0)} """ defstruct Keyword.keys(@schema) @type t() :: %__MODULE__{} @spec docs() :: String.t() def docs do NimbleOptions.docs(@schema) end @spec validate(Keyword.t()) :: {:ok, %__MODULE__{}} | {:error, String.t()} def validate(options) do case NimbleOptions.validate(options, @schema) do {:ok, options} -> new_statsd_options = rename_socket_path(options[:statsd]) new_options = Keyword.put(options, :statsd, new_statsd_options) {:ok, struct(__MODULE__, new_options)} {:error, err} -> {:error, Exception.message(err)} end end @doc false @spec host(term()) :: {:ok, :inet.ip_address() | :inet.hostname()} | {:error, String.t()} def host(address) when is_tuple(address) do case :inet.ntoa(address) do {:error, _} -> {:error, "expected :host to be a valid IP address, got #{inspect(address)}"} _ -> {:ok, address} end end def host(address) when is_binary(address) do {:ok, to_charlist(address)} end def host(term) do {:error, "expected :host to be an IP address or a hostname, got #{inspect(term)}"} end @doc false @spec socket_path(term()) :: {:ok, :inet.local_address()} | {:error, String.t()} def socket_path(path) when is_binary(path) do {:ok, {:local, to_charlist(path)}} end def socket_path(term) do {:error, "expected :socket_path to be a string, got #{inspect(term)}"} end @doc false @spec formatter(term()) :: {:ok, :standard | :datadog} | {:error, String.t()} def formatter(:standard) do {:ok, :standard} end def formatter(:datadog) do {:ok, :datadog} end def formatter(term) do {:error, "expected :formatter be either :standard or :datadog, got #{inspect(term)}"} end defp rename_socket_path(nil) do nil end defp rename_socket_path(statsd_opts) do if socket_path = Keyword.get(statsd_opts, :socket_path) do statsd_opts |> Keyword.put(:host, socket_path) |> Keyword.delete(:socket_path) else statsd_opts end end end