@@ -13,6 +13,7 @@ import (
1313 "google.golang.org/api/monitoring/v3"
1414
1515 "github.com/frodenas/stackdriver_exporter/utils"
16+ "sort"
1617)
1718
1819type 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