Skip to content

Observability Troubleshooting

Operational runbook for diagnosing and resolving observability issues.


T1. Metric is missing or shows zero

T1.1 Prometheus endpoint returns 404

Check: Is the prometheus Cargo feature enabled?

bash
# Verify the binary was built with prometheus support
strings /path/to/dcc-mcp-server | grep prometheus
# No output → feature not compiled in

# Check the log for prometheus initialization
grep -i prometheus /var/log/dcc-mcp/gateway.log

Fix: Rebuild with --features prometheus, or use a wheel that includes it:

bash
maturin develop --features python-bindings,ext-module,workflow,prometheus

Also check: Did you set enable_prometheus=True in McpHttpConfig?

python
cfg = McpHttpConfig(
    port=8765,
    server_name="maya-mcp",
    enable_prometheus=True,  # required
)

T1.2 dcc_mcp_build_info is the only metric visible

The exporter publishes dcc_mcp_build_info on startup as a heartbeat to confirm the endpoint is live. If only this metric appears:

  • No tool calls have been dispatched yet — metrics are created lazily on first observation.
  • Call a tool and scrape again.

T1.3 ObservabilityQuery returns all zeroes

Check: Does the SQLite database exist?

bash
ls -la /tmp/dcc-mcp-registry/gateway_admin.sqlite
# or
echo $DCC_MCP_GATEWAY_ADMIN_DB

Check: Has the gateway ever run? The SQLite file is created on the first gateway startup. If it does not exist, no data has been collected.

python
from dcc_mcp_core.admin_sqlite_lane import resolve_admin_db_path
db_path = resolve_admin_db_path()
print("DB exists:", db_path.exists())

Fix: Start the gateway process and make some tool calls, then retry the query.

T1.4 Query returns zeroes but database has rows

Verify the ObservabilityQuery is pointed at the correct database path:

python
from dcc_mcp_core import ObservabilityQuery
q = ObservabilityQuery(db_path="/actual/path/gateway_admin.sqlite")

The default constructor with db_path=None and no read_json_fn returns empty results. Either provide db_path (for direct SQLite access) or a read_json_fn callback.


T2. OTLP traces not appearing

T2.1 Spans not reaching the collector

Check: Is OTEL_EXPORTER_OTLP_ENDPOINT set?

bash
echo $OTEL_EXPORTER_OTLP_ENDPOINT
# Must be set — OTLP is opt-in by environment variable

Check: Is the collector reachable?

bash
# Test gRPC connectivity (requires grpcurl)
grpcurl -plaintext localhost:4317 grpc.health.v1.Health/Check

# Test HTTP collector
curl -s http://localhost:4318/health

Check: Gateway logs for OTLP errors:

bash
grep -i otlp /var/log/dcc-mcp/gateway.log | tail -20

T2.2 Spans reaching collector but not visible in Jaeger/Tempo

Check: Does the collector's service pipeline include an exporter?

yaml
# otel-collector.yaml
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [otlphttp/jaeger]   # ← must be present

Check: Is the backend's query port accessible?

ToolUI port
Jaeger16686
Tempo3200 (also 4317 for OTLP)
Phoenix6006

T2.3 Only some DCC instances produce traces

Check: Per-DCC environment:

bash
# Each DCC process needs its own OTEL_* vars
# Gateway passes through OTEL environment to child processes
# But some DCCs may strip environment. Verify:
cat /proc/<dcc-pid>/environ | tr '\0' '\n' | grep OTEL

T3. Admin UI issues

T3.1 GET /admin returns 404

Check: Is the gateway process running?

bash
ps aux | grep dcc-mcp-server

Check: What port is the gateway on?

bash
# Default port is 0 (OS-assigned). Check the gateway log:
grep "listening on" /var/log/dcc-mcp/gateway.log

Fix: The admin UI is served by the elected gateway, not by per-DCC servers. Connect to the gateway's port, not a DCC server's port.

T3.2 Admin tables are empty ("No data")

Check: Audit ring buffer capacity:

bash
# Default is 5000 rows. Check if the ring has ever been populated:
curl -s http://localhost:8765/admin/api/calls | python -c "import sys,json; print(len(json.load(sys.stdin)))"

Check: Has DCC_MCP_GATEWAY_AUDIT_DIR been set? If not, data is in-memory only and resets on gateway restart.

Check: Has traffic actually passed through the gateway?

bash
# Verify the registry has backends registered
curl -s http://localhost:8765/admin/api/workers

T3.3 Stats show 0 calls despite activity

The stats endpoint computes from the trace ring buffer (default 200 traces). If your traffic exceeds 200 requests, older entries are evicted. Increase the trace ring capacity:

bash
DCC_MCP_TRACE_LOG_CAPACITY=2000 dcc-mcp-server

Or use the SQLite-backed path (admin-persist-sqlite feature) for access to all historical data.


T4. ToolRecorder (Python) issues

T4.1 metrics() returns None

python
metrics = recorder.metrics("create_sphere")
if metrics is None:
    print("This tool has never been recorded.")

Fix: Ensure recorder.start("create_sphere", "maya") was called and guard.finish() was invoked. The metric is only registered after a completed recording cycle.

T4.2 Percentiles are always 0.0

Percentiles require a minimum sample count:

  • p95 stabilises after ~20 samples.
  • p99 stabilises after ~100 samples.

Below these thresholds, percentiles may default to 0.0 or match the maximum observed value, depending on the internal histogram state.

T4.3 p95_duration_ms equals avg_duration_ms

This happens when the sample count is too low for meaningful percentile computation (< 20 samples). Increase the sample size or use avg_duration_ms instead.


T5. Schema / database issues

T5.1 Cannot open gateway_admin.sqlite

Check: File permissions:

bash
ls -la /tmp/dcc-mcp-registry/gateway_admin.sqlite
# Must be readable by the user running the agent/CLI

Check: WAL journal mode — the file may have -wal and -shm companions:

bash
ls -la /tmp/dcc-mcp-registry/gateway_admin.sqlite*

All three files must be present and accessible for read operations.

Check: Is another process holding a write lock?

bash
# On Linux/Mac:
lsof /tmp/dcc-mcp-registry/gateway_admin.sqlite

T5.2 no such table: tool_calls

The database was created by an older gateway version (schema v1). The DDL is CREATE TABLE IF NOT EXISTS, so restarting the gateway creates missing tables automatically.

bash
# Verify current schema version
sqlite3 /tmp/dcc-mcp-registry/gateway_admin.sqlite ".tables"
# Should show: tool_calls, sessions, session_events, ...

Fix: Restart the gateway to trigger the DDL bootstrap.

T5.3 Database is growing too large

The SQLite lane is append-only. If the database grows beyond acceptable bounds, consider:

  1. Adding a cleanup job:

    sql
    DELETE FROM tool_calls WHERE started_at_ms < strftime('%s', 'now', '-30 days') * 1000;
    DELETE FROM sessions WHERE started_at_ms < strftime('%s', 'now', '-30 days') * 1000;
    DELETE FROM session_events WHERE created_at_ms < strftime('%s', 'now', '-30 days') * 1000;
  2. Running VACUUM after large deletes:

    sql
    VACUUM;
  3. Building the gateway without admin-persist-sqlite to use only the in-memory ring buffer (data resets on restart).


T6. Common configuration mistakes

SymptomLikely cause
Prometheus endpoint 404prometheus feature not compiled or enable_prometheus=False
OTLP traces not appearingOTEL_EXPORTER_OTLP_ENDPOINT not set
ObservabilityQuery returns zerosWrong db_path or gateway has never run
Admin UI "No data"DCC_MCP_GATEWAY_AUDIT_DIR not set (in-memory only)
Stats inaccurateTrace ring capacity too small (default 200)
SQLite not persistingadmin-persist-sqlite feature not compiled in
ToolRecorder percentiles zeroFewer than 20 samples
skill_paths_custom reads failFile exists but table missing; first gateway startup hasn't finished DDL

Quick diagnostic script

python
"""Run this on a machine to verify observability is working."""
import os, sys
from pathlib import Path

# 1. Check Prometheus
port = os.environ.get("DCC_MCP_HTTP_PORT", "8765")
import urllib.request
try:
    resp = urllib.request.urlopen(f"http://localhost:{port}/metrics")
    print(f"[OK] Prometheus endpoint reachable on port {port}")
except Exception as e:
    print(f"[WARN] Prometheus endpoint: {e}")

# 2. Check OTLP
if "OTEL_EXPORTER_OTLP_ENDPOINT" in os.environ:
    print(f"[OK] OTLP configured: {os.environ['OTEL_EXPORTER_OTLP_ENDPOINT']}")
else:
    print("[INFO] OTLP not configured (optional)")

# 3. Check SQLite
from dcc_mcp_core.admin_sqlite_lane import resolve_admin_db_path
db = resolve_admin_db_path()
if db.exists():
    print(f"[OK] Admin SQLite database exists: {db}")
else:
    print(f"[WARN] Admin SQLite database not found at {db}")

# 4. Check gateway admin UI
try:
    resp = urllib.request.urlopen(f"http://localhost:{port}/admin/api/calls")
    print(f"[OK] Gateway admin API reachable")
except Exception as e:
    print(f"[WARN] Gateway admin API: {e}")

Released under the MIT License.