RFC: Metrics Dashboard Simulator and Development Instance
Summary
Establish a standardized simulator and development environment for testing Prometheus metrics and Grafana dashboards before production deployment. This enables safe validation of dashboard performance, alert thresholds, and capacity planning under realistic load conditions.
Motivation
Currently, new metrics and dashboards are tested directly in production, leading to:
Dashboard performance issues discovered after deployment
Incorrect alert thresholds causing false positives/negatives
Cardinality explosions impacting Prometheus performance
No way to validate behavior under extreme load conditions
Difficulty reproducing production issues in development
Proposal
1. Metrics Simulator Framework
Create a Python-based simulator that generates realistic metric patterns for any Prometheus-instrumented service.
# Core simulator interface
class MetricsSimulator :
def __init__ (self , config ):
self .users = config ['users' ]
self .activity_model = config ['activity_model' ]
self .metrics_registry = config ['metrics' ]
def generate_activity (self ):
# Brownian motion with mean reversion
# Configurable burst patterns
# Seasonal variations (business hours, weekends)
Key Features:
Configurable user populations : Power users, regular users, idle users
Activity models : Brownian motion, Poisson processes, replay from production
Fault injection : Simulate outages, thundering herds, cardinality bombs
Time acceleration : Simulate 30 days of metrics in 1 hour
2. Development Instance Architecture
# docker-compose.dev.yml
version : ' 3.8'
services :
# Isolated Prometheus for testing
prometheus-dev :
image : prom/prometheus:latest
ports :
- " 9091:9090"
command :
- ' --storage.tsdb.retention.time=7d'
- ' --storage.tsdb.retention.size=10GB'
- ' --web.enable-lifecycle' # Allow config reloads
volumes :
- ./prometheus-dev.yml:/etc/prometheus/prometheus.yml
- prometheus-dev-data:/prometheus
# Isolated Grafana for dashboard development
grafana-dev :
image : grafana/grafana:latest
ports :
- " 3001:3000"
environment :
- GF_DEFAULT_INSTANCE_NAME=dev
- GF_USERS_DEFAULT_THEME=light # Distinguish from prod
- GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/etc/grafana/home.json
volumes :
- ./grafana-provisioning-dev:/etc/grafana/provisioning
- grafana-dev-data:/var/lib/grafana
# Metrics simulator
simulator :
build : ./simulator
environment :
- SCENARIO=${SCENARIO:-baseline}
- USERS=${USERS:-100}
- DURATION=${DURATION:-3600}
volumes :
- ./scenarios:/app/scenarios
3. Testing Scenarios
Baseline Scenario
100 users with typical activity patterns
Normal distribution of requests
Standard error rates (0.1%)
Load Test Scenarios
scenarios :
black_friday :
users : 1000
activity_multiplier : 10
duration : 4h
pattern : " spike"
gradual_rollout :
users : [10, 50, 100, 500, 1000]
ramp_time : 30m
pattern : " linear"
disaster_recovery :
users : 100
failures :
- time : 10m
type : " total_outage"
duration : 5m
- time : 30m
type : " cascade_failure"
affected_percentage : 50
4. Validation Framework
Performance Benchmarks
benchmarks :
dashboard_load :
target : < 2s
panels : all
time_range : 24h
query_performance :
simple_counter : < 100ms
rate_calculation : < 500ms
histogram_quantile : < 1s
cardinality_limits :
total_series : < 1M
per_metric : < 10K
label_combinations : < 100K
Automated Testing
def test_dashboard_performance ():
# Start simulator with high load
simulator .start_scenario ("black_friday" )
# Wait for metrics to accumulate
time .sleep (300 )
# Test dashboard load time
load_time = grafana .measure_dashboard_load ("claude-metrics-v2" )
assert load_time < 2.0 , f"Dashboard load too slow: { load_time } s"
# Test query performance
for panel in dashboard .panels :
query_time = prometheus .measure_query (panel .query )
assert query_time < panel .sla , f"Query too slow: { panel .id } "
5. Development Workflow
graph LR
A[New Metric/Dashboard] --> B[Update Simulator]
B --> C[Run Test Scenarios]
C --> D{Performance OK?}
D -->|No| E[Optimize Queries]
E --> C
D -->|Yes| F[Test Alerts]
F --> G[Document Thresholds]
G --> H[PR with Tests]
H --> I[Deploy to Staging]
I --> J[Shadow Prod Traffic]
J --> K[Production Release]
Loading
6. Simulator Configuration
# simulator-config.yml
metrics :
- name : otel_claude_code_session_count_total
type : counter
labels :
- user_id
generation :
rate : " 10 * activity_level"
- name : otel_claude_code_token_usage_tokens_total
type : counter
labels :
- user_id
- model
- type
generation :
rate : " 500 * activity_level"
distribution :
model :
claude-3.5-sonnet : 0.4
claude-3.7-opus : 0.35
claude-4.0-opus : 0.25
type :
input : 0.6
output : 0.3
cache : 0.1
activity_models :
brownian :
volatility : 0.3
mean_reversion : 0.1
dt : 0.1
seasonal :
business_hours_multiplier : 2.0
weekend_multiplier : 0.3
timezone : " America/New_York"
7. Benefits
For Development
Test dashboards with realistic data before production
Validate alert thresholds reduce false positives
Identify performance issues early
Reproduce production issues locally
For Operations
Capacity planning with load projections
Disaster recovery testing
Cardinality impact assessment
Query optimization opportunities
For Product
A/B test dashboard designs
Validate metric usefulness before instrumentation
Understand user behavior patterns
Cost estimation for different usage patterns
8. Implementation Plan
Phase 1: Core Simulator (Week 1-2)
Phase 2: Advanced Features (Week 3-4)
Phase 3: Automation (Week 5-6)
Phase 4: Documentation (Week 7)
9. Success Metrics
90% of dashboard issues caught before production
50% reduction in false positive alerts
80% of developers using simulator for testing
0 cardinality-related outages
10. Alternatives Considered
Production Sampling
Pros : Real data
Cons : Can't test edge cases, privacy concerns
Synthetic Monitoring
Pros : Continuous validation
Cons : Limited scenarios, can't test pre-production
Chaos Engineering
Pros : Real failure testing
Cons : Risk to production, limited to failures
11. Open Questions
Should simulator be a separate service or library?
How to handle multi-tenant scenarios?
Integration with existing load testing tools?
Standardize across all teams or per-service?
How to share scenarios between teams?
12. Security Considerations
No production data in test instances
Separate networks for dev/prod
Access controls for test environments
Audit logging for configuration changes
13. References
Appendix A: Example Usage
# Start development environment
docker-compose -f docker-compose.dev.yml up -d
# Run baseline scenario
./simulator.py --scenario baseline --users 100
# Test black friday load
./simulator.py --scenario black_friday --duration 4h
# Validate dashboard performance
./test_dashboard.py --dashboard claude-metrics-v2 --scenario high_load
# Generate capacity planning report
./simulator.py --scenario growth_projection --report capacity.html
Appendix B: Scenario Template
# scenarios/template.yml
name : " Scenario Name"
description : " What this scenario tests"
users :
total : 100
distribution :
power_users :
percentage : 10
activity_base : 2.5
regular_users :
percentage : 70
activity_base : 1.0
idle_users :
percentage : 20
activity_base : 0.2
timeline :
- time : 0
event : " start"
- time : 10m
event : " increase_load"
multiplier : 2.0
- time : 30m
event : " inject_failure"
type : " model_outage"
affected : ["claude-4.0-opus"]
- time : 45m
event : " recovery"
assertions :
- metric : " error_rate"
condition : " < 0.05"
- metric : " p99_latency"
condition : " < 1000ms"
- metric : " dashboard_load_time"
condition : " < 2s"
Appendix C: Complete Implementation Example
#!/usr/bin/env python3
"""
Complete simulator implementation for Claude Code metrics
"""
import time
import random
import numpy as np
from prometheus_client import Counter , Histogram , Gauge , start_http_server
from threading import Thread
from dataclasses import dataclass
from typing import List , Dict , Any
import yaml
import logging
@dataclass
class User :
id : str
activity_level : float
base_activity : float
volatility : float
user_type : str # "power", "regular", "idle"
class ActivityModel :
"""Brownian motion with mean reversion for realistic activity patterns"""
def update (self , user : User , dt : float = 0.1 ) -> float :
# Mean reversion strength
theta = 0.1
# Random shock
shock = np .random .normal (0 , user .volatility * np .sqrt (dt ))
# Update with mean reversion
drift = theta * (user .base_activity - user .activity_level ) * dt
user .activity_level += drift + shock
# Bounds
user .activity_level = max (0.1 , min (5.0 , user .activity_level ))
return user .activity_level
class MetricsGenerator :
"""Generate Prometheus metrics based on user activity"""
def __init__ (self , config : Dict [str , Any ]):
self .config = config
self ._setup_metrics ()
def _setup_metrics (self ):
self .session_counter = Counter (
'otel_claude_code_session_count_total' ,
'Total sessions' ,
['user_id' ]
)
self .token_counter = Counter (
'otel_claude_code_token_usage_tokens_total' ,
'Total tokens used' ,
['user_id' , 'model' , 'type' ]
)
self .cost_counter = Counter (
'otel_claude_code_cost_usage_USD_total' ,
'Total cost in USD' ,
['user_id' , 'model' ]
)
self .commit_counter = Counter (
'otel_claude_code_commit_count_total' ,
'Total commits' ,
['user_id' ]
)
def generate_session (self , user : User ):
"""Generate metrics for a user session"""
if random .random () > user .activity_level / 5.0 :
return
# Start session
self .session_counter .labels (user_id = user .id ).inc ()
# Choose model based on configuration
model_weights = self .config ['metrics' ]['model_distribution' ]
model = random .choices (
list (model_weights .keys ()),
weights = list (model_weights .values ())
)[0 ]
# Generate tokens
base_tokens = int (500 * user .activity_level * random .uniform (0.5 , 2.0 ))
# Input tokens
input_tokens = int (base_tokens * random .uniform (0.8 , 1.2 ))
self .token_counter .labels (
user_id = user .id ,
model = model ,
type = 'input'
).inc (input_tokens )
# Output tokens
output_tokens = int (base_tokens * 0.6 * random .uniform (0.5 , 1.0 ))
self .token_counter .labels (
user_id = user .id ,
model = model ,
type = 'output'
).inc (output_tokens )
# Calculate cost
costs = self .config ['metrics' ]['costs_per_1k_tokens' ][model ]
total_cost = (input_tokens / 1000 ) * costs ['input' ] + \
(output_tokens / 1000 ) * costs ['output' ]
self .cost_counter .labels (
user_id = user .id ,
model = model
).inc (total_cost )
# Random commits
if random .random () < 0.2 * user .activity_level :
commits = random .randint (1 , 3 )
self .commit_counter .labels (user_id = user .id ).inc (commits )
class Simulator :
"""Main simulator orchestrating user activities"""
def __init__ (self , config_file : str ):
with open (config_file , 'r' ) as f :
self .config = yaml .safe_load (f )
self .users : List [User ] = []
self .activity_model = ActivityModel ()
self .metrics_generator = MetricsGenerator (self .config )
self .running = True
self ._setup_logging ()
self ._create_users ()
def _setup_logging (self ):
logging .basicConfig (
level = logging .INFO ,
format = '%(asctime)s - %(levelname)s - %(message)s'
)
self .logger = logging .getLogger (__name__ )
def _create_users (self ):
"""Create user population based on configuration"""
user_config = self .config ['users' ]
total_users = user_config ['total' ]
for i in range (total_users ):
# Determine user type
rand = random .random ()
if rand < user_config ['distribution' ]['power_users' ]['percentage' ]:
user_type = 'power'
base = random .uniform (2.0 , 3.0 )
volatility = 0.2
elif rand < (user_config ['distribution' ]['power_users' ]['percentage' ] +
user_config ['distribution' ]['regular_users' ]['percentage' ]):
user_type = 'regular'
base = random .uniform (0.8 , 1.5 )
volatility = 0.3
else :
user_type = 'idle'
base = random .uniform (0.2 , 0.5 )
volatility = 0.4
user = User (
id = f"user_{ i :03d} " ,
activity_level = base ,
base_activity = base ,
volatility = volatility ,
user_type = user_type
)
self .users .append (user )
self .logger .info (f"Created { len (self .users )} users" )
def run_scenario (self , scenario_name : str ):
"""Run a specific test scenario"""
scenario = self .config ['scenarios' ][scenario_name ]
self .logger .info (f"Running scenario: { scenario_name } " )
start_time = time .time ()
tick = 0
while self .running :
current_time = time .time () - start_time
# Check timeline events
for event in scenario .get ('timeline' , []):
if abs (current_time - self ._parse_time (event ['time' ])) < 0.1 :
self ._handle_event (event )
# Update activities every 10 ticks
if tick % 10 == 0 :
for user in self .users :
self .activity_model .update (user )
# Generate metrics
active_count = int (len (self .users ) * 0.3 )
active_users = random .sample (self .users , active_count )
for user in active_users :
self .metrics_generator .generate_session (user )
time .sleep (0.1 )
tick += 1
# Status update
if tick % 100 == 0 :
avg_activity = np .mean ([u .activity_level for u in self .users ])
self .logger .info (f"Time: { current_time :.1f} s, Avg activity: { avg_activity :.2f} " )
def _parse_time (self , time_str : str ) -> float :
"""Parse time string like '10m' or '2h' to seconds"""
if time_str .endswith ('m' ):
return float (time_str [:- 1 ]) * 60
elif time_str .endswith ('h' ):
return float (time_str [:- 1 ]) * 3600
else :
return float (time_str )
def _handle_event (self , event : Dict [str , Any ]):
"""Handle scenario events"""
self .logger .info (f"Event: { event ['event' ]} " )
if event ['event' ] == 'increase_load' :
multiplier = event .get ('multiplier' , 2.0 )
for user in self .users :
user .activity_level *= multiplier
elif event ['event' ] == 'inject_failure' :
failure_type = event .get ('type' , 'random' )
if failure_type == 'model_outage' :
# Simulate model outage by setting activity to 0 for some users
affected_count = int (len (self .users ) * 0.3 )
for user in random .sample (self .users , affected_count ):
user .activity_level = 0
def start (self ):
"""Start the simulator and metrics server"""
# Start Prometheus metrics server
port = self .config .get ('metrics_port' , 8000 )
start_http_server (port )
self .logger .info (f"Metrics server started on port { port } " )
# Run default scenario
scenario = self .config .get ('default_scenario' , 'baseline' )
self .run_scenario (scenario )
if __name__ == "__main__" :
import sys
config_file = sys .argv [1 ] if len (sys .argv ) > 1 else "simulator-config.yml"
simulator = Simulator (config_file )
try :
simulator .start ()
except KeyboardInterrupt :
simulator .running = False
logging .info ("Simulator stopped" )
RFC: Metrics Dashboard Simulator and Development Instance
Summary
Establish a standardized simulator and development environment for testing Prometheus metrics and Grafana dashboards before production deployment. This enables safe validation of dashboard performance, alert thresholds, and capacity planning under realistic load conditions.
Motivation
Currently, new metrics and dashboards are tested directly in production, leading to:
Proposal
1. Metrics Simulator Framework
Create a Python-based simulator that generates realistic metric patterns for any Prometheus-instrumented service.
Key Features:
2. Development Instance Architecture
3. Testing Scenarios
Baseline Scenario
Load Test Scenarios
4. Validation Framework
Performance Benchmarks
Automated Testing
5. Development Workflow
graph LR A[New Metric/Dashboard] --> B[Update Simulator] B --> C[Run Test Scenarios] C --> D{Performance OK?} D -->|No| E[Optimize Queries] E --> C D -->|Yes| F[Test Alerts] F --> G[Document Thresholds] G --> H[PR with Tests] H --> I[Deploy to Staging] I --> J[Shadow Prod Traffic] J --> K[Production Release]6. Simulator Configuration
7. Benefits
For Development
For Operations
For Product
8. Implementation Plan
Phase 1: Core Simulator (Week 1-2)
Phase 2: Advanced Features (Week 3-4)
Phase 3: Automation (Week 5-6)
Phase 4: Documentation (Week 7)
9. Success Metrics
10. Alternatives Considered
Production Sampling
Synthetic Monitoring
Chaos Engineering
11. Open Questions
12. Security Considerations
13. References
Appendix A: Example Usage
Appendix B: Scenario Template
Appendix C: Complete Implementation Example