e3c37a46e2
at the first iteration, only the following metrics are collected: - HTTP metrics of each API endpoint - cache counter for request/hit/miss - histogram of storage actions, including: GetContent, PutContent, Stat, List, Move, and Delete Signed-off-by: tifayuki <tifayuki@gmail.com>
315 lines
8.7 KiB
Go
315 lines
8.7 KiB
Go
package metrics
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
type Labels map[string]string
|
|
|
|
// NewNamespace returns a namespaces that is responsible for managing a collection of
|
|
// metrics for a particual namespace and subsystem
|
|
//
|
|
// labels allows const labels to be added to all metrics created in this namespace
|
|
// and are commonly used for data like application version and git commit
|
|
func NewNamespace(name, subsystem string, labels Labels) *Namespace {
|
|
if labels == nil {
|
|
labels = make(map[string]string)
|
|
}
|
|
return &Namespace{
|
|
name: name,
|
|
subsystem: subsystem,
|
|
labels: labels,
|
|
}
|
|
}
|
|
|
|
// Namespace describes a set of metrics that share a namespace and subsystem.
|
|
type Namespace struct {
|
|
name string
|
|
subsystem string
|
|
labels Labels
|
|
mu sync.Mutex
|
|
metrics []prometheus.Collector
|
|
}
|
|
|
|
// WithConstLabels returns a namespace with the provided set of labels merged
|
|
// with the existing constant labels on the namespace.
|
|
//
|
|
// Only metrics created with the returned namespace will get the new constant
|
|
// labels. The returned namespace must be registered separately.
|
|
func (n *Namespace) WithConstLabels(labels Labels) *Namespace {
|
|
n.mu.Lock()
|
|
ns := &Namespace{
|
|
name: n.name,
|
|
subsystem: n.subsystem,
|
|
labels: mergeLabels(n.labels, labels),
|
|
}
|
|
n.mu.Unlock()
|
|
return ns
|
|
}
|
|
|
|
func (n *Namespace) NewCounter(name, help string) Counter {
|
|
c := &counter{pc: prometheus.NewCounter(n.newCounterOpts(name, help))}
|
|
n.Add(c)
|
|
return c
|
|
}
|
|
|
|
func (n *Namespace) NewLabeledCounter(name, help string, labels ...string) LabeledCounter {
|
|
c := &labeledCounter{pc: prometheus.NewCounterVec(n.newCounterOpts(name, help), labels)}
|
|
n.Add(c)
|
|
return c
|
|
}
|
|
|
|
func (n *Namespace) newCounterOpts(name, help string) prometheus.CounterOpts {
|
|
return prometheus.CounterOpts{
|
|
Namespace: n.name,
|
|
Subsystem: n.subsystem,
|
|
Name: makeName(name, Total),
|
|
Help: help,
|
|
ConstLabels: prometheus.Labels(n.labels),
|
|
}
|
|
}
|
|
|
|
func (n *Namespace) NewTimer(name, help string) Timer {
|
|
t := &timer{
|
|
m: prometheus.NewHistogram(n.newTimerOpts(name, help)),
|
|
}
|
|
n.Add(t)
|
|
return t
|
|
}
|
|
|
|
func (n *Namespace) NewLabeledTimer(name, help string, labels ...string) LabeledTimer {
|
|
t := &labeledTimer{
|
|
m: prometheus.NewHistogramVec(n.newTimerOpts(name, help), labels),
|
|
}
|
|
n.Add(t)
|
|
return t
|
|
}
|
|
|
|
func (n *Namespace) newTimerOpts(name, help string) prometheus.HistogramOpts {
|
|
return prometheus.HistogramOpts{
|
|
Namespace: n.name,
|
|
Subsystem: n.subsystem,
|
|
Name: makeName(name, Seconds),
|
|
Help: help,
|
|
ConstLabels: prometheus.Labels(n.labels),
|
|
}
|
|
}
|
|
|
|
func (n *Namespace) NewGauge(name, help string, unit Unit) Gauge {
|
|
g := &gauge{
|
|
pg: prometheus.NewGauge(n.newGaugeOpts(name, help, unit)),
|
|
}
|
|
n.Add(g)
|
|
return g
|
|
}
|
|
|
|
func (n *Namespace) NewLabeledGauge(name, help string, unit Unit, labels ...string) LabeledGauge {
|
|
g := &labeledGauge{
|
|
pg: prometheus.NewGaugeVec(n.newGaugeOpts(name, help, unit), labels),
|
|
}
|
|
n.Add(g)
|
|
return g
|
|
}
|
|
|
|
func (n *Namespace) newGaugeOpts(name, help string, unit Unit) prometheus.GaugeOpts {
|
|
return prometheus.GaugeOpts{
|
|
Namespace: n.name,
|
|
Subsystem: n.subsystem,
|
|
Name: makeName(name, unit),
|
|
Help: help,
|
|
ConstLabels: prometheus.Labels(n.labels),
|
|
}
|
|
}
|
|
|
|
func (n *Namespace) Describe(ch chan<- *prometheus.Desc) {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
|
|
for _, metric := range n.metrics {
|
|
metric.Describe(ch)
|
|
}
|
|
}
|
|
|
|
func (n *Namespace) Collect(ch chan<- prometheus.Metric) {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
|
|
for _, metric := range n.metrics {
|
|
metric.Collect(ch)
|
|
}
|
|
}
|
|
|
|
func (n *Namespace) Add(collector prometheus.Collector) {
|
|
n.mu.Lock()
|
|
n.metrics = append(n.metrics, collector)
|
|
n.mu.Unlock()
|
|
}
|
|
|
|
func (n *Namespace) NewDesc(name, help string, unit Unit, labels ...string) *prometheus.Desc {
|
|
name = makeName(name, unit)
|
|
namespace := n.name
|
|
if n.subsystem != "" {
|
|
namespace = fmt.Sprintf("%s_%s", namespace, n.subsystem)
|
|
}
|
|
name = fmt.Sprintf("%s_%s", namespace, name)
|
|
return prometheus.NewDesc(name, help, labels, prometheus.Labels(n.labels))
|
|
}
|
|
|
|
// mergeLabels merges two or more labels objects into a single map, favoring
|
|
// the later labels.
|
|
func mergeLabels(lbs ...Labels) Labels {
|
|
merged := make(Labels)
|
|
|
|
for _, target := range lbs {
|
|
for k, v := range target {
|
|
merged[k] = v
|
|
}
|
|
}
|
|
|
|
return merged
|
|
}
|
|
|
|
func makeName(name string, unit Unit) string {
|
|
if unit == "" {
|
|
return name
|
|
}
|
|
|
|
return fmt.Sprintf("%s_%s", name, unit)
|
|
}
|
|
|
|
func (n *Namespace) NewDefaultHttpMetrics(handlerName string) []*HTTPMetric {
|
|
return n.NewHttpMetricsWithOpts(handlerName, HTTPHandlerOpts{
|
|
DurationBuckets: defaultDurationBuckets,
|
|
RequestSizeBuckets: defaultResponseSizeBuckets,
|
|
ResponseSizeBuckets: defaultResponseSizeBuckets,
|
|
})
|
|
}
|
|
|
|
func (n *Namespace) NewHttpMetrics(handlerName string, durationBuckets, requestSizeBuckets, responseSizeBuckets []float64) []*HTTPMetric {
|
|
return n.NewHttpMetricsWithOpts(handlerName, HTTPHandlerOpts{
|
|
DurationBuckets: durationBuckets,
|
|
RequestSizeBuckets: requestSizeBuckets,
|
|
ResponseSizeBuckets: responseSizeBuckets,
|
|
})
|
|
}
|
|
|
|
func (n *Namespace) NewHttpMetricsWithOpts(handlerName string, opts HTTPHandlerOpts) []*HTTPMetric {
|
|
var httpMetrics []*HTTPMetric
|
|
inFlightMetric := n.NewInFlightGaugeMetric(handlerName)
|
|
requestTotalMetric := n.NewRequestTotalMetric(handlerName)
|
|
requestDurationMetric := n.NewRequestDurationMetric(handlerName, opts.DurationBuckets)
|
|
requestSizeMetric := n.NewRequestSizeMetric(handlerName, opts.RequestSizeBuckets)
|
|
responseSizeMetric := n.NewResponseSizeMetric(handlerName, opts.ResponseSizeBuckets)
|
|
httpMetrics = append(httpMetrics, inFlightMetric, requestDurationMetric, requestTotalMetric, requestSizeMetric, responseSizeMetric)
|
|
return httpMetrics
|
|
}
|
|
|
|
func (n *Namespace) NewInFlightGaugeMetric(handlerName string) *HTTPMetric {
|
|
labels := prometheus.Labels(n.labels)
|
|
labels["handler"] = handlerName
|
|
metric := prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Namespace: n.name,
|
|
Subsystem: n.subsystem,
|
|
Name: "in_flight_requests",
|
|
Help: "The in-flight HTTP requests",
|
|
ConstLabels: prometheus.Labels(labels),
|
|
})
|
|
httpMetric := &HTTPMetric{
|
|
Collector: metric,
|
|
handlerType: InstrumentHandlerInFlight,
|
|
}
|
|
n.Add(httpMetric)
|
|
return httpMetric
|
|
}
|
|
|
|
func (n *Namespace) NewRequestTotalMetric(handlerName string) *HTTPMetric {
|
|
labels := prometheus.Labels(n.labels)
|
|
labels["handler"] = handlerName
|
|
metric := prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: n.name,
|
|
Subsystem: n.subsystem,
|
|
Name: "requests_total",
|
|
Help: "Total number of HTTP requests made.",
|
|
ConstLabels: prometheus.Labels(labels),
|
|
},
|
|
[]string{"code", "method"},
|
|
)
|
|
httpMetric := &HTTPMetric{
|
|
Collector: metric,
|
|
handlerType: InstrumentHandlerCounter,
|
|
}
|
|
n.Add(httpMetric)
|
|
return httpMetric
|
|
}
|
|
func (n *Namespace) NewRequestDurationMetric(handlerName string, buckets []float64) *HTTPMetric {
|
|
if len(buckets) == 0 {
|
|
panic("DurationBuckets must be provided")
|
|
}
|
|
labels := prometheus.Labels(n.labels)
|
|
labels["handler"] = handlerName
|
|
opts := prometheus.HistogramOpts{
|
|
Namespace: n.name,
|
|
Subsystem: n.subsystem,
|
|
Name: "request_duration_seconds",
|
|
Help: "The HTTP request latencies in seconds.",
|
|
Buckets: buckets,
|
|
ConstLabels: prometheus.Labels(labels),
|
|
}
|
|
metric := prometheus.NewHistogramVec(opts, []string{"method"})
|
|
httpMetric := &HTTPMetric{
|
|
Collector: metric,
|
|
handlerType: InstrumentHandlerDuration,
|
|
}
|
|
n.Add(httpMetric)
|
|
return httpMetric
|
|
}
|
|
|
|
func (n *Namespace) NewRequestSizeMetric(handlerName string, buckets []float64) *HTTPMetric {
|
|
if len(buckets) == 0 {
|
|
panic("RequestSizeBuckets must be provided")
|
|
}
|
|
labels := prometheus.Labels(n.labels)
|
|
labels["handler"] = handlerName
|
|
opts := prometheus.HistogramOpts{
|
|
Namespace: n.name,
|
|
Subsystem: n.subsystem,
|
|
Name: "request_size_bytes",
|
|
Help: "The HTTP request sizes in bytes.",
|
|
Buckets: buckets,
|
|
ConstLabels: prometheus.Labels(labels),
|
|
}
|
|
metric := prometheus.NewHistogramVec(opts, []string{})
|
|
httpMetric := &HTTPMetric{
|
|
Collector: metric,
|
|
handlerType: InstrumentHandlerRequestSize,
|
|
}
|
|
n.Add(httpMetric)
|
|
return httpMetric
|
|
}
|
|
|
|
func (n *Namespace) NewResponseSizeMetric(handlerName string, buckets []float64) *HTTPMetric {
|
|
if len(buckets) == 0 {
|
|
panic("ResponseSizeBuckets must be provided")
|
|
}
|
|
labels := prometheus.Labels(n.labels)
|
|
labels["handler"] = handlerName
|
|
opts := prometheus.HistogramOpts{
|
|
Namespace: n.name,
|
|
Subsystem: n.subsystem,
|
|
Name: "response_size_bytes",
|
|
Help: "The HTTP response sizes in bytes.",
|
|
Buckets: buckets,
|
|
ConstLabels: prometheus.Labels(labels),
|
|
}
|
|
metrics := prometheus.NewHistogramVec(opts, []string{})
|
|
httpMetric := &HTTPMetric{
|
|
Collector: metrics,
|
|
handlerType: InstrumentHandlerResponseSize,
|
|
}
|
|
n.Add(httpMetric)
|
|
return httpMetric
|
|
}
|