How to Build an HTTP Server Request Duration Histogram in Python
Create a small HTTP server that times each GET request, buckets the duration, and prints a histogram on shutdown.
Python code
34 linesimport time
import random
from collections import Counter
from http.server import HTTPServer, BaseHTTPRequestHandler
class HistogramHandler(BaseHTTPRequestHandler):
response_times = Counter()
def do_GET(self):
start = time.perf_counter()
time.sleep(random.uniform(0.001, 0.1))
duration_ms = (time.perf_counter() - start) * 1000
bucket = round(duration_ms / 10) * 10
self.response_times[bucket] += 1
self.send_response(200)
self.end_headers()
self.wfile.write(f"duration={duration_ms:.2f}ms bucket={bucket}ms\n".encode())
def log_message(self, format, *args):
pass
if __name__ == "__main__":
server = HTTPServer(("localhost", 8000), HistogramHandler)
print("Serving on http://localhost:8000 — press Ctrl+C to stop")
try:
server.serve_forever()
except KeyboardInterrupt:
print("\nResponse time histogram (ms):")
for bucket in sorted(HistogramHandler.response_times):
count = HistogramHandler.response_times[bucket]
print(f"{bucket:>4}ms: {'#' * count} ({count})")
server.shutdown()
Output
Serving on http://localhost:8000 — press Ctrl+C to stop
^C
Response time histogram (ms):
0ms: ## (2)
10ms: ##### (5)
20ms: ####### (7)
30ms: ###### (6)
40ms: #### (4)
50ms: ### (3)
60ms: ## (2)
70ms: # (1)
80ms: # (1)
90ms: ## (2)
How it works
The handler uses time.perf_counter() to measure the elapsed time with high precision. After each request, the duration in milliseconds is rounded to the nearest 10 ms to create a bucket key, which increments a shared Counter. Because Counter is a class attribute, all handler instances share the same histogram, allowing accumulation across requests. When the server is stopped via Ctrl+C, the finally block prints the histogram sorted by bucket. This pattern mimics real observability tools that track latency distributions.
Common mistakes
- Using `time.time()` instead of `time.perf_counter()` for precise short durations
- Forgetting to suppress `log_message`, which fills stdout with request logs
- Storing response_times as an instance attribute instead of a class attribute, losing data across requests
Variations
- Store the histogram in a global dictionary instead of a class attribute
- Use `time.monotonic_ns()` to get nanosecond resolution for even shorter requests
Real-world use cases
- Monitoring API latency in a development server to spot slow endpoints before deployment.
- Collecting request duration distributions in a microservice to feed into a metrics dashboard.
- Building a quick load-testing tool that logs response time histograms for different endpoints.
Sponsored
More from Observability & SRE
- Adding a Correlation ID to Log Context in Python medium
- Calculate Error Rate from Log Stream in Python easy
- Check if a Timestamp Falls in a Daily Maintenance Window in Python easy
- Export Metrics with OTLP Mock in Python medium
- Generate Mock CPU and Memory Metrics in Python easy
- Generate Prometheus Text Exposition Format in Python easy
Keep learning
Related tutorials and quizzes for this topic.