Files
zeekayandHanzo Dev b3608b62ab point the metric consumers at the prefix this server emits
PrometheusMetricsWriter emits DatastoreProfileEvents_, DatastoreMetrics_,
DatastoreAsyncMetrics_, DatastoreErrorMetric_ and Datastore_Info, while four
consumers still asked for the ClickHouse spelling, so each was reading a string
its own server had stopped writing. The three integration tests failed outright,
the prometheus endpoint one twice over, since its allowed-prefix assertion
rejects every metric line before the query counter is ever looked up. The keeper
stress harness was quieter and worse: an allowlist matching nothing reads as a
run that produced no metrics rather than as an error.

The keeper-stress-analysis skill keeps the ClickHouse spelling on purpose. It
queries keeper_stress_tests.keeper_metrics_ts on play.clickhouse.com, where
those names are stored data written by upstream nightlies rather than anything
this server emits, and that table holds no Datastore rows at all. Renaming there
would turn a query that matches into one that matches nothing.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-14 10:35:18 -07:00

82 lines
2.4 KiB
Python

import json
import re
_METRIC_RE = re.compile(
r"^([a-zA-Z_:][a-zA-Z0-9_:]*)(\{([^}]*)\})?\s+([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)\s*$"
)
_LABEL_RE = re.compile(r"\s*([a-zA-Z_][a-zA-Z0-9_]*)=\"([^\"]*)\"\s*")
# Keeper's PrometheusMetricsWriter emits: DatastoreProfileEvents_*, DatastoreMetrics_*,
# DatastoreAsyncMetrics_*, DatastoreHistogramMetrics_*, DatastoreDimensionalMetrics_*.
# Also allow legacy/alternative prefixes.
DEFAULT_PREFIXES = (
"clickhouse_keeper_",
"ClickHouseKeeper_",
"DatastoreProfileEvents_",
"DatastoreMetrics_",
"DatastoreAsyncMetrics_",
"DatastoreHistogramMetrics_",
"DatastoreDimensionalMetrics_",
"keeper_",
"raft_",
)
def _unescape_label_value(s):
"""Unescape Prometheus label value (\\, \", \n, \t, \r)."""
escape_map = {"n": "\n", "t": "\t", "r": "\r", '"': '"', "\\": "\\"}
out = []
i = 0
while i < len(s):
if s[i] == "\\" and i + 1 < len(s):
out.append(escape_map.get(s[i + 1], s[i + 1]))
i += 2
else:
out.append(s[i])
i += 1
return "".join(out)
def parse_prometheus_text(
text, allow_prefixes=DEFAULT_PREFIXES, name_allowlist=None, exclude_label_keys=None
):
if not text:
return []
name_allow = set(name_allowlist) if name_allowlist else None
excl_label_keys = set(exclude_label_keys) if exclude_label_keys else None
rows = []
for line in text.splitlines():
if not line or line.startswith("#"):
continue
m = _METRIC_RE.match(line.strip())
if not m:
continue
name, _, label_blob, val = m.groups()
if allow_prefixes and not any(name.startswith(p) for p in allow_prefixes):
continue
if name_allow and name not in name_allow:
continue
labels = {}
if label_blob:
for lm in _LABEL_RE.finditer(label_blob):
labels[lm.group(1)] = _unescape_label_value(lm.group(2))
if excl_label_keys and any(k in excl_label_keys for k in labels):
continue
try:
value = float(val)
except Exception:
continue
rows.append({
"name": name,
"value": value,
"labels_json": json.dumps(labels, ensure_ascii=False),
})
return rows