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
577 lines (403 loc) • 18.8 kB
Markdown
# SRE Investigation Patterns
Structured investigation methodology for the Grafana LGTM stack, mapped to Grafana Lens tools.
Use this as a playbook when the user asks to investigate, debug, triage, or write a postmortem.
## 1. Five-Phase Investigation Methodology
### Phase 1: Scope
Understand the symptom, timeline, and blast radius before gathering data.
| Action | Tool | Example |
|--------|------|---------|
| Check active alerts | `grafana_check_alerts` (list) | See pending alerts with `suggestedInvestigation` |
| List recent events | `grafana_annotate` (list, from="now-6h") | Deployments, config changes, investigations |
| Check alert rules | `grafana_check_alerts` (list_rules, compact=true) | Which rules are firing/pending? |
| Quick security sweep | `grafana_security_check` | Parallel threat assessment |
**Output**: Symptom description, affected services, timeline bounds, recent changes.
### Phase 2: Gather Evidence (Statistics First)
**Critical discipline: always aggregate before sampling.** Run `count_over_time` before reading individual log entries. Use `grafana_explain_metric` before raw PromQL. Search traces before fetching individual ones.
| Signal | Statistics First | Then Sample |
|--------|-----------------|-------------|
| Metrics | `grafana_explain_metric` (trend + stats + anomaly) | `grafana_query` (specific breakdowns) |
| Logs | `count_over_time`, `sum by (level)`, `topk` patterns | Individual log entries (max 50) |
| Traces | `grafana_query_traces` (search with filters) | `grafana_query_traces` (get specific trace) |
### Phase 3: Form Hypotheses
Structure each hypothesis with supporting evidence and a specific tool call to test it.
```
H1: [Hypothesis statement]
Evidence: [What suggests this]
Test: [tool_name] with [specific params]
H2: [Alternative hypothesis]
Evidence: [What suggests this]
Test: [tool_name] with [specific params]
```
### Phase 4: Test Hypotheses
Execute the mapped tool calls. Track which hypotheses are confirmed vs ruled out with specific evidence.
### Phase 5: Conclude
- Root cause + evidence chain + confidence level
- `grafana_annotate` findings on relevant dashboards
- `grafana_check_alerts` acknowledge investigated alerts
- Present using the Evidence Presentation Format (Section 10)
## 2. RED Method (Rate / Errors / Duration)
Service-centric health assessment. Works for both OpenClaw agent metrics and generic HTTP services.
### OpenClaw Agent RED
| Signal | PromQL | Tool |
|--------|--------|------|
| **Rate** | `sum(rate(openclaw_lens_messages_processed_total[5m]))` | `grafana_query` |
| **Errors** | `sum(rate(openclaw_lens_messages_processed_total{outcome="error"}[5m])) / sum(rate(openclaw_lens_messages_processed_total[5m]))` | `grafana_query` |
| **Duration** | `histogram_quantile(0.95, sum(rate(gen_ai_client_operation_duration_seconds_bucket[5m])) by (le))` | `grafana_query` |
### Generic HTTP Service RED
| Signal | PromQL |
|--------|--------|
| **Rate** | `sum(rate(http_requests_total[5m]))` |
| **Errors** | `sum(rate(http_requests_total{code=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))` |
| **Duration** | `histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))` |
### RED by Dimension
Break down by `model`, `channel`, `tool`, or custom labels to isolate the source:
```promql
# Error rate by model
sum by (model) (rate(openclaw_lens_messages_processed_total{outcome="error"}[5m]))
/ sum by (model) (rate(openclaw_lens_messages_processed_total[5m]))
# Duration by tool
histogram_quantile(0.95, sum by (le, tool) (rate(openclaw_lens_tool_duration_ms_bucket[5m])))
```
## 3. USE Method (Utilization / Saturation / Errors)
Resource-centric health assessment for infrastructure and agent resources.
### OpenClaw Agent USE
| Signal | PromQL | What It Means |
|--------|--------|---------------|
| **Utilization** — context window | `openclaw_lens_context_tokens{type="used"} / openclaw_lens_context_tokens{type="limit"} * 100` | % of context window consumed |
| **Utilization** — cache efficiency | `openclaw_lens_cache_read_ratio` | Cache hit ratio (higher is better) |
| **Saturation** — queue depth | `openclaw_lens_queue_depth` | Messages waiting to be processed |
| **Saturation** — queue lane depth | `openclaw_lens_queue_lane_depth` | Per-lane queue depth |
| **Saturation** — queue wait time | `histogram_quantile(0.95, sum(rate(openclaw_lens_queue_wait_ms_bucket[5m])) by (le))` | p95 wait time in queue |
| **Errors** — stuck sessions | `openclaw_lens_sessions_stuck` | Sessions unable to make progress |
| **Errors** — tool loops | `sum(openclaw_lens_tool_loops_active)` | Tools in infinite loops |
| **Errors** — tool error classes | `sum by (error_class) (rate(openclaw_lens_tool_error_classes_total[5m]))` | Error rate by category |
### Generic Node USE (node-exporter)
| Signal | PromQL |
|--------|--------|
| **Utilization** — CPU | `100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)` |
| **Utilization** — Memory | `(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100` |
| **Saturation** — Disk I/O | `rate(node_disk_io_time_seconds_total[5m])` |
| **Saturation** — Network | `rate(node_network_receive_bytes_total[5m]) + rate(node_network_transmit_bytes_total[5m])` |
| **Errors** — Disk | `rate(node_disk_io_time_weighted_seconds_total[5m])` |
## 4. Anomaly Detection via PromQL
### Z-Score (σ-Based Anomaly Scoring)
Compare a metric's current value against its historical baseline:
```promql
# Z-score: how many standard deviations from the 7-day mean?
(avg_over_time(METRIC[1h]) - avg_over_time(METRIC[7d]))
/ (stddev_over_time(METRIC[7d]) + 1e-10)
```
**Severity mapping**:
| Z-Score (σ) | Severity | Interpretation |
|-------------|----------|----------------|
| < 1.5 | Normal | Within expected variance |
| 1.5 – 2.0 | Mild | Slightly unusual, may be noise |
| 2.0 – 3.0 | Significant | Likely a real change — investigate |
| > 3.0 | Critical | Extremely unlikely to be noise |
The `grafana_explain_metric` tool computes this automatically for 24h period queries and returns it in the `anomaly` field.
### Seasonality Comparison
Compare with the same time yesterday and last week:
```promql
# Value at same time yesterday
METRIC offset 1d
# Value at same time last week
METRIC offset 7d
# Percent change vs yesterday
(METRIC - METRIC offset 1d) / (METRIC offset 1d + 1e-10) * 100
# Percent change vs last week
(METRIC - METRIC offset 7d) / (METRIC offset 7d + 1e-10) * 100
```
The `grafana_explain_metric` tool computes this automatically for 24h period queries and returns it in the `seasonality` field.
### Predictive and Rate-of-Change
```promql
# Where is this metric heading? (linear prediction 1h ahead)
predict_linear(METRIC[6h], 3600)
# How fast is it changing right now?
deriv(METRIC[15m])
# Is it flapping? (number of value changes in the last hour)
changes(METRIC[1h])
```
## 5. SLI/SLO Burn Rate
Multi-window burn rate alerting for availability and latency SLIs.
### Availability SLI (Error Rate)
```promql
# Error budget consumption rate (14.4x = 100% budget in 1h)
(
sum(rate(openclaw_lens_messages_processed_total{outcome="error"}[1h]))
/ sum(rate(openclaw_lens_messages_processed_total[1h]))
) / (1 - 0.999) # SLO = 99.9%
```
### Multi-Window Burn Rate
| Window | Fast Burn (14.4x) | Slow Burn (1x) |
|--------|-------------------|----------------|
| 1h / 5m | Exhausts budget in ~1h | — |
| 6h / 30m | Exhausts budget in ~3d | — |
| 1d / 2h | — | Exhausts budget in ~30d |
```promql
# Fast burn: 2% of monthly error budget consumed in 1 hour
(
sum(rate(openclaw_lens_messages_processed_total{outcome="error"}[1h]))
/ sum(rate(openclaw_lens_messages_processed_total[1h]))
) > (14.4 * (1 - 0.999))
and
(
sum(rate(openclaw_lens_messages_processed_total{outcome="error"}[5m]))
/ sum(rate(openclaw_lens_messages_processed_total[5m]))
) > (14.4 * (1 - 0.999))
```
### Latency SLI
```promql
# What percentage of LLM calls exceed 10s? (latency SLI)
1 - (
sum(rate(gen_ai_client_operation_duration_seconds_bucket{le="10"}[5m]))
/ sum(rate(gen_ai_client_operation_duration_seconds_count[5m]))
)
```
## 6. LogQL Investigation Patterns
**Statistics-first discipline**: Always run aggregation queries before reading individual log entries.
### Step 1: Volume and Error Rate
```logql
# Total log volume over time
sum(count_over_time({service_name="openclaw"}[5m]))
# Error rate over time
sum(rate({service_name="openclaw"} | logfmt | level="ERROR" [5m]))
```
### Step 2: Severity Breakdown
```logql
# Log count by severity level
sum by (level) (count_over_time({service_name="openclaw"} | json [1h]))
```
### Step 3: Top Error Patterns
```logql
# Top error sources by service
topk(10, sum by (service_name) (rate({level="error"} | logfmt [5m])))
# Top error event types
topk(10, sum by (event_name) (count_over_time({service_name="openclaw"} | json | level="ERROR" [1h])))
```
### Step 4: Sample Investigation
Only AFTER identifying patterns from steps 1-3. Max 50 entries.
```logql
# Sample error entries from the incident window
{service_name="openclaw"} | json | level="ERROR" | line_format "{{.event_name}}: {{.body}}"
```
Use `grafana_query_logs` with `start`/`end` params to narrow to the incident window.
### Step 5: Temporal Correlation
Narrow to the exact incident window using `start` and `end` parameters from Phase 1 (Scope).
### Step 6: Log-to-Trace Correlation
```logql
# Find error logs that have trace correlation
{service_name="openclaw"} | json | trace_id != "" | level="ERROR"
```
Extract `trace_id` values → use `grafana_query_traces` with `queryType: "get"` to inspect the full trace.
### Step 7: Component Filtering
OpenClaw logs have a `component` attribute for filtering:
| Component | What It Contains |
|-----------|-----------------|
| `lifecycle` | Session/LLM/tool/subagent lifecycle events |
| `diagnostic` | Diagnostic event processing (metric collector) |
| `app` | Application logs (tslog output forwarded via registerLogTransport) |
```logql
# Only lifecycle events (most useful for SRE investigation)
{service_name="openclaw"} | json | component="lifecycle"
# Only application logs (plugin code output)
{service_name="openclaw"} | json | component="app"
```
## 7. TraceQL Investigation Patterns
### Finding Problematic Traces
```traceql
# Slow spans (>10s)
{ resource.service.name = "openclaw" && duration > 10s }
# Error spans
{ resource.service.name = "openclaw" && status = error }
# Slow LLM calls specifically
{ resource.service.name = "openclaw" && name =~ "chat.*" && duration > 10s }
# Failed tool executions
{ resource.service.name = "openclaw" && span.gen_ai.operation.name = "execute_tool" && status = error }
```
### Structural Operators (Error Chains)
```traceql
# Find traces where a frontend span has a downstream error
{ resource.service.name = "frontend" } >> { status = error }
# Find traces where an LLM call led to a tool error
{ name =~ "chat.*" } >> { span.gen_ai.operation.name = "execute_tool" && status = error }
```
### Duration Aggregates
```traceql
# Traces with average span duration > 20ms
{ } | avg(span:duration) > 20ms
# Traces with at least one span > 1s
{ } | max(span:duration) > 1s
```
### Trace-to-Log Correlation
After fetching a trace with `grafana_query_traces` (queryType: "get"), the response includes `correlationHint.logQuery` — a ready-to-use LogQL query for correlated logs. Pass it to `grafana_query_logs`.
## 8. Investigation Workflow Recipes
### a) Latency Investigation
```
Symptom: "LLM calls are slow" / "agent is sluggish"
1. grafana_explain_metric
expr: "gen_ai_client_operation_duration_seconds"
period: "24h"
→ Get trend, anomaly score, seasonality
2. grafana_query
expr: "histogram_quantile(0.95, sum by (le, gen_ai_request_model) (rate(gen_ai_client_operation_duration_seconds_bucket[5m])))"
→ Break down latency by model
3. grafana_query_logs
expr: '{service_name="openclaw"} | json | component="lifecycle" |= "llm.output" | openclaw_duration_s > 10'
→ Find specific slow LLM calls (stats first — count_over_time, then samples)
4. grafana_query_traces
query: '{ resource.service.name = "openclaw" && name =~ "chat.*" && duration > 10s }'
→ Inspect slow trace span hierarchy
5. grafana_annotate
text: "Investigation: slow LLM calls — [root cause]", tags: ["investigation"]
```
### b) Error Spike Investigation
```
Symptom: Alert firing / "errors are up"
1. grafana_check_alerts (list)
→ Get alert details + suggestedInvestigation
2. grafana_query
expr: "sum by (outcome) (rate(openclaw_lens_messages_processed_total[5m]))"
→ Confirm error rate and see if it's rising
3. grafana_query_logs [STATISTICS FIRST]
expr: 'sum by (level) (count_over_time({service_name="openclaw"} | json [1h]))'
→ Severity breakdown — how many errors vs warnings?
expr: 'topk(10, sum by (event_name) (count_over_time({service_name="openclaw"} | json | level="ERROR" [1h])))'
→ Top error patterns
expr: '{service_name="openclaw"} | json | level="ERROR"' (limit: 10)
→ Sample error entries AFTER understanding patterns
4. grafana_query_traces
query: '{ resource.service.name = "openclaw" && status = error }'
→ Error trace spans for detailed analysis
5. grafana_annotate
text: "Error spike investigation: [findings]", tags: ["investigation"]
```
### c) Change Detection (Before/After Deployment)
```
Symptom: "Did the deployment break anything?"
1. grafana_annotate (list, tags: ["deploy"], from: "now-24h")
→ Find deployment timestamps
2. grafana_query
expr: "rate(openclaw_lens_messages_processed_total{outcome='error'}[5m])"
+ use annotation timestamps for before/after windows
3. grafana_explain_metric
expr: "openclaw_lens_daily_cost_usd"
compareWith: "previous"
→ Period-over-period comparison
4. grafana_query_logs
expr: '{service_name="openclaw"} | json | component="lifecycle" | event_name=~"gateway.*"'
→ Infrastructure events around the deployment
```
### d) Anomaly Assessment
```
Symptom: "Is this metric normal?" / "Detect anomalies"
1. grafana_explain_metric
expr: "METRIC_NAME", period: "24h"
→ Automatic anomaly scoring (z-score) + seasonality (vs 1d/7d ago)
2. grafana_query (if anomaly.severity >= "significant")
expr: "(avg_over_time(METRIC[1h]) - avg_over_time(METRIC[7d])) / (stddev_over_time(METRIC[7d]) + 1e-10)"
→ Detailed z-score over time
3. grafana_query
expr: "METRIC offset 1d" and "METRIC offset 7d"
→ Compare with yesterday and last week
4. grafana_query
expr: "predict_linear(METRIC[6h], 3600)"
→ Where is this heading in the next hour?
```
### e) Cost Investigation
```
Symptom: "Why is my bill high?" / "Cost spike"
1. grafana_explain_metric
expr: "openclaw_lens_daily_cost_usd", period: "24h"
→ Trend + anomaly score + seasonality
2. grafana_query
expr: "sum by (model) (increase(openclaw_lens_cost_by_model_total[1d]))"
→ Cost attribution by model
3. grafana_query
expr: "sum by (token_type) (increase(openclaw_lens_cost_by_token_type[1d]))"
→ Cost attribution by token type (input vs output vs cache)
4. grafana_query
expr: "openclaw_lens_cache_token_ratio"
→ Cache efficiency — low ratio means more expensive uncached calls
5. grafana_query_logs
expr: '{service_name="openclaw"} | json | event_name="usage.session_summary" | openclaw_cost_total > 1'
→ Expensive sessions (>$1)
```
### f) Postmortem Generation
```
Symptom: "What happened?" / "Write a postmortem" / "Incident summary"
1. grafana_check_alerts (list)
→ Find resolved alerts with timeline
2. grafana_annotate (list, from="incident_start", to="incident_end")
→ Build event timeline
3. grafana_explain_metric
→ Key metrics with trend/stats for the incident period
4. grafana_query_logs [STATISTICS FIRST]
→ Error patterns, severity breakdown
5. grafana_query_traces
→ Error traces for root cause evidence
6. Synthesize into Postmortem Template (Section 9)
```
## 9. Postmortem Template
When asked to write a postmortem, incident summary, or "what happened?", use this blameless format:
```markdown
# Incident: [Brief Title]
**Duration**: [start_time] – [end_time] ([total_duration])
**Severity**: [P1/P2/P3/P4]
**Services affected**: [list]
## Timeline
| Time | Event | Source |
|------|-------|--------|
| HH:MM | [event description] | [tool used: grafana_check_alerts / grafana_annotate / grafana_query_logs] |
## Root Cause
[1-2 sentences describing root cause with specific evidence]
## Evidence Collected
### Metrics
- [metric_name]: [value/trend] (from grafana_explain_metric / grafana_query)
### Logs
- [pattern]: [count] occurrences (from grafana_query_logs count_over_time)
- [sample]: [relevant log line] (from grafana_query_logs)
### Traces
- [trace_id]: [summary of span hierarchy] (from grafana_query_traces)
## Hypotheses Tested
| # | Hypothesis | Status | Evidence |
|---|-----------|--------|----------|
| H1 | [hypothesis] | Confirmed / Ruled out | [evidence] |
| H2 | [hypothesis] | Confirmed / Ruled out | [evidence] |
## What Couldn't Be Determined
- [observability gap] — [reason] (e.g., "Auth failure rate unknown — openclaw auth middleware emits no telemetry")
## Action Items
### Immediate (done)
- [ ] [action taken during incident]
### Short-term (1-2 weeks)
- [ ] [follow-up action]
### Long-term (systemic)
- [ ] [systemic improvement]
```
## 10. Evidence Presentation Format
When presenting investigation findings, structure the output as:
### Sources Consulted
List each tool call with query and result summary:
- `grafana_explain_metric(openclaw_lens_daily_cost_usd)` → $7.50, up 45% vs yesterday, anomaly score 2.3σ (significant)
- `grafana_query(sum by (model) (...))` → claude-opus: $5.80, claude-sonnet: $1.70
- `grafana_query_logs(count_over_time ...)` → 47 errors in last hour, 80% from tool timeouts
### Hypotheses Tested
- **H1: Model cost increase** → Ruled out (same model distribution as last week)
- **H2: Cache degradation** → Confirmed (cache ratio dropped from 0.7 to 0.3 after deployment)
### What Was Ruled Out
- Model mix unchanged (grafana_query by model)
- No tool loop activity (grafana_security_check: green)
### Confidence Level
High / Medium / Low — with justification.
### Limitations
- Auth failure data unavailable (openclaw auth middleware emits no telemetry)
- Loki datasource not configured — log correlation unavailable
- Only 24h of metric history available — longer-term seasonality assessment not possible