Files
QSfera/Server/vendor/github.com/open-policy-agent/opa/v1/metrics/metrics.go
T
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

371 lines
8.7 KiB
Go

// Copyright 2017 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
// Package metrics contains helpers for performance metric management inside the policy engine.
package metrics
import (
"encoding/json"
"fmt"
"slices"
"strings"
"sync"
"sync/atomic"
"time"
go_metrics "github.com/rcrowley/go-metrics"
)
// Well-known metric names.
const (
BundleRequest = "bundle_request"
ServerHandler = "server_handler"
ServerQueryCacheHit = "server_query_cache_hit"
SDKDecisionEval = "sdk_decision_eval"
RegoQueryCompile = "rego_query_compile"
RegoQueryEval = "rego_query_eval"
RegoQueryParse = "rego_query_parse"
RegoModuleParse = "rego_module_parse"
RegoDataParse = "rego_data_parse"
RegoModuleCompile = "rego_module_compile"
RegoPartialEval = "rego_partial_eval"
RegoInputParse = "rego_input_parse"
RegoLoadFiles = "rego_load_files"
RegoLoadBundles = "rego_load_bundles"
RegoExternalResolve = "rego_external_resolve"
CompilePrepPartial = "compile_prep_partial"
CompileEvalConstraints = "compile_eval_constraints"
CompileTranslateQueries = "compile_translate_queries"
CompileExtractAnnotationsUnknowns = "compile_extract_annotations_unknowns"
CompileExtractAnnotationsMask = "compile_extract_annotations_mask"
CompileEvalMaskRule = "compile_eval_mask_rule"
)
// Info contains attributes describing the underlying metrics provider.
type Info struct {
Name string `json:"name"` // name is a unique human-readable identifier for the provider.
}
// Metrics defines the interface for a collection of performance metrics in the
// policy engine.
type Metrics interface {
Info() Info
Timer(name string) Timer
Histogram(name string) Histogram
Counter(name string) Counter
All() map[string]any
Clear()
json.Marshaler
}
type TimerMetrics interface {
Timers() map[string]any
}
type metrics struct {
mtx sync.Mutex
timers map[string]Timer
histograms map[string]Histogram
counters map[string]Counter
}
// New returns a new Metrics object.
func New() Metrics {
return &metrics{
timers: map[string]Timer{},
histograms: map[string]Histogram{},
counters: map[string]Counter{},
}
}
// NoOp returns a Metrics implementation that does nothing and costs nothing.
// Used when metrics are expected, but not of interest.
func NoOp() Metrics {
return noOpMetricsInstance
}
type metric struct {
Key string
Value any
}
func (*metrics) Info() Info {
return Info{
Name: "<built-in>",
}
}
func (m *metrics) String() string {
all := m.All()
sorted := make([]metric, 0, len(all))
for key, value := range all {
sorted = append(sorted, metric{
Key: key,
Value: value,
})
}
slices.SortFunc(sorted, func(a, b metric) int {
return strings.Compare(a.Key, b.Key)
})
buf := make([]string, len(sorted))
for i := range sorted {
buf[i] = fmt.Sprintf("%v:%v", sorted[i].Key, sorted[i].Value)
}
return strings.Join(buf, " ")
}
func (m *metrics) MarshalJSON() ([]byte, error) {
return json.Marshal(m.All())
}
func (m *metrics) Timer(name string) Timer {
m.mtx.Lock()
defer m.mtx.Unlock()
t, ok := m.timers[name]
if !ok {
t = &timer{}
m.timers[name] = t
}
return t
}
func (m *metrics) Histogram(name string) Histogram {
m.mtx.Lock()
defer m.mtx.Unlock()
h, ok := m.histograms[name]
if !ok {
h = newHistogram()
m.histograms[name] = h
}
return h
}
func (m *metrics) Counter(name string) Counter {
m.mtx.Lock()
defer m.mtx.Unlock()
c, ok := m.counters[name]
if !ok {
zero := counter{}
c = &zero
m.counters[name] = c
}
return c
}
func (m *metrics) All() map[string]any {
m.mtx.Lock()
defer m.mtx.Unlock()
result := make(map[string]any, len(m.timers)+len(m.histograms)+len(m.counters))
for name, timer := range m.timers {
result[m.formatKey(name, timer)] = timer.Value()
}
for name, hist := range m.histograms {
result[m.formatKey(name, hist)] = hist.Value()
}
for name, cntr := range m.counters {
result[m.formatKey(name, cntr)] = cntr.Value()
}
return result
}
func (m *metrics) Timers() map[string]any {
m.mtx.Lock()
defer m.mtx.Unlock()
ts := make(map[string]any, len(m.timers))
for n, t := range m.timers {
ts[m.formatKey(n, t)] = t.Value()
}
return ts
}
func (m *metrics) Clear() {
m.mtx.Lock()
defer m.mtx.Unlock()
m.timers = map[string]Timer{}
m.histograms = map[string]Histogram{}
m.counters = map[string]Counter{}
}
func (*metrics) formatKey(name string, metrics any) string {
switch metrics.(type) {
case Timer:
return "timer_" + name + "_ns"
case Histogram:
return "histogram_" + name
case Counter:
return "counter_" + name
default:
return name
}
}
// Timer defines the interface for a restartable timer that accumulates elapsed
// time.
type Timer interface {
Value() any
Int64() int64
// Start or resume a timer's time tracking.
Start()
// Stop a timer, and accumulate the delta (in nanoseconds) since it was last
// started.
Stop() int64
}
type timer struct {
mtx sync.Mutex
start time.Time
value int64
}
func (t *timer) Start() {
t.mtx.Lock()
t.start = time.Now()
t.mtx.Unlock()
}
func (t *timer) Stop() int64 {
t.mtx.Lock()
defer t.mtx.Unlock()
var delta int64
if !t.start.IsZero() {
// Add the delta to the accumulated time value so far.
delta = time.Since(t.start).Nanoseconds()
t.value += delta
t.start = time.Time{} // Reset the start time to zero.
}
return delta
}
func (t *timer) Value() any {
return t.Int64()
}
func (t *timer) Int64() int64 {
t.mtx.Lock()
defer t.mtx.Unlock()
return t.value
}
// Histogram defines the interface for a histogram with hardcoded percentiles.
type Histogram interface {
Value() any
Update(int64)
}
type histogram struct {
hist go_metrics.Histogram // is thread-safe because of the underlying ExpDecaySample
}
func newHistogram() Histogram {
// NOTE(tsandall): the reservoir size and alpha factor are taken from
// https://github.com/rcrowley/go-metrics. They may need to be tweaked in
// the future.
sample := go_metrics.NewExpDecaySample(1028, 0.015)
hist := go_metrics.NewHistogram(sample)
return &histogram{hist}
}
func (h *histogram) Update(v int64) {
h.hist.Update(v)
}
func (h *histogram) Value() any {
values := make(map[string]any, 12)
snap := h.hist.Snapshot()
percentiles := snap.Percentiles([]float64{
0.5,
0.75,
0.9,
0.95,
0.99,
0.999,
0.9999,
})
values["count"] = snap.Count()
values["min"] = snap.Min()
values["max"] = snap.Max()
values["mean"] = snap.Mean()
values["stddev"] = snap.StdDev()
values["median"] = percentiles[0]
values["75%"] = percentiles[1]
values["90%"] = percentiles[2]
values["95%"] = percentiles[3]
values["99%"] = percentiles[4]
values["99.9%"] = percentiles[5]
values["99.99%"] = percentiles[6]
return values
}
// Counter defines the interface for a monotonic increasing counter.
type Counter interface {
Value() any
Incr()
Add(n uint64)
}
type counter struct {
c uint64
}
func (c *counter) Incr() {
atomic.AddUint64(&c.c, 1)
}
func (c *counter) Add(n uint64) {
atomic.AddUint64(&c.c, n)
}
func (c *counter) Value() any {
return atomic.LoadUint64(&c.c)
}
func Statistics(num ...int64) any {
t := newHistogram()
for _, n := range num {
t.Update(n)
}
return t.Value()
}
type noOpMetrics struct{}
type noOpTimer struct{}
type noOpHistogram struct{}
type noOpCounter struct{}
var (
noOpMetricsInstance = &noOpMetrics{}
noOpTimerInstance = &noOpTimer{}
noOpHistogramInstance = &noOpHistogram{}
noOpCounterInstance = &noOpCounter{}
)
func (*noOpMetrics) Info() Info { return Info{Name: "<built-in no-op>"} }
func (*noOpMetrics) Timer(name string) Timer { return noOpTimerInstance }
func (*noOpMetrics) Histogram(name string) Histogram { return noOpHistogramInstance }
func (*noOpMetrics) Counter(name string) Counter { return noOpCounterInstance }
func (*noOpMetrics) All() map[string]any { return nil }
func (*noOpMetrics) Clear() {}
func (*noOpMetrics) MarshalJSON() ([]byte, error) {
return []byte(`{"name": "<built-in no-op>"}`), nil
}
func (*noOpTimer) Start() {}
func (*noOpTimer) Stop() int64 { return 0 }
func (*noOpTimer) Value() any { return 0 }
func (*noOpTimer) Int64() int64 { return 0 }
func (*noOpHistogram) Update(v int64) {}
func (*noOpHistogram) Value() any { return nil }
func (*noOpCounter) Incr() {}
func (*noOpCounter) Add(_ uint64) {}
func (*noOpCounter) Value() any { return 0 }
func (*noOpCounter) Int64() int64 { return 0 }