Packages
prom_ex
0.1.8-alpha
1.12.0
1.11.0
1.10.0
1.9.0
1.8.0
1.7.1
1.7.0
retired
1.6.0
1.5.0
1.4.1
1.4.0
1.3.0
1.2.2
1.2.1
1.2.0
1.1.1
1.1.0
1.0.1
1.0.0
0.1.15-beta
0.1.14-beta
0.1.13-beta
0.1.12-beta
0.1.11-alpha
0.1.10-alpha
0.1.9-alpha
0.1.8-alpha
0.1.7-alpha
0.1.6-alpha
0.1.5-alpha
0.1.4-alpha
0.1.3-alpha
0.1.2-alpha
0.1.1-alpha
0.1.0-alpha
Prometheus metrics and Grafana dashboards for all of your favorite Elixir libraries
Current section
Files
Jump to
Current section
Files
lib/core/exporter.ex
defmodule PromEx.TelemetryMetricsPrometheus.Core.Exporter do
@moduledoc false
alias Telemetry.Metrics.{Counter, Distribution, LastValue, Sum}
def export(time_series, definitions) do
definitions
|> Stream.map(fn %{name: name} = metric ->
case time_series[name] do
nil -> nil
ts -> format(metric, ts)
end
end)
|> Enum.reject(&is_nil/1)
|> Enum.map(&[&1, "\n"])
|> IO.chardata_to_string()
end
def format(%Counter{} = metric, time_series) do
format_standard({metric, time_series}, "counter")
end
def format(%LastValue{} = metric, time_series) do
format_standard({metric, time_series}, "gauge")
end
def format(%Sum{} = metric, time_series) do
format_standard({metric, time_series}, "counter")
end
def format(%Distribution{} = metric, time_series) do
name = format_name(metric.name)
help = "# HELP #{name} #{escape_help(metric.description)}"
type = "# TYPE #{name} histogram"
distributions =
Enum.map_join(time_series, "\n", fn ts ->
format_distribution(metric, ts)
end)
Enum.join([help, type, distributions], "\n")
end
def format_distribution(metric, {{_, labels}, {buckets, count, sum}}) do
name = format_name(metric.name)
has_labels = map_size(labels) > 0
samples =
Enum.map_join(buckets, "\n", fn {upper_bound, count} ->
if has_labels do
~s(#{name}_bucket{#{format_labels(labels)},le="#{upper_bound}"} #{count})
else
~s(#{name}_bucket{le="#{upper_bound}"} #{count})
end
end)
summary =
if has_labels do
"#{name}_sum{#{format_labels(labels)}} #{sum}\n#{name}_count{#{format_labels(labels)}} #{count}"
else
"#{name}_sum #{sum}\n#{name}_count #{count}"
end
Enum.join([samples, summary], "\n")
end
defp format_standard({metric, time_series}, type) do
name = format_name(metric.name)
help = "# HELP #{name} #{escape_help(metric.description)}"
type = "# TYPE #{name} #{type}"
samples =
Enum.map_join(time_series, "\n", fn {{_, labels}, val} ->
has_labels = map_size(labels) > 0
if has_labels do
"#{name}{#{format_labels(labels)}} #{val}"
else
"#{name} #{val}"
end
end)
Enum.join([help, type, samples], "\n")
end
defp format_labels(labels) do
labels
|> Enum.map(fn {k, v} -> ~s/#{k}="#{escape(v)}"/ end)
|> Enum.sort()
|> Enum.join(",")
end
defp format_name(name) do
name
|> Enum.join("_")
|> String.replace(~r/[^a-zA-Z_][^a-zA-Z0-9_]*/, "")
end
defp escape(value) do
value
|> to_string()
|> String.replace(~S("), ~S(\"))
|> String.replace(~S(\\), ~S(\\\\))
|> String.replace(~S(\n), ~S(\\n))
end
defp escape_help(value) do
value
|> to_string()
|> String.replace(~S(\\), ~S(\\\\))
|> String.replace(~S(\n), ~S(\\n))
end
end