Packages

Web based metrics, monitoring, and observer.

Current section

Files

Jump to
wobserver_ng lib wobserver util metrics prometheus.ex
Raw

lib/wobserver/util/metrics/prometheus.ex

defmodule Wobserver.Util.Metrics.Prometheus do
@moduledoc ~S"""
Prometheus formatter.
Formats metrics in a for Prometheus readable way.
See: [https://prometheus.io/docs/instrumenting/writing_exporters/](https://prometheus.io/docs/instrumenting/writing_exporters/)
"""
@behaviour Wobserver.Util.Metrics.Formatter
@doc ~S"""
Format a set of `data` with a `label` for a Prometheus.
The `data` must be given as a `list` of tuples with the following format: `{value, labels}`, where `labels` is a keyword list with labels and their values.
The following options can also be given:
- `type`, the type of the metric. The following values are currently supported: `:gauge`, `:counter`.
- `help`, a single line text description of the metric.
"""
@spec format_data(
name :: String.t,
data :: [{integer | float, keyword}],
type :: :atom,
help :: String.t
) :: String.t
def format_data(name, data, type, help) do
"#{format_help name, help}#{format_type name, type}#{format_values name, data}"
end
@doc ~S"""
Combines formatted metrics together.
Arguments:
- `metrics`, a list of formatted metrics for one node.
Example:
iex> combine_metrics ["metric1{node="127.0.0.1"} 5\n", "metric2{node="127.0.0.1"} 5\n"]
"metric1{node="127.0.0.1"} 5\n", "metric2{node="127.0.0.1"} 5\n"
"""
@spec combine_metrics(
metrics :: list[String.t]
) :: String.t
def combine_metrics(metrics), do: Enum.join(metrics)
@doc ~S"""
Merges formatted sets of metrics from different nodes together.
The merge will filter out double declarations of help and type.
Arguments:
- `metrics`, a list of formatted sets metrics for multiple node.
Example:
iex> combine_metrics ["metric{node="192.168.0.6"} 5\n", "metric{node="192.168.0.5"} 5\n"]
"metric{node="192.168.0.6"} 5\n", "metric{node="192.168.0.7"} 5\n"
"""
@spec merge_metrics(
metrics :: list[String.t]
) :: String.t
def merge_metrics(metrics) do
{combined, _} = Enum.reduce(metrics, {"", []}, &filter/2)
combined
end
# Helpers
defp format_help(_name, nil), do: ""
defp format_help(name, help) do
"\# HELP #{name} #{help}\n"
end
defp format_type(_name, nil), do: ""
defp format_type(name, type) do
"\# TYPE #{name} #{type}\n"
end
defp format_labels(labels) do
labels
|> Enum.map(fn {label, value} -> "#{label}=\"#{value}\"" end)
|> Enum.join(",")
end
defp format_values(name, data) do
data
|> Enum.map(fn {value, labels} -> "#{name}{#{format_labels labels}} #{value}\n" end)
|> Enum.join
end
defp analyze_metrics(metrics) do
help =
~r/^\# HELP ([a-zA-Z_]+\ )/m
|> Regex.scan(metrics)
|> Enum.map(fn [match | _] -> match end)
type =
~r/^\# TYPE ([a-zA-Z_]+\ )/m
|> Regex.scan(metrics)
|> Enum.map(fn [match | _] -> match end)
help ++ type
end
defp filter_line(line, filter) do
filter
|> Enum.find_value(false, &String.starts_with?(line, &1))
|> Kernel.!
end
defp filter(metric, {metrics, filter}) do
filtered_metric =
metric
|> String.split("\n")
|> Enum.filter(&filter_line(&1, filter))
|> Enum.join("\n")
updated_filter =
filtered_metric
|> analyze_metrics()
|> Kernel.++(filter)
{metrics <> filtered_metric, updated_filter}
end
end