ostium.proto

index ยท ostium

Overview

Ostium wire protocol codec: newline-framed text lines over a unix stream socket, fields separated by |. Shared by the bundled client and the ostiarius daemon, so encoder and decoder can never drift.

Metric lines (m) carry counter deltas, gauges, or pre-aggregated timings; block commands (b) and CLI queries (q) get replies (ok/err/qd/qe). Label values and free-text fields percent-encode %, |, ,, = and control bytes, so a raw split on the delimiters is always safe.

Functions

NameSignature
valid_appvalid_app(app) -> ok
valid_namevalid_name(name) -> ok
valid_ipvalid_ip(ip) -> ok
escapeescape(s) -> escaped
unescapeunescape(s) -> decoded, err
format_numformat_num(n) -> formatted, err
canonical_labelscanonical_labels(labels) -> labels_str, err
parse_labelsparse_labels(labels_str) -> labels, err
series_idseries_id(app, name, labels) -> id, err
encode_counterencode_counter(app, name, labels, delta) -> line, err
encode_gaugeencode_gauge(app, name, labels, value) -> line, err
encode_timingencode_timing(app, name, labels, agg) -> line, err
encode_blockencode_block(app, ip, opts) -> line, err
encode_queryencode_query(cmd, arg) -> line, err
encode_replyencode_reply(kind, a, b) -> line, err
decodedecode(line) -> msg, err

valid_app(app) -> ok

Check that an app name is valid for the wire

App names are 1 to 32 bytes of [a-z0-9_-]. The alphabet excludes every field delimiter, so an app name never needs escaping.

valid_name(name) -> ok

Check that a metric name is valid for the wire

Metric names are 1 to 64 bytes of [a-z0-9_], the same delimiter-free alphabet rule that applies to app names.

valid_ip(ip) -> ok

Check that a string is plausibly an IP address

A shape check only: up to 45 bytes of hex digits, dots and colons, which covers IPv4, IPv6 and IPv4-mapped forms. The daemon hands the address to ipset, which is the real parser.

escape(s) -> escaped

Percent-encode the characters that would break field framing

Encodes %, |, ,, = and control bytes as %XX. Applied to label values and to free text such as the block reason, so a raw split on the delimiters is always safe.

unescape(s) -> decoded, err

Decode a percent-encoded field back to its raw bytes

Rejects a % that is not followed by two hex digits; every other byte passes through untouched.

format_num(n) -> formatted, err

Format a number for the wire

%.14g matches LuaJIT's own tostring precision: enough significant digits to carry unix timestamps intact (a plain %.9g would round them), while never printing locale-dependent or exponentless noise. Non-finite values are rejected.

canonical_labels(labels) -> labels_str, err

Canonicalize a labels table into the wire labels string

Validates label keys against [a-z0-9_]{1,32}, escapes values (max 128 bytes before escaping), sorts pairs by key and joins them as key=value,.... A nil or empty table yields the empty string. Series identity relies on this canonical form: the same table always encodes to the same string.

parse_labels(labels_str) -> labels, err

Parse a canonical labels string back into a table

series_id(app, name, labels) -> id, err

Build the canonical series identity string

Returns app|name|labels with labels in canonical form. Accepts either a labels table (canonicalized here) or an already-canonical labels string (passed through untouched).

encode_counter(app, name, labels, delta) -> line, err

Encode a counter delta metric line

The delta is the increment accumulated since the last flush, never a running total, and must be a finite number no smaller than zero.

encode_gauge(app, name, labels, value) -> line, err

Encode a gauge metric line

The value is absolute and replaces whatever the daemon currently holds for the series.

encode_timing(app, name, labels, agg) -> line, err

Encode a timing aggregate metric line

agg is {count, sum, min, max} in seconds, accumulated by the client between flushes. Count must be a positive integer and min must not exceed max.

encode_block(app, ip, opts) -> line, err

Encode a block command line

opts.ttl is a non-negative integer in seconds (0 or absent means the ipset default); opts.reason is free text, escaped on the wire.

encode_query(cmd, arg) -> line, err

Encode a CLI query line

encode_reply(kind, a, b) -> line, err

Encode a reply line

Kinds: ("ok", mtype), ("err", mtype, message) with the message escaped, ("qd", fields) where fields is an array of raw strings that must not contain | or control bytes (the producer escapes free-text portions itself), ("qe", count).

decode(line) -> msg, err

Decode one protocol line into a tagged message table

Accepts a line with or without the trailing newline (\r\n is tolerated for interactive debugging). Returns a table tagged by kind: "metric", "block", "query", "ok", "err", "qd" or "qe". Metric labels stay in their raw canonical string form; the daemon treats them as an opaque, sorted identity component.