|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +Mock metrics server that exposes Prometheus-format metrics for testing. |
| 4 | +
|
| 5 | +Usage: |
| 6 | + python mock_metrics_server.py [--port PORT] |
| 7 | +
|
| 8 | +The server exposes metrics at /metrics endpoint. |
| 9 | +""" |
| 10 | + |
| 11 | +import argparse |
| 12 | +import random |
| 13 | +import time |
| 14 | +from http.server import HTTPServer, BaseHTTPRequestHandler |
| 15 | +from threading import Thread |
| 16 | + |
| 17 | +# Simulated metric values |
| 18 | +start_time = time.time() |
| 19 | +request_count = 0 |
| 20 | +error_count = 0 |
| 21 | + |
| 22 | + |
| 23 | +class MetricsHandler(BaseHTTPRequestHandler): |
| 24 | + def do_GET(self): |
| 25 | + global request_count, error_count |
| 26 | + |
| 27 | + if self.path == "/metrics": |
| 28 | + request_count += 1 |
| 29 | + # Randomly increment error count |
| 30 | + if random.random() < 0.1: |
| 31 | + error_count += 1 |
| 32 | + |
| 33 | + metrics = self._generate_metrics() |
| 34 | + self.send_response(200) |
| 35 | + self.send_header("Content-Type", "text/plain; version=0.0.4") |
| 36 | + self.end_headers() |
| 37 | + self.wfile.write(metrics.encode("utf-8")) |
| 38 | + elif self.path == "/health": |
| 39 | + self.send_response(200) |
| 40 | + self.send_header("Content-Type", "text/plain") |
| 41 | + self.end_headers() |
| 42 | + self.wfile.write(b"OK") |
| 43 | + else: |
| 44 | + self.send_response(404) |
| 45 | + self.end_headers() |
| 46 | + |
| 47 | + def _generate_metrics(self): |
| 48 | + uptime = time.time() - start_time |
| 49 | + |
| 50 | + # Generate some realistic-looking metrics |
| 51 | + metrics = [] |
| 52 | + |
| 53 | + # Counter: total requests |
| 54 | + metrics.append("# HELP mock_requests_total Total number of requests received") |
| 55 | + metrics.append("# TYPE mock_requests_total counter") |
| 56 | + metrics.append(f"mock_requests_total {request_count}") |
| 57 | + |
| 58 | + # Counter: errors |
| 59 | + metrics.append("# HELP mock_errors_total Total number of errors") |
| 60 | + metrics.append("# TYPE mock_errors_total counter") |
| 61 | + metrics.append(f"mock_errors_total {error_count}") |
| 62 | + |
| 63 | + # Gauge: uptime |
| 64 | + metrics.append("# HELP mock_uptime_seconds Time since server started") |
| 65 | + metrics.append("# TYPE mock_uptime_seconds gauge") |
| 66 | + metrics.append(f"mock_uptime_seconds {uptime:.2f}") |
| 67 | + |
| 68 | + # Gauge: random CPU usage simulation |
| 69 | + metrics.append("# HELP mock_cpu_usage_percent Simulated CPU usage") |
| 70 | + metrics.append("# TYPE mock_cpu_usage_percent gauge") |
| 71 | + metrics.append(f"mock_cpu_usage_percent {random.uniform(10, 90):.2f}") |
| 72 | + |
| 73 | + # Gauge: random memory usage simulation |
| 74 | + metrics.append("# HELP mock_memory_usage_bytes Simulated memory usage") |
| 75 | + metrics.append("# TYPE mock_memory_usage_bytes gauge") |
| 76 | + metrics.append(f"mock_memory_usage_bytes {random.randint(100000000, 500000000)}") |
| 77 | + |
| 78 | + # Histogram: simulated request duration |
| 79 | + metrics.append("# HELP mock_request_duration_seconds Request duration histogram") |
| 80 | + metrics.append("# TYPE mock_request_duration_seconds histogram") |
| 81 | + # Simulate bucket counts |
| 82 | + le_buckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0] |
| 83 | + cumulative = 0 |
| 84 | + for le in le_buckets: |
| 85 | + cumulative += random.randint(0, 10) |
| 86 | + metrics.append(f'mock_request_duration_seconds_bucket{{le="{le}"}} {cumulative}') |
| 87 | + metrics.append(f'mock_request_duration_seconds_bucket{{le="+Inf"}} {cumulative + random.randint(0, 5)}') |
| 88 | + metrics.append(f"mock_request_duration_seconds_sum {random.uniform(0.1, 10.0):.4f}") |
| 89 | + metrics.append(f"mock_request_duration_seconds_count {cumulative}") |
| 90 | + |
| 91 | + # Metrics with labels |
| 92 | + metrics.append("# HELP mock_http_requests_by_status HTTP requests by status code") |
| 93 | + metrics.append("# TYPE mock_http_requests_by_status counter") |
| 94 | + metrics.append(f'mock_http_requests_by_status{{status="200"}} {request_count - error_count}') |
| 95 | + metrics.append(f'mock_http_requests_by_status{{status="500"}} {error_count}') |
| 96 | + |
| 97 | + # Multi-label metric |
| 98 | + metrics.append("# HELP mock_api_calls API calls by endpoint and method") |
| 99 | + metrics.append("# TYPE mock_api_calls counter") |
| 100 | + metrics.append(f'mock_api_calls{{endpoint="/users",method="GET"}} {random.randint(100, 1000)}') |
| 101 | + metrics.append(f'mock_api_calls{{endpoint="/users",method="POST"}} {random.randint(10, 100)}') |
| 102 | + metrics.append(f'mock_api_calls{{endpoint="/orders",method="GET"}} {random.randint(50, 500)}') |
| 103 | + metrics.append(f'mock_api_calls{{endpoint="/orders",method="POST"}} {random.randint(20, 200)}') |
| 104 | + |
| 105 | + return "\n".join(metrics) + "\n" |
| 106 | + |
| 107 | + def log_message(self, format, *args): |
| 108 | + # Suppress default logging, or customize it |
| 109 | + print(f"[{self.log_date_time_string()}] {args[0]}") |
| 110 | + |
| 111 | + |
| 112 | +def main(): |
| 113 | + parser = argparse.ArgumentParser(description="Mock Prometheus metrics server") |
| 114 | + parser.add_argument("--port", type=int, default=8080, help="Port to listen on (default: 8080)") |
| 115 | + args = parser.parse_args() |
| 116 | + |
| 117 | + server = HTTPServer(("0.0.0.0", args.port), MetricsHandler) |
| 118 | + print(f"Mock metrics server running on http://0.0.0.0:{args.port}") |
| 119 | + print(f"Metrics available at http://0.0.0.0:{args.port}/metrics") |
| 120 | + print("Press Ctrl+C to stop") |
| 121 | + |
| 122 | + try: |
| 123 | + server.serve_forever() |
| 124 | + except KeyboardInterrupt: |
| 125 | + print("\nShutting down...") |
| 126 | + server.shutdown() |
| 127 | + |
| 128 | + |
| 129 | +if __name__ == "__main__": |
| 130 | + main() |
0 commit comments