Skip to content

Commit f49ceb0

Browse files
committed
Collect metrics to fill missing labels
1 parent de048a0 commit f49ceb0

File tree

2 files changed

+274
-27
lines changed

2 files changed

+274
-27
lines changed

collectors/fnv.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package collectors
2+
3+
const separatorByte = 255
4+
5+
// https://github.com/prometheus/client_golang/blob/master/prometheus/fnv.go
6+
// Inline and byte-free variant of hash/fnv's fnv64a.
7+
8+
const (
9+
offset64 = 14695981039346656037
10+
prime64 = 1099511628211
11+
)
12+
13+
// hashNew initializies a new fnv64a hash value.
14+
func hashNew() uint64 {
15+
return offset64
16+
}
17+
18+
// hashAdd adds a string to a fnv64a hash value, returning the updated hash.
19+
func hashAdd(h uint64, s string) uint64 {
20+
for i := 0; i < len(s); i++ {
21+
h ^= uint64(s[i])
22+
h *= prime64
23+
}
24+
return h
25+
}
26+
27+
// hashAddByte adds a byte to a fnv64a hash value, returning the updated hash.
28+
func hashAddByte(h uint64, b byte) uint64 {
29+
h ^= uint64(b)
30+
h *= prime64
31+
return h
32+
}

collectors/monitoring_collector.go

Lines changed: 242 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"google.golang.org/api/monitoring/v3"
1414

1515
"github.com/frodenas/stackdriver_exporter/utils"
16+
"sort"
1617
)
1718

1819
type MonitoringCollector struct {
@@ -231,8 +232,14 @@ func (c *MonitoringCollector) reportTimeSeriesMetrics(
231232
var metricValue float64
232233
var metricValueType prometheus.ValueType
233234
var newestTSPoint *monitoring.Point
234-
var metricDesc *prometheus.Desc
235235

236+
timeSeriesMetrics := &TimeSeriesMetrics{
237+
metricDescriptor: metricDescriptor,
238+
ch: ch,
239+
fillMissingLabels: true, //TODO: this can be configurable
240+
constMetrics: make(map[string][]ConstMetric),
241+
histogramMetrics: make(map[string][]HistogramMetric),
242+
}
236243
for _, timeSeries := range page.TimeSeries {
237244
newestEndTime := time.Unix(0, 0)
238245
for _, point := range timeSeries.Points {
@@ -245,7 +252,6 @@ func (c *MonitoringCollector) reportTimeSeriesMetrics(
245252
newestTSPoint = point
246253
}
247254
}
248-
249255
labelKeys := []string{"unit"}
250256
labelValues := []string{metricDescriptor.Unit}
251257

@@ -263,17 +269,6 @@ func (c *MonitoringCollector) reportTimeSeriesMetrics(
263269
labelValues = append(labelValues, value)
264270
}
265271

266-
// The metric name to report is composed by the 3 parts:
267-
// 1. namespace is a constant prefix (stackdriver)
268-
// 2. subsystem is the monitored resource type (ie gce_instance)
269-
// 3. name is the metric type (ie compute.googleapis.com/instance/cpu/usage_time)
270-
metricDesc = prometheus.NewDesc(
271-
prometheus.BuildFQName("stackdriver", utils.NormalizeMetricName(timeSeries.Resource.Type), utils.NormalizeMetricName(timeSeries.Metric.Type)),
272-
metricDescriptor.Description,
273-
labelKeys,
274-
prometheus.Labels{},
275-
)
276-
277272
switch timeSeries.MetricKind {
278273
case "GAUGE":
279274
metricValueType = prometheus.GaugeValue
@@ -299,13 +294,7 @@ func (c *MonitoringCollector) reportTimeSeriesMetrics(
299294
dist := newestTSPoint.Value.DistributionValue
300295
buckets, err := c.generateHistogramBuckets(dist)
301296
if err == nil {
302-
ch <- prometheus.MustNewConstHistogram(
303-
metricDesc,
304-
uint64(dist.Count),
305-
dist.Mean*float64(dist.Count), // Stackdriver does not provide the sum, but we can fake it
306-
buckets,
307-
labelValues...,
308-
)
297+
timeSeriesMetrics.CollectNewConstHistogram(timeSeries, labelKeys, dist, buckets, labelValues)
309298
} else {
310299
log.Debugf("Discarding resource %s metric %s: %s", timeSeries.Resource.Type, timeSeries.Metric.Type, err)
311300
}
@@ -315,14 +304,9 @@ func (c *MonitoringCollector) reportTimeSeriesMetrics(
315304
continue
316305
}
317306

318-
ch <- prometheus.MustNewConstMetric(
319-
metricDesc,
320-
metricValueType,
321-
metricValue,
322-
labelValues...,
323-
)
307+
timeSeriesMetrics.CollectNewConstMetric(timeSeries, labelKeys, metricValueType, metricValue, labelValues)
324308
}
325-
309+
timeSeriesMetrics.Complete()
326310
return nil
327311
}
328312

@@ -378,3 +362,234 @@ func (c *MonitoringCollector) generateHistogramBuckets(
378362
}
379363
return buckets, nil
380364
}
365+
366+
func buildFQName(timeSeries *monitoring.TimeSeries) string {
367+
// The metric name to report is composed by the 3 parts:
368+
// 1. namespace is a constant prefix (stackdriver)
369+
// 2. subsystem is the monitored resource type (ie gce_instance)
370+
// 3. name is the metric type (ie compute.googleapis.com/instance/cpu/usage_time)
371+
return prometheus.BuildFQName("stackdriver", utils.NormalizeMetricName(timeSeries.Resource.Type), utils.NormalizeMetricName(timeSeries.Metric.Type))
372+
}
373+
374+
type TimeSeriesMetrics struct {
375+
metricDescriptor *monitoring.MetricDescriptor
376+
ch chan<- prometheus.Metric
377+
378+
fillMissingLabels bool
379+
constMetrics map[string][]ConstMetric
380+
histogramMetrics map[string][]HistogramMetric
381+
}
382+
383+
func (t *TimeSeriesMetrics) NewMetricDesc(fqName string, labelKeys []string) *prometheus.Desc {
384+
return prometheus.NewDesc(
385+
fqName,
386+
t.metricDescriptor.Description,
387+
labelKeys,
388+
prometheus.Labels{},
389+
)
390+
}
391+
392+
type ConstMetric struct {
393+
fqName string
394+
labelKeys []string
395+
valueType prometheus.ValueType
396+
value float64
397+
labelValues []string
398+
399+
keysHash uint64
400+
}
401+
402+
type HistogramMetric struct {
403+
fqName string
404+
labelKeys []string
405+
dist *monitoring.Distribution
406+
buckets map[float64]uint64
407+
labelValues []string
408+
409+
keysHash uint64
410+
}
411+
412+
func (t *TimeSeriesMetrics) CollectNewConstHistogram(timeSeries *monitoring.TimeSeries, labelKeys []string, dist *monitoring.Distribution, buckets map[float64]uint64, labelValues []string) {
413+
fqName := buildFQName(timeSeries)
414+
415+
if t.fillMissingLabels {
416+
vs, ok := t.histogramMetrics[fqName]
417+
if !ok {
418+
vs = make([]HistogramMetric, 0)
419+
}
420+
v := HistogramMetric{
421+
fqName: fqName,
422+
labelKeys: labelKeys,
423+
dist: dist,
424+
buckets: buckets,
425+
labelValues: labelValues,
426+
427+
keysHash: hashLabelKeys(labelKeys),
428+
}
429+
t.histogramMetrics[fqName] = append(vs, v)
430+
return
431+
}
432+
433+
metric := prometheus.MustNewConstHistogram(
434+
t.NewMetricDesc(fqName, labelKeys),
435+
uint64(dist.Count),
436+
dist.Mean*float64(dist.Count), // Stackdriver does not provide the sum, but we can fake it
437+
buckets,
438+
labelValues...,
439+
)
440+
t.ch <- metric
441+
}
442+
443+
func (t *TimeSeriesMetrics) CollectNewConstMetric(timeSeries *monitoring.TimeSeries, labelKeys []string, metricValueType prometheus.ValueType, metricValue float64, labelValues []string) {
444+
fqName := buildFQName(timeSeries)
445+
446+
if t.fillMissingLabels {
447+
vs, ok := t.constMetrics[fqName]
448+
if !ok {
449+
vs = make([]ConstMetric, 0)
450+
}
451+
v := ConstMetric{
452+
fqName: fqName,
453+
labelKeys: labelKeys,
454+
valueType: metricValueType,
455+
value: metricValue,
456+
labelValues: labelValues,
457+
458+
keysHash: hashLabelKeys(labelKeys),
459+
}
460+
t.constMetrics[fqName] = append(vs, v)
461+
return
462+
}
463+
464+
metric := prometheus.MustNewConstMetric(
465+
t.NewMetricDesc(fqName, labelKeys),
466+
metricValueType,
467+
metricValue,
468+
labelValues...,
469+
)
470+
t.ch <- metric
471+
}
472+
473+
func hashLabelKeys(labelKeys []string) uint64 {
474+
dh := hashNew()
475+
sortedKeys := make([]string, len(labelKeys))
476+
copy(sortedKeys, labelKeys)
477+
sort.Strings(sortedKeys)
478+
for _, key := range sortedKeys {
479+
dh = hashAdd(dh, key)
480+
dh = hashAddByte(dh, separatorByte)
481+
}
482+
return dh
483+
}
484+
485+
func (t *TimeSeriesMetrics) Complete() {
486+
t.CompleteConstMetrics()
487+
t.CompleteHistogramMetrics()
488+
}
489+
490+
func (t *TimeSeriesMetrics) CompleteConstMetrics() {
491+
for _, vs := range t.constMetrics {
492+
if len(vs) > 1 {
493+
var needFill bool
494+
for i := 1; i < len(vs); i++ {
495+
if vs[0].keysHash != vs[i].keysHash {
496+
needFill = true
497+
}
498+
}
499+
if needFill {
500+
vs = fillConstMetricsLabels(vs)
501+
}
502+
}
503+
504+
for _, v := range vs {
505+
metric := prometheus.MustNewConstMetric(
506+
t.NewMetricDesc(v.fqName, v.labelKeys),
507+
v.valueType,
508+
v.value,
509+
v.labelValues...,
510+
)
511+
t.ch <- metric
512+
}
513+
}
514+
}
515+
516+
func (t *TimeSeriesMetrics) CompleteHistogramMetrics() {
517+
for _, vs := range t.histogramMetrics {
518+
if len(vs) > 1 {
519+
var needFill bool
520+
for i := 1; i < len(vs); i++ {
521+
if vs[0].keysHash != vs[i].keysHash {
522+
needFill = true
523+
}
524+
}
525+
if needFill {
526+
vs = fillHistogramMetricsLabels(vs)
527+
}
528+
}
529+
530+
for _, v := range vs {
531+
metric := prometheus.MustNewConstHistogram(
532+
t.NewMetricDesc(v.fqName, v.labelKeys),
533+
uint64(v.dist.Count),
534+
v.dist.Mean*float64(v.dist.Count), // Stackdriver does not provide the sum, but we can fake it
535+
v.buckets,
536+
v.labelValues...,
537+
)
538+
t.ch <- metric
539+
}
540+
}
541+
}
542+
543+
func fillConstMetricsLabels(metrics []ConstMetric) []ConstMetric {
544+
allKeys := make(map[string]struct{})
545+
for _, metric := range metrics {
546+
for _, key := range metric.labelKeys {
547+
allKeys[key] = struct{}{}
548+
}
549+
}
550+
result := make([]ConstMetric, len(metrics))
551+
for i, metric := range metrics {
552+
if len(metric.labelKeys) != len(allKeys) {
553+
metricKeys := make(map[string]struct{})
554+
for _, key := range metric.labelKeys {
555+
metricKeys[key] = struct{}{}
556+
}
557+
for key := range allKeys {
558+
if _, ok := metricKeys[key]; !ok {
559+
metric.labelKeys = append(metric.labelKeys, key)
560+
metric.labelValues = append(metric.labelValues, "")
561+
}
562+
}
563+
}
564+
result[i] = metric
565+
}
566+
567+
return result
568+
}
569+
570+
func fillHistogramMetricsLabels(metrics []HistogramMetric) []HistogramMetric {
571+
allKeys := make(map[string]struct{})
572+
for _, metric := range metrics {
573+
for _, key := range metric.labelKeys {
574+
allKeys[key] = struct{}{}
575+
}
576+
}
577+
result := make([]HistogramMetric, len(metrics))
578+
for i, metric := range metrics {
579+
if len(metric.labelKeys) != len(allKeys) {
580+
metricKeys := make(map[string]struct{})
581+
for _, key := range metric.labelKeys {
582+
metricKeys[key] = struct{}{}
583+
}
584+
for key := range allKeys {
585+
if _, ok := metricKeys[key]; !ok {
586+
metric.labelKeys = append(metric.labelKeys, key)
587+
metric.labelValues = append(metric.labelValues, "")
588+
}
589+
}
590+
}
591+
result[i] = metric
592+
}
593+
594+
return result
595+
}

0 commit comments

Comments
 (0)