9
9
10
10
"github.com/coder/coder/v2/coderd/agentmetrics"
11
11
"github.com/prometheus/client_golang/prometheus"
12
+ "github.com/prometheus/common/model"
12
13
"golang.org/x/xerrors"
13
14
14
15
"cdr.dev/slog"
@@ -107,8 +108,9 @@ var _ prometheus.Collector = new(MetricsAggregator)
107
108
108
109
func (am * annotatedMetric ) asPrometheus () (prometheus.Metric , error ) {
109
110
var (
110
- baseLabelNames [] string = am .aggregateByLabels
111
+ baseLabelNames = am .aggregateByLabels
111
112
baseLabelValues []string
113
+ extraLabels = am .Labels
112
114
)
113
115
114
116
for _ , label := range am .aggregateByLabels {
@@ -120,19 +122,17 @@ func (am *annotatedMetric) asPrometheus() (prometheus.Metric, error) {
120
122
baseLabelValues = append (baseLabelValues , val )
121
123
}
122
124
123
- labels := make ([]string , 0 , len (baseLabelNames )+ len (am . Labels ))
124
- labelValues := make ([]string , 0 , len (baseLabelNames )+ len (am . Labels ))
125
+ labels := make ([]string , 0 , len (baseLabelNames )+ len (extraLabels ))
126
+ labelValues := make ([]string , 0 , len (baseLabelNames )+ len (extraLabels ))
125
127
126
128
labels = append (labels , baseLabelNames ... )
127
129
labelValues = append (labelValues , baseLabelValues ... )
128
130
129
- for _ , l := range am . Labels {
131
+ for _ , l := range extraLabels {
130
132
labels = append (labels , l .Name )
131
133
labelValues = append (labelValues , l .Value )
132
134
}
133
135
134
- //fmt.Printf(">>>>[%s] [%s] %s [%q] [%q]: %v\n", time.Now().Format(time.RFC3339Nano), am.Type, am.Name, labels, labelValues, am.Value)
135
-
136
136
desc := prometheus .NewDesc (am .Name , metricHelpForAgent , labels , nil )
137
137
valueType , err := asPrometheusValueType (am .Type )
138
138
if err != nil {
@@ -237,50 +237,56 @@ func NewMetricsAggregator(logger slog.Logger, registerer prometheus.Registerer,
237
237
}, nil
238
238
}
239
239
240
- type MetricAggregator struct {
240
+ // labelAggregator is used to control cardinality of collected Prometheus metrics by pre-aggregating series based on given labels.
241
+ type labelAggregator struct {
241
242
aggregations map [string ]float64
242
243
metrics map [string ]annotatedMetric
243
244
}
244
245
245
- func NewMetricAggregator (size int ) * MetricAggregator {
246
- return & MetricAggregator {
246
+ func newLabelAggregator (size int ) * labelAggregator {
247
+ return & labelAggregator {
247
248
aggregations : make (map [string ]float64 , size ),
248
249
metrics : make (map [string ]annotatedMetric , size ),
249
250
}
250
251
}
251
252
252
- func (a * MetricAggregator ) Aggregate (am annotatedMetric , labels []string ) error {
253
- // if we already have an entry for this key, don't clone this am afresh - rather use the existing one
254
- // this will be a bit more memory efficient
255
- // ...do this after unit-test is written
256
-
257
- clone := am .clone ()
258
-
259
- fields := make (map [string ]string , len (labels ))
253
+ func (a * labelAggregator ) aggregate (am annotatedMetric , labels []string ) error {
254
+ // Use a LabelSet because it can give deterministic fingerprints of label combinations regardless of map ordering.
255
+ labelSet := make (model.LabelSet , len (labels ))
260
256
labelValues := make ([]string , 0 , len (labels ))
261
257
262
258
for _ , label := range labels {
263
- val , err := clone .getFieldByLabel (label )
259
+ val , err := am .getFieldByLabel (label )
264
260
if err != nil {
265
261
return err
266
262
}
267
263
268
- fields [ label ] = val
264
+ labelSet [ model . LabelName ( label ) ] = model . LabelValue ( val )
269
265
labelValues = append (labelValues , val )
270
266
}
271
267
272
- key := fmt .Sprintf ("%s:%v" , clone .Stats_Metric .Name , fields )
268
+ // Memoize based on the metric name & the unique combination of labels.
269
+ key := fmt .Sprintf ("%s:%v" , am .Stats_Metric .Name , labelSet .FastFingerprint ())
270
+
271
+ // Aggregate the value based on the key.
272
+ a .aggregations [key ] += am .Value
273
+
274
+ metric , found := a .metrics [key ]
275
+ if ! found {
276
+ // Take a copy of the given annotatedMetric because it may be manipulated later and contains pointers.
277
+ metric = am .clone ()
278
+ }
273
279
274
- clone .aggregateByLabels = labels
275
- a .aggregations [key ] += clone .Value
280
+ // Store the metric.
281
+ metric .aggregateByLabels = labels
282
+ metric .Value = a .aggregations [key ]
276
283
277
- clone .Value = a .aggregations [key ]
278
- a .metrics [key ] = clone
284
+ a .metrics [key ] = metric
279
285
280
286
return nil
281
287
}
282
288
283
- func (a * MetricAggregator ) asMetrics () (out []annotatedMetric ) {
289
+ func (a * labelAggregator ) toMetrics () (out []annotatedMetric ) {
284
290
for _ , am := range a .metrics {
285
291
out = append (out , am )
286
292
}
@@ -331,24 +337,25 @@ func (ma *MetricsAggregator) Run(ctx context.Context) func() {
331
337
332
338
// If custom aggregation labels have not been chosen, generate Prometheus metrics without any pre-aggregation.
333
339
// This results in higher cardinality, but may be desirable in larger deployments.
340
+ // Default behaviour.
334
341
if len (ma .aggregateByLabels ) == 0 {
335
342
for _ , m := range ma .store {
336
- // Aggregate by high cardinality labels .
337
- m .aggregateByLabels = agentMetricsLabels
343
+ // Aggregate by all available metrics .
344
+ m .aggregateByLabels = defaultAgentMetricsLabels
338
345
input = append (input , m )
339
346
}
340
347
} else {
341
348
// However, if custom aggregations have been chosen, we need to aggregate the values from the annotated
342
349
// metrics because we cannot register multiple metric series with the same labels.
343
- aggregator := NewMetricAggregator (len (ma .store ) * len ( ma . aggregateByLabels ))
350
+ la := newLabelAggregator (len (ma .store ))
344
351
345
352
for _ , m := range ma .store {
346
- if err := aggregator . Aggregate (m , ma .aggregateByLabels ); err != nil {
353
+ if err := la . aggregate (m , ma .aggregateByLabels ); err != nil {
347
354
ma .log .Error (ctx , "can't aggregate labels" , slog .F ("labels" , strings .Join (ma .aggregateByLabels , "," )), slog .Error (err ))
348
355
}
349
356
}
350
357
351
- input = aggregator . asMetrics ()
358
+ input = la . toMetrics ()
352
359
}
353
360
354
361
for _ , m := range input {
@@ -395,7 +402,7 @@ func (ma *MetricsAggregator) Run(ctx context.Context) func() {
395
402
func (* MetricsAggregator ) Describe (_ chan <- * prometheus.Desc ) {
396
403
}
397
404
398
- var agentMetricsLabels = []string {agentmetrics .UsernameLabel , agentmetrics .WorkspaceNameLabel , agentmetrics .AgentNameLabel , agentmetrics .TemplateNameLabel }
405
+ var defaultAgentMetricsLabels = []string {agentmetrics .UsernameLabel , agentmetrics .WorkspaceNameLabel , agentmetrics .AgentNameLabel , agentmetrics .TemplateNameLabel }
399
406
400
407
// AgentMetricLabels are the labels used to decorate an agent's metrics.
401
408
// This list should match the list of labels in agentMetricsLabels.
0 commit comments