UNPKG

openclaw-grafana-lens

Version:

OpenClaw plugin that gives AI agents full Grafana access — 18 composable tools for PromQL/LogQL/TraceQL queries, dashboard creation, alerting, SRE investigation, security monitoring, data collection pipeline management via Grafana Alloy (29 recipes), and

95 lines (94 loc) 3.46 kB
/** * OTLP JSON Writer * * Constructs OTLP ExportMetricsServiceRequest payloads as JSON with custom * timestamps and POSTs them to the OTLP HTTP endpoint. This bypasses the * OTel SDK (which doesn't expose timestamp control) while using the same * wire protocol and collector endpoint. * * Data flow: * TimestampedSample[] → OTLP JSON (gauge dataPoints with timeUnixNano) * → POST to :4318/v1/metrics → OTel Collector → Mimir → Grafana */ // ── Writer ─────────────────────────────────────────────────────────── export class OtlpJsonWriter { endpoint; headers; constructor(config) { this.endpoint = config.endpoint; this.headers = config.headers ?? {}; } async write(samples) { if (samples.length === 0) return; const payload = this.buildPayload(samples); const res = await fetch(this.endpoint, { method: "POST", headers: { "Content-Type": "application/json", ...this.headers, }, body: JSON.stringify(payload), }); if (!res.ok) { const body = await res.text().catch(() => ""); throw new Error(`OTLP push failed (HTTP ${res.status}): ${body || res.statusText}`); } } /** Visible for testing. */ buildPayload(samples) { // Group samples by metric name const grouped = new Map(); for (const s of samples) { let group = grouped.get(s.metricName); if (!group) { group = []; grouped.set(s.metricName, group); } group.push(s); } const metrics = []; for (const [name, group] of grouped) { metrics.push({ name, description: group[0].description ?? "Custom metric", gauge: { dataPoints: group.map((s) => ({ timeUnixNano: msToNanoString(s.timestampMs), asDouble: s.value, attributes: labelsToAttributes(s.labels), })), }, }); } return { resourceMetrics: [ { resource: { attributes: [ { key: "service.name", value: { stringValue: "openclaw" } }, { key: "service.namespace", value: { stringValue: "grafana-lens" } }, ], }, scopeMetrics: [ { scope: { name: "grafana-lens-custom" }, metrics, }, ], }, ], }; } } // ── Helpers ────────────────────────────────────────────────────────── /** Convert millisecond timestamp to nanosecond string (avoids Number overflow). */ export function msToNanoString(ms) { return `${ms}000000`; } export function labelsToAttributes(labels) { return Object.entries(labels).map(([key, val]) => ({ key, value: { stringValue: val }, })); }