Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Standardised patterns for collecting, storing, and querying application metrics. Codifies the project's existing database-backed approach (Supabase + PostgreSQL) and defines conventions for metric naming, aggregation, and display. Designed for Vercel/serverless — no Prometheus scraping required.
Description
Codifies database-backed metrics instrumentation for NodeJS-Starter-V1 using Supabase/PostgreSQL, covering standardised metric types (counters, gauges, histograms), naming conventions, time-series aggregation queries, and optional OpenTelemetry export for serverless-compatible observability.
When to Apply
Positive Triggers
Adding new metrics or KPIs to the application
Creating dashboard data sources or analytics endpoints
Instrumenting API routes, agent executions, or background jobs
Querying time-series metric data for trends and reports
Setting up alerting thresholds based on metric values
User mentions: "metrics", "KPI", "instrumentation", "analytics", "monitoring", "dashboard data"
Negative Triggers
Adding log statements to code (use structured-logging instead)
Designing dashboard UI components (use dashboard-patterns when available)
All three types are stored in PostgreSQL (no in-memory counters — they vanish between serverless invocations). Counters and histograms go to metrics_events; gauges go to metrics_gauges.
Naming Convention
All metric names follow the pattern:
{domain}_{entity}_{measurement}[_{unit}]
Segment
Examples
Rules
domain
agent, api, cron, auth
snake_case, matches module
entity
run, request, token, job
singular noun
measurement
total, duration, rate, size
what is being measured
unit (optional)
ms, bytes, usd, percent
SI or currency unit
Standard Metrics
Metric Name
Type
Labels
Description
agent_run_total
Counter
agent_type, status
Total agent executions
agent_run_duration_ms
Histogram
agent_type
Execution time
agent_run_active
Gauge
agent_type
Currently running agents
api_request_total
Counter
method, route, status_code
HTTP requests
api_request_duration_ms
Histogram
method, route
Request latency
llm_token_total
Counter
model, direction
Token usage (input/output)
llm_cost_usd
Counter
model
LLM API cost
cron_job_duration_ms
Histogram
job_name
Cron execution time
cron_job_total
Counter
job_name, status
Cron executions
auth_login_total
Counter
method, result
Login attempts
Backend Patterns
MetricsRegistry
A thin wrapper around the existing AgentMetrics pattern, extended with standard metric types. Three methods: increment() (counter), observe() (histogram), set_gauge() (gauge).
Use BaseHTTPMiddleware to record api_request_total (counter) and api_request_duration_ms (histogram) on every request. Label with method, route, status_code. Example:
Query metrics grouped by time period for trend charts. Fetch events from metrics_events, group by truncated timestamp (minute/hour/day), and compute count, sum, avg, min, max per bucket. Use PostgreSQL date_trunc via Supabase RPC for server-side efficiency, or bucket in Python for small datasets:
asyncdefget_metric_timeseries(
self, metric_name: str, bucket: str = "hour", since_hours: int = 24,
) -> list[dict[str, Any]]:
since = (datetime.now(UTC) - timedelta(hours=since_hours)).isoformat()
result = self.client.table("metrics_events").select("value, recorded_at").eq(
"metric_name", metric_name
).gte("recorded_at", since).order("recorded_at").execute()
buckets: dict[str, list[float]] = {}
for row in (result.data or []):
ts = datetime.fromisoformat(row["recorded_at"])
fmt = {"hour": "%Y-%m-%dT%H:00:00Z", "day": "%Y-%m-%dT00:00:00Z"}.get(bucket, "%Y-%m-%dT%H:%M:00Z")
buckets.setdefault(ts.strftime(fmt), []).append(row["value"])
return [{"timestamp": ts, "count": len(v), "sum": sum(v), "avg": sum(v) / len(v), "min": min(v), "max": max(v)} for ts, v in buckets.items()]
Percentile Calculation (Histogram Metrics)
For histogram metrics, compute p50/p90/p95/p99 using statistics.quantiles. Query metrics_events filtered by metric_name and time range, sort values, then calculate:
import statistics
asyncdefget_percentiles(self, metric_name: str, since_hours: int = 24) -> dict[str, float]:
since = (datetime.now(UTC) - timedelta(hours=since_hours)).isoformat()
result = self.client.table("metrics_events").select("value").eq(
"metric_name", metric_name
).gte("recorded_at", since).execute()
values = sorted(r["value"] for r in (result.data or []))
iflen(values) < 2:
return {"p50": 0, "p90": 0, "p95": 0, "p99": 0}
q = statistics.quantiles(values, n=100)
return {"p50": q[49], "p90": q[89], "p95": q[94], "p99": q[98]}
Summary Endpoint
Expose a /metrics/summary endpoint returning counters, gauges, and histogram percentiles:
Define MetricSummary TypeScript interface mirroring the backend response. Use the existing proxy route pattern (apps/web/app/api/analytics/) to forward requests. Poll at 30-second intervals (matching the existing analytics dashboard) or use Supabase Realtime for gauge updates. Display via the existing MetricTile component from status-command-centre/.
Database Schema
metrics_events Table (Counters + Histograms)
CREATE TABLE IF NOTEXISTS metrics_events (
id UUID PRIMARY KEYDEFAULT gen_random_uuid(),
metric_name TEXT NOT NULL,
metric_type TEXT NOT NULLCHECK (metric_type IN ('counter', 'histogram')),
valueDOUBLE PRECISIONNOT NULL,
labels JSONB NOT NULLDEFAULT'{}',
recorded_at TIMESTAMPTZ NOT NULLDEFAULT NOW()
);
CREATE INDEX idx_metrics_events_name_time ON metrics_events (metric_name, recorded_at DESC);
CREATE INDEX idx_metrics_events_labels ON metrics_events USING GIN (labels);
metrics_gauges Table
CREATE TABLE IF NOTEXISTS metrics_gauges (
metric_name TEXT NOT NULL,
label_key TEXT NOT NULLDEFAULT'',
labels JSONB NOT NULLDEFAULT'{}',
valueDOUBLE PRECISIONNOT NULL,
recorded_at TIMESTAMPTZ NOT NULLDEFAULT NOW(),
PRIMARY KEY (metric_name, label_key)
);
Retention Policy
Schedule a cron job (/api/cron/metrics-cleanup) to DELETE FROM metrics_events WHERE recorded_at < NOW() - INTERVAL '90 days'.
OpenTelemetry Bridge (Optional)
For production with full observability infrastructure (Grafana, Datadog), optionally export metrics to an OTel collector by creating MeterProvider instruments that mirror the database metric names. Only enable when OTEL_EXPORTER_OTLP_ENDPOINT is configured. The database-backed approach works standalone for Vercel/serverless.
Anti-Patterns
Anti-Pattern
Why It Fails
Correct Approach
In-memory counters on serverless
Lost between invocations, no persistence
Database-backed metrics
Prometheus scrape endpoint on Vercel
No persistent process to scrape
Database storage + query endpoints
Logging metrics as unstructured strings
Cannot aggregate or query
MetricsRegistry with typed methods
Recording per-request without labels
Cannot filter by route or status
Always include method, route, status_code labels
Querying raw events for dashboards
Slow on large datasets
Pre-aggregate via cron or use time-bucket queries
Unbounded metrics_events growth
Storage costs, slow queries
90-day retention cron job
Checklist for New Metrics
Definition
Metric name follows {domain}_{entity}_{measurement}[_{unit}] convention
Type chosen correctly: counter (cumulative), gauge (current), histogram (distribution)