Metrik Apa yang Layak Dibuat?
Godaan terbesar saat instrumentasi adalah mengukur segalanya. Hasilnya: ribuan metrik yang tidak pernah dilihat, kardinalitas membengkak, dan tidak ada yang tahu metrik mana yang penting saat insiden terjadi.
Gunakan dua kerangka baku berikut sebagai titik awal.
Metode RED — untuk Layanan (request-driven)
| Huruf | Arti | Metrik |
|---|---|---|
| Rate | Berapa request per detik | rate(http_requests_total[5m]) |
| Errors | Berapa yang gagal | rate(http_requests_total{status=~"5.."}[5m]) |
| Duration | Berapa lama | histogram_quantile(0.95, ...) |
Metode USE — untuk Sumber Daya (CPU, disk, memori)
| Huruf | Arti | Contoh |
|---|---|---|
| Utilization | Berapa persen terpakai | CPU 78% |
| Saturation | Berapa banyak yang mengantre | Load average, panjang run queue |
| Errors | Berapa kegagalan | Disk I/O error, paket jaringan drop |
Tiga metrik ini saja sudah bisa menjawab sebagian besar pertanyaan operasional:
http_requests_total{method, route, status}— Counterhttp_request_duration_seconds{method, route}— Histogramhttp_requests_in_flight{route}— Gauge
Tambahkan metrik bisnis (pesanan_dibuat_total, pembayaran_gagal_total) setelah tiga di atas berjalan.
Go
package main
import (
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
requestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Jumlah total request HTTP.",
},
[]string{"method", "route", "status"}, // label berkardinalitas RENDAH
)
requestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Durasi request HTTP dalam detik.",
Buckets: []float64{.05, .1, .2, .3, .5, .75, 1, 2, 5}, // sesuai SLO
},
[]string{"method", "route"},
)
inFlight = promauto.NewGauge(prometheus.GaugeOpts{
Name: "http_requests_in_flight",
Help: "Jumlah request yang sedang diproses.",
})
)
func instrumentasi(route string, next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
inFlight.Inc()
defer inFlight.Dec()
mulai := time.Now()
rec := &statusRecorder{ResponseWriter: w, status: 200}
next(rec, r)
// PENTING: pakai 'route' (pola), bukan r.URL.Path (nilai sebenarnya)
requestsTotal.WithLabelValues(r.Method, route, itoa(rec.status)).Inc()
requestDuration.WithLabelValues(r.Method, route).Observe(time.Since(mulai).Seconds())
}
}
func main() {
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/api/pesanan", instrumentasi("/api/pesanan", handlerPesanan))
http.ListenAndServe(":8080", nil)
}Python (FastAPI / Flask)
from prometheus_client import Counter, Histogram, Gauge, make_asgi_app
from fastapi import FastAPI, Request
import time
REQUESTS = Counter(
"http_requests_total", "Jumlah total request HTTP.",
["method", "route", "status"],
)
DURATION = Histogram(
"http_request_duration_seconds", "Durasi request HTTP.",
["method", "route"],
buckets=(.05, .1, .2, .3, .5, .75, 1, 2, 5),
)
IN_FLIGHT = Gauge("http_requests_in_flight", "Request yang sedang diproses.")
app = FastAPI()
app.mount("/metrics", make_asgi_app()) # endpoint /metrics
@app.middleware("http")
async def ukur(request: Request, call_next):
# Pakai pola route, BUKAN path sebenarnya -> mencegah cardinality explosion
route = request.scope.get("route").path if request.scope.get("route") else "unknown"
IN_FLIGHT.inc()
mulai = time.perf_counter()
try:
response = await call_next(request)
status = response.status_code
return response
except Exception:
status = 500
raise
finally:
IN_FLIGHT.dec()
REQUESTS.labels(request.method, route, str(status)).inc()
DURATION.labels(request.method, route).observe(time.perf_counter() - mulai)Setiap worker adalah proses terpisah dengan registry metriknya sendiri. Prometheus akan men-scrape worker yang berbeda-beda secara acak, sehingga angkanya melompat-lompat.
Solusi: aktifkan mode multiprocess dengan variabel lingkungan PROMETHEUS_MULTIPROC_DIR yang menunjuk ke direktori bersama, dan gunakan MultiProcessCollector.
Node.js
const client = require('prom-client');
// Metrik bawaan Node: event loop lag, heap, GC — sangat berguna
client.collectDefaultMetrics({ prefix: 'node_' });
const requests = new client.Counter({
name: 'http_requests_total',
help: 'Jumlah total request HTTP.',
labelNames: ['method', 'route', 'status'],
});
const duration = new client.Histogram({
name: 'http_request_duration_seconds',
help: 'Durasi request HTTP.',
labelNames: ['method', 'route'],
buckets: [0.05, 0.1, 0.2, 0.3, 0.5, 0.75, 1, 2, 5],
});
app.use((req, res, next) => {
const selesai = duration.startTimer({ method: req.method });
res.on('finish', () => {
// req.route.path = pola seperti '/api/user/:id', bukan '/api/user/48213'
const route = (req.route && req.route.path) || 'unknown';
selesai({ route });
requests.inc({ method: req.method, route, status: res.statusCode });
});
next();
});
app.get('/metrics', async (req, res) => {
res.set('Content-Type', client.register.contentType);
res.end(await client.register.metrics());
});