e071084ebe
govulncheck / govulncheck (push) Waiting to run
Harness (E2E) / Harnesses (mock LLM) (push) Waiting to run
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Waiting to run
Lint / golangci-lint (push) Waiting to run
Run Tests / Unit Tests (push) Waiting to run
Run Tests / Etcd Integration Tests (push) Waiting to run
96 lines
3.0 KiB
Go
96 lines
3.0 KiB
Go
package prometheus
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
// metrics bundles the counters/histograms used by the wrappers.
|
|
// A metrics value is keyed by (name + namespace + subsystem) so that
|
|
// multiple wrappers created with the same options share the same
|
|
// collectors instead of failing with AlreadyRegisteredError.
|
|
type metrics struct {
|
|
requestTotal *prometheus.CounterVec
|
|
requestDuration *prometheus.HistogramVec
|
|
}
|
|
|
|
// metricLabels are the labels we use on every metric. They intentionally
|
|
// stay on a small, low-cardinality set: high-cardinality labels (e.g. the
|
|
// full request body) must not end up in Prometheus.
|
|
var metricLabels = []string{"service", "endpoint", "status"}
|
|
|
|
var (
|
|
metricsMu sync.Mutex
|
|
metricsCache = map[string]*metrics{}
|
|
)
|
|
|
|
// getMetrics returns a cached metrics bundle for the given options, creating
|
|
// and registering it on first use. Collectors that were already registered
|
|
// on the underlying Registerer (e.g. because a user constructed two wrappers
|
|
// with identical options) are reused transparently.
|
|
func getMetrics(opts Options) *metrics {
|
|
key := opts.Name + "\x00" + opts.Namespace + "\x00" + opts.Subsystem
|
|
|
|
metricsMu.Lock()
|
|
defer metricsMu.Unlock()
|
|
|
|
if m, ok := metricsCache[key]; ok {
|
|
return m
|
|
}
|
|
|
|
counter := prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: opts.Namespace,
|
|
Subsystem: opts.Subsystem,
|
|
Name: opts.Name + "_request_total",
|
|
Help: "How many go-micro requests processed, partitioned by service, endpoint and status.",
|
|
ConstLabels: opts.ConstLabels,
|
|
},
|
|
metricLabels,
|
|
)
|
|
|
|
histogram := prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: opts.Namespace,
|
|
Subsystem: opts.Subsystem,
|
|
Name: opts.Name + "_request_duration_seconds",
|
|
Help: "Histogram of go-micro request latencies in seconds, partitioned by service, endpoint and status.",
|
|
ConstLabels: opts.ConstLabels,
|
|
Buckets: opts.Buckets,
|
|
},
|
|
metricLabels,
|
|
)
|
|
|
|
m := &metrics{
|
|
requestTotal: register(opts.Registerer, counter).(*prometheus.CounterVec),
|
|
requestDuration: register(opts.Registerer, histogram).(*prometheus.HistogramVec),
|
|
}
|
|
metricsCache[key] = m
|
|
return m
|
|
}
|
|
|
|
// register registers c on r. If an identical collector is already registered
|
|
// (AlreadyRegisteredError), the existing collector is returned so that the
|
|
// wrapper can be constructed more than once without panicking.
|
|
func register(r prometheus.Registerer, c prometheus.Collector) prometheus.Collector {
|
|
if err := r.Register(c); err != nil {
|
|
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
|
|
return are.ExistingCollector
|
|
}
|
|
// Any other registration error is a programming mistake (e.g.
|
|
// inconsistent label dimensions) and should surface loudly.
|
|
panic(err)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// status returns "success" or "fail" depending on whether err is nil.
|
|
// Using a fixed, low-cardinality set keeps Prometheus memory bounded.
|
|
func status(err error) string {
|
|
if err != nil {
|
|
return "fail"
|
|
}
|
|
return "success"
|
|
}
|