Zero-dependency Prometheus-compatible metrics for Python.
Single-file library that gives you counters, gauges, histograms, summaries,
the OpenMetrics text exposition format, a built-in /metrics HTTP endpoint,
and default process/python collectors — without prometheus_client, twisted,
aiohttp, or any other dependency.
Part of the
tiny-*ecosystem — 21 single-file Python libraries, 0 dependencies across the entire stack.
prometheus_client is excellent but ships ~3 KLOC of collectors, async
helpers, multiprocess shared-memory mode, and optional twisted/aiohttp
exposition. For a service that just wants a /metrics endpoint and a few
counters, that's overkill.
tiny-metrics gives you:
| Need | prometheus_client |
tiny-metrics |
|---|---|---|
| Counter, Gauge, Histogram, Summary | ✓ | ✓ |
OpenMetrics text format (# HELP, # TYPE, # EOF) |
✓ | ✓ |
Built-in /metrics HTTP endpoint |
✓ | ✓ |
Process collector (process_*) |
✓ | ✓ |
Python platform collector (python_info) |
✓ (gauge) | ✓ |
Histogram .time() decorator / ctx manager |
✓ | ✓ |
| Custom collectors | ✓ | ✓ |
| Multiprocess shared-memory mode | ✓ | ✗ |
| Async exposition | ✓ (optional) | ✗ |
| Runtime dependencies | 0 | 0 |
| Lines of code | ~3 000 | ~700 |
For 90% of services the ✗ rows aren't needed. When they are, drop down to
prometheus_client.
# Copy tiny_metrics.py into your project — that's the whole install.
curl -O https://raw.githubusercontent.com/hussain-alsaibai/tiny-metrics/main/tiny_metrics.pyOr as a package:
pip install tiny-metrics # coming soonfrom tiny_metrics import (
Counter, Gauge, Histogram, Summary,
start_http_server, enable_default_collectors,
)
REQUESTS = Counter("http_requests_total", "Total HTTP requests",
labelnames=("method", "code"))
INFLIGHT = Gauge("http_inflight_requests", "In-flight requests")
LATENCY = Histogram("http_request_seconds", "Request latency",
buckets=(0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0))
@LATENCY.time()
def handle(req):
INFLIGHT.inc()
try:
# ... do work ...
REQUESTS.labels(method=req.method, code=200).inc()
finally:
INFLIGHT.dec()
if __name__ == "__main__":
enable_default_collectors() # process_cpu_seconds_total, process_resident_memory_bytes, python_info
start_http_server(port=8000) # /metrics is now scrape-readyThen point Prometheus at it:
scrape_configs:
- job_name: my-service
static_configs:
- targets: ['localhost:8000']Scrape /metrics and you get standard Prometheus format:
# HELP http_requests_total Total HTTP requests
# TYPE http_requests_total counter
http_requests_total{code="200",method="GET"} 142
http_requests_total{code="500",method="POST"} 3
# HELP http_request_seconds Request latency
# TYPE http_request_seconds histogram
http_request_seconds_bucket{le="0.005"} 0
http_request_seconds_bucket{le="0.01"} 1
http_request_seconds_bucket{le="0.05"} 12
http_request_seconds_bucket{le="0.1"} 38
http_request_seconds_bucket{le="0.5"} 130
http_request_seconds_bucket{le="1"} 140
http_request_seconds_bucket{le="5"} 142
http_request_seconds_bucket{le="+Inf"} 142
http_request_seconds_count 142
http_request_seconds_sum 38.21
# HELP process_resident_memory_bytes Resident memory size in bytes
# TYPE process_resident_memory_bytes gauge
process_resident_memory_bytes 25165824
# HELP python_info Python platform information
# TYPE python_info gauge
python_info{implementation="CPython",machine="x86_64",system="Linux",version="3.12.4"} 1.0
# EOF
c = Counter("requests_total", "Total requests", labelnames=("method", "code"))
c.inc() # default labels (none)
c.inc(5, method="GET", code="200") # +5 to GET/200
c.dec(2, method="GET", code="200") # counter decrement (allowed)Counter.inc rejects negative amounts (raises ValueError). Counter.dec
exists for the rare "I really mean subtract" case.
g = Gauge("inflight", "In-flight requests")
g.inc(); g.dec(); g.set(42); g.set_to_current_time() # time.time()
with g.track_inprogress(): # context manager (ctx_mngr) — see below
do_work()Gauge accepts negative increments. Useful for "delta since last scrape"
counters stored as a gauge.
h = Histogram("latency_seconds", "Latency",
buckets=(0.005, 0.01, 0.05, 0.1, 0.5, 1.0))
h.observe(0.123) # manual
with h.time(): # context manager
do_work()
@h.time() # decorator
def handler(): ...Buckets are cumulative — le="1.0" includes everything that fell into
smaller buckets. +Inf always equals _count. Default buckets:
(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0).
s = Summary("rpc_duration_seconds", "RPC latency",
max_age_seconds=600, age_buckets=5)
s.observe(0.42)Summary uses a bounded ring buffer per child and computes quantiles on
scrape (P50, P90, P95, P99). For very-high-cardinality streams prefer
Histogram.
start_http_server(port=8000) # all interfaces
start_http_server(port=8000, addr="127.0.0.1") # localhost only
start_http_server(port=0, registry=my_registry) # ephemeral portEndpoints:
GET /metrics→ OpenMetrics text formatGET /→ tiny HTML link page
from tiny_metrics import enable_default_collectors
enable_default_collectors() # process + python_infoProcessCollector emits:
process_cpu_seconds_total(counter)process_resident_memory_bytes(gauge)process_virtual_memory_bytes(gauge)process_start_time_seconds(gauge)process_open_fds(gauge, Linux)
PlatformCollector emits:
python_info{version, implementation, system, machine}(gauge=1)
from tiny_metrics import _Collector, _Sample
class MyCollector(_Collector):
def collect(self):
return [("my_metric", "gauge", "My custom metric",
[_Sample("my_value", {"shard": "1"}, 42.0)])]
REGISTRY.register_collector(MyCollector())The "tiny-" rule is one file, zero dependencies, MIT-licensed, fully tested. This library fits:
- A single
tiny_metrics.pyfile (~700 LOC) - 0 third-party imports
- 23 unit tests, 100% in-process
If you'd rather have async / multiprocess support, use prometheus_client.
python3 test_tiny_metrics.py23 tests, covers counter / gauge / histogram / summary / labels / exposition format / HTTP handler / process collector / thread safety / validation.
prometheus_client |
tiny_metrics |
|---|---|
Counter("x", "x").inc() |
same |
Histogram(...).time() |
same |
start_http_server(port) |
same |
CollectorRegistry() |
MetricRegistry() |
generate_latest(registry) |
generate_latest(registry) |
multiprocess.MultiProcessCollector |
not yet supported |
prometheus_client.exposition twisted/aiohttp |
stdlib only |
MIT.
tiny-router— HTTP routingtiny-log— structured loggingtiny-config— config loadertiny-validator— input validationtiny-cli— CLI buildertiny-cron— schedulertiny-flags— feature flagstiny-queue— persistent queuetiny-rate— rate limitertiny-retry— retry + backofftiny-pool— worker pooltiny-cache— LRU + TTL cachetiny-trace— distributed tracingtiny-secret— secret loadertiny-compose— DI containertiny-agent— agent frameworktiny-mcp— MCP servertiny-embed— embeddings
Built by OpenClaw — autonomous developer agent.