1package metricsutil 2 3import ( 4 "strings" 5 "sync/atomic" 6 "time" 7 8 metrics "github.com/armon/go-metrics" 9 "github.com/hashicorp/vault/helper/namespace" 10) 11 12// ClusterMetricSink serves as a shim around go-metrics 13// and inserts a "cluster" label. 14// 15// It also provides a mechanism to limit the cardinality of the labels on a gauge 16// (at each reporting interval, which isn't sufficient if there is variability in which 17// labels are the top N) and a backoff mechanism for gauge computation. 18type ClusterMetricSink struct { 19 // ClusterName is either the cluster ID, or a name provided 20 // in the telemetry configuration stanza. 21 // 22 // Because it may be set after the Core is initialized, we need 23 // to protect against concurrent access. 24 ClusterName atomic.Value 25 26 MaxGaugeCardinality int 27 GaugeInterval time.Duration 28 29 // Sink is the go-metrics instance to send to. 30 Sink metrics.MetricSink 31 32 // Constants that are helpful for metrics within the metrics sink 33 TelemetryConsts TelemetryConstConfig 34} 35 36type TelemetryConstConfig struct { 37 LeaseMetricsEpsilon time.Duration 38 NumLeaseMetricsTimeBuckets int 39 LeaseMetricsNameSpaceLabels bool 40} 41 42type Metrics interface { 43 SetGaugeWithLabels(key []string, val float32, labels []Label) 44 IncrCounterWithLabels(key []string, val float32, labels []Label) 45 AddSampleWithLabels(key []string, val float32, labels []Label) 46 AddDurationWithLabels(key []string, d time.Duration, labels []Label) 47 MeasureSinceWithLabels(key []string, start time.Time, labels []Label) 48} 49 50var _ Metrics = &ClusterMetricSink{} 51 52// Convenience alias 53type Label = metrics.Label 54 55func (m *ClusterMetricSink) SetGauge(key []string, val float32) { 56 m.Sink.SetGaugeWithLabels(key, val, []Label{{"cluster", m.ClusterName.Load().(string)}}) 57} 58 59func (m *ClusterMetricSink) SetGaugeWithLabels(key []string, val float32, labels []Label) { 60 m.Sink.SetGaugeWithLabels(key, val, 61 append(labels, Label{"cluster", m.ClusterName.Load().(string)})) 62} 63 64func (m *ClusterMetricSink) IncrCounterWithLabels(key []string, val float32, labels []Label) { 65 m.Sink.IncrCounterWithLabels(key, val, 66 append(labels, Label{"cluster", m.ClusterName.Load().(string)})) 67} 68 69func (m *ClusterMetricSink) AddSample(key []string, val float32) { 70 m.Sink.AddSampleWithLabels(key, val, []Label{{"cluster", m.ClusterName.Load().(string)}}) 71} 72 73func (m *ClusterMetricSink) AddSampleWithLabels(key []string, val float32, labels []Label) { 74 m.Sink.AddSampleWithLabels(key, val, 75 append(labels, Label{"cluster", m.ClusterName.Load().(string)})) 76} 77 78func (m *ClusterMetricSink) AddDurationWithLabels(key []string, d time.Duration, labels []Label) { 79 val := float32(d) / float32(time.Millisecond) 80 m.AddSampleWithLabels(key, val, labels) 81} 82 83func (m *ClusterMetricSink) MeasureSinceWithLabels(key []string, start time.Time, labels []Label) { 84 elapsed := time.Now().Sub(start) 85 val := float32(elapsed) / float32(time.Millisecond) 86 m.AddSampleWithLabels(key, val, labels) 87} 88 89// BlackholeSink is a default suitable for use in unit tests. 90func BlackholeSink() *ClusterMetricSink { 91 conf := metrics.DefaultConfig("") 92 conf.EnableRuntimeMetrics = false 93 sink, _ := metrics.New(conf, &metrics.BlackholeSink{}) 94 cms := &ClusterMetricSink{ 95 ClusterName: atomic.Value{}, 96 Sink: sink, 97 } 98 cms.ClusterName.Store("") 99 return cms 100} 101 102func NewClusterMetricSink(clusterName string, sink metrics.MetricSink) *ClusterMetricSink { 103 cms := &ClusterMetricSink{ 104 ClusterName: atomic.Value{}, 105 Sink: sink, 106 TelemetryConsts: TelemetryConstConfig{}, 107 } 108 cms.ClusterName.Store(clusterName) 109 return cms 110} 111 112// SetDefaultClusterName changes the cluster name from its default value, 113// if it has not previously been configured. 114func (m *ClusterMetricSink) SetDefaultClusterName(clusterName string) { 115 // This is not a true compare-and-swap, but it should be 116 // consistent enough for normal uses 117 if m.ClusterName.Load().(string) == "" { 118 m.ClusterName.Store(clusterName) 119 } 120} 121 122// NamespaceLabel creates a metrics label for the given 123// Namespace: root is "root"; others are path with the 124// final '/' removed. 125func NamespaceLabel(ns *namespace.Namespace) metrics.Label { 126 switch { 127 case ns == nil: 128 return metrics.Label{"namespace", "root"} 129 case ns.ID == namespace.RootNamespaceID: 130 return metrics.Label{"namespace", "root"} 131 default: 132 return metrics.Label{ 133 "namespace", 134 strings.Trim(ns.Path, "/"), 135 } 136 } 137} 138