Skip to main content
Version: 1.1

Metrics

The metrics module counts how many checks each limiter allowed, denied, and failed, and can export those counts to Prometheus.

go get github.com/KARTIKrocks/go-ratelimit/metrics

Instrumenting a limiter​

Wrap a keyed limiter with NewInstrumented, giving it a name:

import "github.com/KARTIKrocks/go-ratelimit/metrics"

base := ratelimit.NewKeyedTokenBucket(10, 20, time.Minute)
defer base.Close()

limiter := metrics.NewInstrumented(base, "api")

handler := ratelimit.Middleware(limiter)(mux)

Instrumented implements KeyedResultLimiter, so it works wherever that interface is accepted — the middleware, PathLimiter, or your own code. It has no Allow, Wait, or Reset; call those on the base limiter, and note that WaitMiddleware, which needs a KeyedLimiter, cannot take it. Every Take and TakeN records one allowed or denied check. Check and CheckN are read-only peeks and record nothing.

For a single (non-keyed) limiter, use NewSimpleInstrumented:

global := metrics.NewSimpleInstrumented(ratelimit.NewTokenBucket(500, 100), "global")
handler := ratelimit.MiddlewareFunc(global)(mux)

Reading the counts​

stats := limiter.GetStats() // this limiter
stats = metrics.GetStats("api") // any limiter, by name
all := metrics.GetAllStats() // map[string]metrics.Stats

fmt.Println(stats.Allowed, stats.Denied, stats.Errors, stats.LastUpdate)

LastUpdate has one-second resolution.

Errors​

The wrappers only see Results, so they cannot tell a Redis failure from a decision. Record errors yourself from the Redis error handler:

limiter := redisstore.NewRedisTokenBucket(store, "myapp", 10, 20,
redisstore.WithErrorHandler(func(err error) {
metrics.RecordError("api")
}),
)

Prometheus​

Call RegisterPrometheus once at startup, then serve the registry as usual:

metrics.RegisterPrometheus(prometheus.DefaultRegisterer)

mux.Handle("/metrics", promhttp.Handler())
MetricLabelsCounts
ratelimit_requests_totallimiter, result (allowed / denied)every recorded check
ratelimit_requests_allowed_totallimiterallowed checks
ratelimit_requests_denied_totallimiterdenied checks
ratelimit_errors_totallimiterrecorded errors

The limiter label is the name you gave the wrapper. A useful alert is the denial ratio per limiter:

sum by (limiter) (rate(ratelimit_requests_denied_total[5m]))
/ sum by (limiter) (rate(ratelimit_requests_total[5m]))

:::warning Register once

The Prometheus collectors are package-level. Calling RegisterPrometheus twice with the same registerer panics with a duplicate registration error, so call it from main, not from code that can run more than once. Checks recorded before it is called are not exported.

:::

Separate collectors​

The package-level functions use a global Collector. For isolated counts — in tests, or for a component that should not share names with the rest of the process — create one with NewCollector and pass it to NewInstrumentedWithCollector:

collector := metrics.NewCollector()
limiter := metrics.NewInstrumentedWithCollector(base, "api", collector)

collector.GetStats("api")
collector.ResetAll()

Prometheus export is shared: once registered, every collector also increments the Prometheus counters.