Collect metrics
pathways start serves its metrics at GET /metrics on the
health endpoint, in the OpenMetrics 1.0 text
format. Prometheus reads it as it is, and so does any collector that reads Prometheus metrics. The
names, their labels and what each one means are listed in the
configuration reference.
Serve them
A health: block is all it takes: /metrics is on by default, behind the endpoint's bearer token or
mutual TLS.
health:
port: 9090
host: 0.0.0.0
tls:
cert: certs/health.pem
key: certs/health.key
curl --cacert certs/ca.pem -H "Authorization: Bearer $PATHWAYS_HEALTH_TOKEN" https://localhost:9090/metrics
The answer's Content-Type is application/openmetrics-text; version=1.0.0; charset=utf-8, and the
text ends with # EOF.
A health endpoint with auth: none and no tls.ca can be read by anyone who can reach its port,
over plaintext or TLS alike, so it answers /metrics with 403, and pathways start logs
metrics-refused. Give it a token,
or, on a network only your collector can reach, say so in the file:
health:
port: 9090
plaintext: true
auth: none
metrics:
allowUnauthenticated: true
metrics: { enabled: false } turns /metrics off; it then answers with the health body, as any
other path does.
Scrape them with Prometheus
scrape_configs:
- job_name: pathways
scheme: https
metrics_path: /metrics
authorization:
type: Bearer
credentials_file: /etc/prometheus/pathways-health-token
tls_config:
ca_file: /etc/prometheus/pathways-ca.pem
static_configs:
- targets: ["pathways-1.internal.example:9090", "pathways-2.internal.example:9090"]
One set of store gauges costs four counting reads of the database per channel, and
pathways_store_latency_seconds reports how long they took. A set serves every scrape of the next 5
seconds, so several collectors, or one that retries, read the database once.
Read them right
- Counters start at 0 when the engine starts.
pathways_messages_received_total,pathways_messages_refused_total,pathways_transforms_total,pathways_deliveries_total,pathways_messages_settled_totalandpathways_settled_destinations_totalcount what this process did since it started, and each series carries its_createdtime. Read them withrate()orincrease(), which handle the reset a restart makes. - Each message counts once on each channel. A message handed from one channel to another, by a
channel destination or
ctx.route, counts as received on each, once; the hand-off counts as a delivery of the channel it leaves. Each piece of a batch counts as a message. A body the source refused (a compressed HTTP body, a frame with a second message header) counts inpathways_messages_refused_totaland never as received. - The settled counters follow the
settledevent. Each event adds one topathways_messages_settled_totaland one per destination topathways_settled_destinations_total, under the state the event gives that destination. - Store gauges are the database's.
pathways_queue_depth,pathways_oldest_pending_age_seconds,pathways_deliveries_retrying,pathways_deliveries_failedandpathways_messages_failedare read from the database (at most once every 5 seconds), so they survive a restart, and every node on the same database reports the same values. Takemaxover nodes, neversum. - A standby serves no gauges. Until it takes the engine lease, a standby
(
pathways start --standby) serves its counters, all 0, and nothing else. - Labels are bounded. Each series is labelled by
channel,destination(the destination'sname:, or its place in the channel'sdestinations:list from 0 where it has none) and anoutcomefrom a fixed list, never by a message id, a control id, a patient field, a metadata key, a URL, an error's text or a host, so the number of series is set by your channels, not by your traffic. Give each destination aname:and its series keep their label when the list is reordered; its store gauges follow it by its destination key either way.
Alert on them
groups:
- name: pathways
rules:
- alert: PathwaysQueueStuck
expr: max by (channel, destination) (pathways_oldest_pending_age_seconds) > 600
for: 5m
- alert: PathwaysDeliveriesFailing
expr: sum by (channel, destination) (increase(pathways_deliveries_total{outcome=~"rejected|exhausted"}[15m])) > 0
- alert: PathwaysRetrying
expr: max by (channel, destination) (pathways_deliveries_retrying) > 0
for: 15m
- alert: PathwaysSourceDown
expr: pathways_source_up == 0
for: 2m
- alert: PathwaysStoreDown
expr: pathways_store_up == 0
for: 1m
A message that waits longer than its destination should take is the alarm that matters most:
pathways_oldest_pending_age_seconds rises while a destination is down or refusing, whatever the
traffic. pathways_deliveries_failed and pathways_messages_failed then say how many need an
operator; see Inspect, replay and clear messages.
Read the names from code
The package exports the list /metrics serves, in order, for a dashboard or an alarm built in code:
import { METRIC_FAMILIES, OPENMETRICS_CONTENT_TYPE } from "@cosyte/pathways";
for (const family of METRIC_FAMILIES) {
const sample = family.type === "counter" ? `${family.name}_total` : family.name;
console.log(sample, family.type, family.labels.join(","));
}
console.log(OPENMETRICS_CONTENT_TYPE);