github.com/waldiirawan/apm-agent-go/v2@v2.2.2/metrics.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package apm // import "github.com/waldiirawan/apm-agent-go/v2" 19 20 import ( 21 "context" 22 "sort" 23 "strings" 24 "sync" 25 26 "github.com/waldiirawan/apm-agent-go/v2/internal/wildcard" 27 "github.com/waldiirawan/apm-agent-go/v2/model" 28 ) 29 30 // Metrics holds a set of metrics. 31 type Metrics struct { 32 disabled wildcard.Matchers 33 34 mu sync.Mutex 35 metrics []*model.Metrics 36 37 // transactionGroupMetrics holds metrics which are scoped to transaction 38 // groups, and are not sorted according to their labels. 39 transactionGroupMetrics []*model.Metrics 40 } 41 42 func (m *Metrics) reset() { 43 m.metrics = m.metrics[:0] 44 m.transactionGroupMetrics = m.transactionGroupMetrics[:0] 45 } 46 47 // MetricLabel is a name/value pair for labeling metrics. 48 type MetricLabel struct { 49 // Name is the label name. 50 Name string 51 52 // Value is the label value. 53 Value string 54 } 55 56 // MetricsGatherer provides an interface for gathering metrics. 57 type MetricsGatherer interface { 58 // GatherMetrics gathers metrics and adds them to m. 59 // 60 // If ctx.Done() is signaled, gathering should be aborted and 61 // ctx.Err() returned. If GatherMetrics returns an error, it 62 // will be logged, but otherwise there is no effect; the 63 // implementation must take care not to leave m in an invalid 64 // state due to errors. 65 GatherMetrics(ctx context.Context, m *Metrics) error 66 } 67 68 // GatherMetricsFunc is a function type implementing MetricsGatherer. 69 type GatherMetricsFunc func(context.Context, *Metrics) error 70 71 // GatherMetrics calls f(ctx, m). 72 func (f GatherMetricsFunc) GatherMetrics(ctx context.Context, m *Metrics) error { 73 return f(ctx, m) 74 } 75 76 // Add adds a metric with the given name, labels, and value, 77 // The labels are expected to be sorted lexicographically. 78 func (m *Metrics) Add(name string, labels []MetricLabel, value float64) { 79 m.addMetric(name, labels, model.Metric{Value: value}) 80 } 81 82 // AddHistogram adds a histogram metric with the given name, labels, counts, 83 // and values. The labels are expected to be sorted lexicographically, and 84 // bucket values provided in ascending order. 85 func (m *Metrics) AddHistogram(name string, labels []MetricLabel, values []float64, counts []uint64) { 86 m.addMetric(name, labels, model.Metric{Values: values, Counts: counts, Type: "histogram"}) 87 } 88 89 func (m *Metrics) addMetric(name string, labels []MetricLabel, metric model.Metric) { 90 if m.disabled.MatchAny(name) { 91 return 92 } 93 m.mu.Lock() 94 defer m.mu.Unlock() 95 96 var metrics *model.Metrics 97 results := make([]int, len(m.metrics)) 98 i := sort.Search(len(m.metrics), func(j int) bool { 99 results[j] = compareLabels(m.metrics[j].Labels, labels) 100 return results[j] >= 0 101 }) 102 if i < len(results) && results[i] == 0 { 103 // labels are equal 104 metrics = m.metrics[i] 105 } else { 106 var modelLabels model.StringMap 107 if len(labels) > 0 { 108 modelLabels = make(model.StringMap, len(labels)) 109 for i, l := range labels { 110 modelLabels[i] = model.StringMapItem{ 111 Key: l.Name, Value: l.Value, 112 } 113 } 114 } 115 metrics = &model.Metrics{ 116 Labels: modelLabels, 117 Samples: make(map[string]model.Metric), 118 } 119 if i == len(results) { 120 m.metrics = append(m.metrics, metrics) 121 } else { 122 m.metrics = append(m.metrics, nil) 123 copy(m.metrics[i+1:], m.metrics[i:]) 124 m.metrics[i] = metrics 125 } 126 } 127 metrics.Samples[name] = metric 128 } 129 130 func compareLabels(a model.StringMap, b []MetricLabel) int { 131 na, nb := len(a), len(b) 132 n := na 133 if na > nb { 134 n = nb 135 } 136 for i := 0; i < n; i++ { 137 la, lb := a[i], b[i] 138 d := strings.Compare(la.Key, lb.Name) 139 if d == 0 { 140 d = strings.Compare(la.Value, lb.Value) 141 } 142 if d != 0 { 143 return d 144 } 145 } 146 switch { 147 case na < nb: 148 return -1 149 case na > nb: 150 return 1 151 } 152 return 0 153 } 154 155 func gatherMetrics(ctx context.Context, g MetricsGatherer, m *Metrics, logger Logger) { 156 defer func() { 157 if r := recover(); r != nil { 158 if logger != nil { 159 logger.Debugf("%T.GatherMetrics panicked: %s", g, r) 160 } 161 } 162 }() 163 if err := g.GatherMetrics(ctx, m); err != nil { 164 if logger != nil && err != context.Canceled { 165 logger.Debugf("%T.GatherMetrics failed: %s", g, err) 166 } 167 } 168 }