Metrics don't appear by themselves — applications must be instrumented. This episode covers the Prometheus and OpenTelemetry SDK instrumentation libraries, creating counters, gauges, and histograms, auto-instrumentation, and how to expose metrics via a metrics endpoint and OTLP.

In episode 6 you got to know Mimir and in episode 7 you learned to read metrics with PromQL. But a fundamental question hasn't been answered: where do metrics come from? The answer is instrumentation — the process of adding code to an application so it produces measurements.
This episode covers two instrumentation paths: using the Prometheus client library directly and using the OpenTelemetry SDK for metrics. You'll learn to create counters, gauges, and histograms manually, take advantage of auto-instrumentation, then expose the results so they can be scraped or exported via OTLP to Mimir.
There are two complementary approaches:
The choice of language depends on your team. Official libraries are available for Go, Java, Python, and Node.js, for example prometheus_client for Python and prometheus-client for Go.
pip install prometheus-clientThe pip install prometheus-client command installs the library used in the examples in this episode.
Use the Prometheus client when your stack is simple and Mimir is the only metrics destination. Use the OpenTelemetry SDK when you've already adopted OTel for traces and logs — one SDK handles all signals consistently.
The simplest example with prometheus_client:
from prometheus_client import Counter, Gauge, Histogram, start_http_server
REQUESTS = Counter("http_requests_total", "Total HTTP requests", ["method"])
IN_FLIGHT = Gauge("http_requests_in_flight", "Requests sedang diproses")
LATENCY = Histogram("http_request_duration_seconds", "Latency request")
def handle(method):
IN_FLIGHT.inc()
with LATENCY.time():
REQUESTS.labels(method=method).inc()
IN_FLIGHT.dec()
start_http_server(8000)Note the pattern start_http_server(8000) — this starts a small HTTP server that exposes metrics on port 8000.
_total for counters and _seconds for durations.method, status, service. Never use user_id or trace_id as labels.Writing manual instrumentation for every framework is unrealistic. This is where auto-instrumentation helps: libraries automatically instrument HTTP servers, database clients, and message queues.
With the OpenTelemetry SDK, runtime and HTTP metrics can be enabled with a few lines:
from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from opentelemetry.instrumentation.system_metrics import SystemMetricsInstrumentor
reader = PeriodicExportingMetricReader(OTLPMetricExporter(endpoint="http://localhost:4317"))
metrics.set_meter_provider(MeterProvider(metric_readers=[reader]))
SystemMetricsInstrumentor().instrument()After running SystemMetricsInstrumentor().instrument(), system CPU and memory metrics start exporting to the Collector every period.
The same concept applies in other languages: opentelemetry-instrumentation-express for Node.js, opentelemetry-instrumentation-jdbc for Java, and opentelemetry-instrumentation-net-http for Go. Each package adds spans and metrics for the target framework without changing business logic.
Prometheus and Mimir pull metrics using the pull model: periodically contacting the application's /metrics endpoint.
curl -s http://localhost:8000/metricsThe output of curl -s http://localhost:8000/metrics is in Prometheus exposition format — lines of text with metric names, labels, and values.
The second path is push: the application or agent periodically sends data to Mimir's remote write endpoint. This is used when the application runs behind a network the scraper can't reach, or when using a collector like Alloy.
url: http://mimir:9009/api/v1/push
send_interval: 15sThe remote write mechanism is covered in more detail in episode 11 when you use Grafana Alloy as the collector.
When there are many services, endpoints can't be recorded manually. Prometheus and Mimir use service discovery — for example Kubernetes labels — to find scrape targets automatically. This approach is covered in depth in episode 27.
Warning
Don't mix high-cardinality dynamic labels into metrics, no matter which instrumentation path you use. A single user_id label alone can multiply your Mimir storage costs.
In episode 8 you understood the two metric instrumentation paths: the Prometheus client library and the OpenTelemetry SDK, created counters, gauges, and histograms manually, took advantage of auto-instrumentation for runtime and HTTP metrics, and presented metrics via both pull and remote write.
The key takeaways:
In the next episode 9 we'll discuss logs with Loki — the distributor, ingester, querier, and compactor architecture, the difference between the index-free approach and Elasticsearch, and log formats and parsing strategies. Metrics are now running; it's logs' turn to complete the second pillar.