Barograph.Ingest.Graphite.Parser (barograph v0.2.0)

Copy Markdown View Source

Pure parsing for the Graphite plaintext line protocol (spec §10.1).

No dependency on thousand_island — compiles and is testable regardless of whether that optional dependency is present.

Summary

Functions

Splits a dotted path into {metric, labels} per a compiled template.

Compiles a dot-separated template string into matchable tokens.

Parses one Graphite plaintext line: metric value timestamp.

Types

template()

@type template() :: [String.t()]

Functions

apply_template(path, template)

@spec apply_template(String.t(), template() | nil) ::
  {:ok, String.t(), map()} | :error

Splits a dotted path into {metric, labels} per a compiled template.

compile_template(str)

@spec compile_template(String.t() | nil) :: {:ok, template() | nil} | {:error, atom()}

Compiles a dot-separated template string into matchable tokens.

"*" skips a path segment. Any other token is a literal label key, matched against the segment at that position. The token "metric" must appear exactly once, only as the final token — it then greedily consumes all remaining path segments, joined with ..

compile_template("*.forklift.metric") #=> {:ok, ["*", "forklift", "metric"]}

nil compiles to nil: the whole dotted path is used verbatim as the metric name, with no labels — zero config for plain ingest.

parse_line(line, template)

@spec parse_line(String.t(), template() | nil) ::
  {:ok, {String.t(), map(), float(), integer()}} | :error

Parses one Graphite plaintext line: metric value timestamp.

Dispatches to Graphite 1.1+ tag syntax (metric;tag=val;...) when a ; is present in the metric field, independent of any template; otherwise applies template to the dotted path.

parse_line("forklift.FL-07.engine.temp 94.2 1752931200", ["*", "forklift", "metric"])
#=> {:ok, {"engine.temp", %{"forklift" => "FL-07"}, 94.2, 1752931200}}

Trailing \r is stripped so CRLF-terminated lines work. Rejects malformed field counts, non-numeric values, the literal "nan" value (used by collectd's write_graphite for undefined datapoints — bg_samples.value is REAL NOT NULL, and letting NaN through would poison aggregate SUM/AVG/MIN/MAX), and non-integer timestamps (a float-looking timestamp is malformed, not truncated).