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.

Medium Python 3.9+ Aug 9, 2026 Observability & SRE 13 views 0 copies

Python code

34 lines
Python 3.9+
import 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

stdout
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

  1. Store the histogram in a global dictionary instead of a class attribute
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Observability & SRE

Related tutorials and quizzes for this topic.