Current section

Files

Jump to
prometheus_parser lib prometheus_parser.ex
Raw

lib/prometheus_parser.ex

defmodule PrometheusParser.Line do
defstruct line_type: nil,
timestamp: nil,
pairs: [],
value: nil,
documentation: nil,
type: nil,
label: nil
end
defimpl String.Chars, for: PrometheusParser.Line do
def pairs_to_string(pairs) do
pairs
|> Enum.map(fn {key, value} -> "#{key}=\"#{value}\"" end)
|> Enum.join(", ")
end
def to_string(%{line_type: "COMMENT", documentation: documentation}),
do: "# #{documentation}"
def to_string(%{line_type: "HELP", label: label, documentation: documentation}),
do: "# HELP #{label} #{documentation}"
def to_string(%{line_type: "TYPE", label: label, type: type}),
do: "# TYPE #{label} #{type}"
def to_string(%{
line_type: "ENTRY",
label: label,
pairs: pairs,
value: value,
timestamp: timestamp
})
when not is_nil(timestamp) do
"#{label}{#{pairs_to_string(pairs)}} #{value} #{timestamp}"
end
def to_string(%{
line_type: "ENTRY",
label: label,
pairs: [],
value: value,
timestamp: timestamp
})
when not is_nil(timestamp) do
"#{label} #{value} #{timestamp}"
end
def to_string(%{
line_type: "ENTRY",
label: label,
pairs: [],
value: value
}) do
"#{label} #{value}"
end
def to_string(%{
line_type: "ENTRY",
label: label,
pairs: pairs,
value: value
}) do
"#{label}{#{pairs_to_string(pairs)}} #{value}"
end
end
defmodule PrometheusParser do
import NimbleParsec
alias PrometheusParser.Line
comment =
string("#")
|> tag(:comment)
|> ignore(string(" "))
documentation =
utf8_string([], min: 1)
|> tag(:documentation)
prom_label =
ascii_char([?a..?z])
|> lookahead()
|> ascii_string([?a..?z, ?A..?Z] ++ [?0..?9] ++ [?_], min: 1)
help =
string("HELP")
|> tag(:help)
|> ignore(string(" "))
|> concat(prom_label)
|> tag(:prom_label)
|> ignore(string(" "))
|> concat(documentation)
type =
string("TYPE")
|> tag(:type)
|> ignore(string(" "))
|> concat(prom_label)
|> ignore(string(" "))
|> choice([
string("gauge"),
string("counter")
])
|> tag(:type)
prom_key_value =
prom_label
|> tag(:pair_key)
|> ignore(string("=\""))
|> optional(ascii_string([?a..?z, ?A..?Z, ?0..?9, ?-..?-, ?_..?_, ?...?:], min: 1))
|> label("expected a-z,A-Z,0-9,\-")
|> tag(:pair_value)
|> ignore(string("\""))
prom_integer_or_float = ascii_string([?0..?9, ?e, ?E, ?., ?+, ?-], min: 1)
prom_entry =
prom_label
|> tag(:prom_label)
|> ignore(string(" "))
|> concat(prom_integer_or_float |> tag(:entry_value))
prom_entry_with_timestamp =
prom_entry
|> ignore(string(" "))
|> concat(prom_integer_or_float |> tag(:timestamp))
prom_entry_with_key_and_value =
prom_label
|> tag(:prom_label)
|> ignore(string("{"))
|> repeat(
prom_key_value
|> ignore(optional(string(",")))
|> ignore(optional(string(" ")))
)
|> ignore(string("}"))
|> ignore(string(" "))
|> concat(prom_integer_or_float |> tag(:entry_value))
prom_entry_with_key_and_value_and_timestamp =
prom_entry_with_key_and_value
|> ignore(string(" "))
|> concat(prom_integer_or_float |> tag(:timestamp))
unsupported =
empty()
|> line()
|> tag(:unsupported)
defparsec(
:parse_line,
choice([
comment |> concat(help),
comment |> concat(type),
comment |> concat(documentation),
prom_entry_with_timestamp,
prom_entry,
prom_entry_with_key_and_value_and_timestamp,
prom_entry_with_key_and_value,
unsupported
])
)
def parse_file(file) do
file
|> File.read!()
|> String.split("\n")
|> Enum.map(&parse/1)
end
def parse(line) do
line
|> parse_line()
|> format()
end
def format({:ok, [{:unsupported, _}], line, %{} = _context, _line, _offset}),
do: {:error, "Unsupported syntax: #{inspect(line)}"}
def format({:ok, acc, "" = _rest, %{} = _context, _line, _offset}),
do: format(acc)
def format(acc) when is_list(acc) do
line =
acc
|> Enum.reduce(%Line{}, fn item, acc ->
case item do
{:comment, ["#"]} ->
%{acc | line_type: "COMMENT"}
{:prom_label, [{:help, ["HELP"]}, label]} ->
%{acc | line_type: "HELP", label: label}
{:prom_label, [label]} ->
%{acc | line_type: "ENTRY", label: label}
{:type, [{:type, ["TYPE"]}, label, type]} ->
%{acc | line_type: "TYPE", label: label, type: type}
{:documentation, [documentation]} ->
%{acc | documentation: documentation}
{:pair_value, [{:pair_key, [key]}]} ->
%{acc | pairs: acc.pairs ++ [{key, ""}]}
{:pair_value, [{:pair_key, [key]}, value]} ->
%{acc | pairs: acc.pairs ++ [{key, value}]}
{:entry_value, [value]} ->
%{acc | value: value}
{:timestamp, [timestamp]} ->
%{acc | timestamp: timestamp}
end
end)
{:ok, line}
end
end