Malachi. Metrics
(malachi v0.22.7)
View Source
Real-time operational and security metrics kept in ETS (atomic update_counter), plus a
periodically-sampled system snapshot and its recent history.
The increment_*/record_* functions bump counters on the hot path (fast, lock-free), rate-limit and
connection-limit blocks, auth failures and account lockouts, audit events, dashboard-auth outcomes, and
TLS handshakes; get_system_metrics/0 reads the live BEAM snapshot
(memory, processes, io) folded together with those counters, and get_history/1 returns the recent
snapshots. A counter is created on first touch, so callers never need to initialize one.
Summary
Functions
Returns a specification to start this module under a supervisor.
The recent per-second history samples for the last seconds (default 60).
A snapshot of system-wide metrics (memory, processes, connections, auth) for the dashboard.
Increment account lockout counter.
Increment account lockout blocked attempt counter.
Increment audit event counter by event type.
Increment connection limit blocked counter.
Increment dashboard authentication blocked (rate limited) counter.
Increment dashboard authentication failure counter.
Increment dashboard authentication success counter.
Increment failed authentication attempt counter.
Increment rate limit blocked counter for specific action.
Increment TLS handshake failure counter.
Increment TLS handshake success counter.
Records an authentication attempt with result (:ok / :error).
Records count records consumed (from the consume telemetry event).
Records that a heal pass finished the seal for count segments whose store was already fenced,
which is what lets their ranges take writes again. See record_orphaned_fence/0 for why both halves
are counted.
Records one group-commit flush (from the storage flush telemetry event): duration_us into the latency
histogram, plus the bytes and records it made durable.
Records a segment that failed checksum verification, by reason (:bad_crc, :bad_magic,
:incomplete). Non-zero means data at rest is damaged somewhere on this node, which is otherwise
a silent condition: a damaged copy serves short reads without any error.
Records one segment whose store was fenced while recording its seal in the control plane failed, so it is closed to writes while the metadata still calls it active and its range accepts nothing.
Records count records and bytes value bytes produced (from the produce telemetry event).
Records count ticks on which the broker's control plane reconcile did not complete, by reason
(:skipped, :down or :timeout; anything else is counted as :other, so the exported label set
stays closed). See Malachi.Telemetry.reconcile_degraded/1.
Records a quorum replication with result (:ok / :no_quorum).
Records what one expire answered (from the retention expire telemetry event): an expired segment
(:ok) counts itself and its bytes under topic; a segment already gone (:no_such_segment)
counts nowhere, since an earlier sweep expired it; anything else is a refusal counted under its reply.
Records count replica directories an expire of topic left behind, because the replica holding them
did not answer its delete (from the retention orphan telemetry event). The counterpart of what the
orphan sweeper reclaims: the two together say whether the sweeper is keeping up.
Records count replica directories the orphan sweeper reclaimed (from the retention orphan
telemetry event). Unlabeled: a reclaimed directory is named by the sweeper's log line, and the
segment it belonged to is exactly what the control plane no longer knows.
Records one skip (from the retention skip telemetry event): a reader of group on topic was moved
past offsets offsets no longer stored. Counted as one event and as the offsets, under the reader's
origin and the skip's span.
Records one retention sweep's duration (from the retention sweep telemetry event). The histogram exists from this server's first start, before the reporter that calls this is attached.
Records that a sweep found topic bound to a policy name it could not resolve, and expired nothing
of it. Counted per sweep, so a name that stays unresolved keeps the series moving: the disk it holds
is invisible otherwise, and the fix is a binding an operator has to make.
Records one integrity scrub pass: how many segments it verified, repaired, and could not repair. A verified total that stops advancing is how an operator sees that the scrub itself has stopped, which the failure counters alone cannot show (they stay at zero both when all is well and when nothing is checking). The unrepairable total is the one that calls for a human: it counts damage the cluster could not heal by itself, which on a single node is every finding there is.
Records copies copies of sealed segments settled against the recorded length, by the heal pass or by the node that
trims, and tells them apart by result (:fenced, :trimmed, :failed or :recorded_size_below), and the
records a trim gave up.
Records a storage operation that failed on a segment's copy (Malachi.Cluster.ReplicationServer took
the copy out of service), bucketed by reason: :enospc (a full volume), :eio (a failing device),
:eacces (a permission problem), and :other for anything else. Buckets rather than the raw reason
because a Prometheus label must come from a closed set, and these three are the ones that call for
different operator actions.
Record the negotiated TLS version for a connection.
Records one message a long-lived server had no clause for (see Malachi.UnexpectedMessage), by the
server's label and the kind it arrived as. A label outside Malachi.UnexpectedMessage.servers/0 is
counted as :other, so the exported label set stays closed whatever a caller passes.
How many {topic, group} pairs have their own retention skip label on this node.
The retention counters as the Prometheus exporter needs them: every skip series (topic, reader,
group, origin, span, with its events and offsets), the expired segments and bytes per topic, the
refusals per reply (every known reply, zero included), the replica directories expiries left behind per
topic, the ones the sweeper reclaimed, and the sweep duration histogram in the shape
of storage_flush_histogram/0 (its count is the number of sweeps). Read only at scrape time.
Starts the metrics server (owns the ETS counter table).
The storage flush histogram as the Prometheus exporter needs it: the count of flushes at or below each
of Malachi.Histogram.edges/0, the total count and the sum in microseconds, the durability totals, and
created, the Unix time in seconds when this node's histogram began.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
The recent per-second history samples for the last seconds (default 60).
A snapshot of system-wide metrics (memory, processes, connections, auth) for the dashboard.
Increment account lockout counter.
Increment account lockout blocked attempt counter.
Increment audit event counter by event type.
Increment connection limit blocked counter.
Increment dashboard authentication blocked (rate limited) counter.
Increment dashboard authentication failure counter.
Increment dashboard authentication success counter.
Increment failed authentication attempt counter.
Increment rate limit blocked counter for specific action.
Increment TLS handshake failure counter.
Increment TLS handshake success counter.
Records an authentication attempt with result (:ok / :error).
Records count records consumed (from the consume telemetry event).
Records that a heal pass finished the seal for count segments whose store was already fenced,
which is what lets their ranges take writes again. See record_orphaned_fence/0 for why both halves
are counted.
@spec record_flush(non_neg_integer(), non_neg_integer(), non_neg_integer()) :: :ok
Records one group-commit flush (from the storage flush telemetry event): duration_us into the latency
histogram, plus the bytes and records it made durable.
Four lock-free adds, not one atomic write: a scrape that lands between them can see a flush in the
count before its duration is in the sum, which skews a windowed mean by at most one flush. A no-op when
Malachi.Metrics never started, so a store used on its own does not need it.
Records a segment that failed checksum verification, by reason (:bad_crc, :bad_magic,
:incomplete). Non-zero means data at rest is damaged somewhere on this node, which is otherwise
a silent condition: a damaged copy serves short reads without any error.
Records one segment whose store was fenced while recording its seal in the control plane failed, so it is closed to writes while the metadata still calls it active and its range accepts nothing.
Counted separately from record_fences_reconciled/1 because it is the RATIO that carries the
meaning: a detection that a later pass reconciles is a split that recovered, and a detection nothing
reconciles is a range that is stuck. One counter could not tell those apart.
Records count records and bytes value bytes produced (from the produce telemetry event).
Records count ticks on which the broker's control plane reconcile did not complete, by reason
(:skipped, :down or :timeout; anything else is counted as :other, so the exported label set
stays closed). See Malachi.Telemetry.reconcile_degraded/1.
Records a quorum replication with result (:ok / :no_quorum).
@spec record_retention_expire(String.t(), non_neg_integer(), atom()) :: :ok
Records what one expire answered (from the retention expire telemetry event): an expired segment
(:ok) counts itself and its bytes under topic; a segment already gone (:no_such_segment)
counts nowhere, since an earlier sweep expired it; anything else is a refusal counted under its reply.
@spec record_retention_orphan_left(String.t(), non_neg_integer()) :: :ok
Records count replica directories an expire of topic left behind, because the replica holding them
did not answer its delete (from the retention orphan telemetry event). The counterpart of what the
orphan sweeper reclaims: the two together say whether the sweeper is keeping up.
@spec record_retention_orphan_removed(non_neg_integer()) :: :ok
Records count replica directories the orphan sweeper reclaimed (from the retention orphan
telemetry event). Unlabeled: a reclaimed directory is named by the sweeper's log line, and the
segment it belonged to is exactly what the control plane no longer knows.
@spec record_retention_skip( String.t(), String.t() | nil, atom(), atom(), non_neg_integer() ) :: :ok
Records one skip (from the retention skip telemetry event): a reader of group on topic was moved
past offsets offsets no longer stored. Counted as one event and as the offsets, under the reader's
origin and the skip's span.
The series carry WHICH KIND of reader they count (reader) beside the name (group): :group with
the name as it is, :none with an empty name for a fetch outside a group (group is nil), and
:other with an empty name for a group folded by the cap. Nothing reserves a group name, so the kind
has to be its own label: with the name alone, a group a client called "__other__" would share a
series with the folded ones, and one called "" with a fetch outside a group.
A {topic, group} pair keeps its own name while fewer than :retention_metrics_max_groups (default
- pairs have been admitted on this node, and is folded into
:otherafter that, so a client inventing group names cannot grow the scrape without bound. The per-topic sum stays exact. Admission is check-then-insert across the few processes that report (one skip reporter per data-plane shard), so concurrent first skips can admit a pair or two past the cap: a bound, not an exact limit.
@spec record_retention_sweep(non_neg_integer()) :: :ok
Records one retention sweep's duration (from the retention sweep telemetry event). The histogram exists from this server's first start, before the reporter that calls this is attached.
@spec record_retention_unresolved_policy(String.t(), non_neg_integer()) :: :ok
Records that a sweep found topic bound to a policy name it could not resolve, and expired nothing
of it. Counted per sweep, so a name that stays unresolved keeps the series moving: the disk it holds
is invisible otherwise, and the fix is a binding an operator has to make.
Records one integrity scrub pass: how many segments it verified, repaired, and could not repair. A verified total that stops advancing is how an operator sees that the scrub itself has stopped, which the failure counters alone cannot show (they stay at zero both when all is well and when nothing is checking). The unrepairable total is the one that calls for a human: it counts damage the cluster could not heal by itself, which on a single node is every finding there is.
Records copies copies of sealed segments settled against the recorded length, by the heal pass or by the node that
trims, and tells them apart by result (:fenced, :trimmed, :failed or :recorded_size_below), and the
records a trim gave up.
Split by result for the same reason record_orphaned_fence/0 is split from
record_fences_reconciled/1: settling a copy is overwhelmingly a marker and nothing else, and the
rare copy that gave up records was holding data its segment's seal excludes. One counter would make
the second unfindable inside the first, and the first pass after an upgrade produces thousands of it.
Records a storage operation that failed on a segment's copy (Malachi.Cluster.ReplicationServer took
the copy out of service), bucketed by reason: :enospc (a full volume), :eio (a failing device),
:eacces (a permission problem), and :other for anything else. Buckets rather than the raw reason
because a Prometheus label must come from a closed set, and these three are the ones that call for
different operator actions.
Record the negotiated TLS version for a connection.
Records one message a long-lived server had no clause for (see Malachi.UnexpectedMessage), by the
server's label and the kind it arrived as. A label outside Malachi.UnexpectedMessage.servers/0 is
counted as :other, so the exported label set stays closed whatever a caller passes.
@spec retention_group_count() :: non_neg_integer()
How many {topic, group} pairs have their own retention skip label on this node.
@spec retention_snapshot() :: %{ skips: [map()], expired: [map()], orphans_left: [map()], orphans_removed: non_neg_integer(), unresolved_policies: [map()], failures: %{required(atom()) => non_neg_integer()}, sweeps: map() }
The retention counters as the Prometheus exporter needs them: every skip series (topic, reader,
group, origin, span, with its events and offsets), the expired segments and bytes per topic, the
refusals per reply (every known reply, zero included), the replica directories expiries left behind per
topic, the ones the sweeper reclaimed, and the sweep duration histogram in the shape
of storage_flush_histogram/0 (its count is the number of sweeps). Read only at scrape time.
Starts the metrics server (owns the ETS counter table).
@spec storage_flush_histogram() :: %{ buckets: [{float(), non_neg_integer()}], count: non_neg_integer(), sum_us: non_neg_integer(), bytes: non_neg_integer(), records: non_neg_integer(), created: float() }
The storage flush histogram as the Prometheus exporter needs it: the count of flushes at or below each
of Malachi.Histogram.edges/0, the total count and the sum in microseconds, the durability totals, and
created, the Unix time in seconds when this node's histogram began.
created is what tells two scrapes of one histogram from scrapes on either side of a node restart: it
changes only when the node boots again, never on a Malachi.Metrics restart, which keeps the samples.
Counters alone cannot tell, since a restarted node can flush past its old totals before the next scrape.
Kept out of get_system_metrics/0, which is snapshotted every second and sent to the dashboard, neither
of which has any use for the buckets. All zeros when Malachi.Metrics never started.