@@ -8,8 +8,10 @@ package aggmetric
8
8
import (
9
9
"math"
10
10
"sync/atomic"
11
+ "time"
11
12
12
13
"github.com/cockroachdb/cockroach/pkg/util/metric"
14
+ "github.com/cockroachdb/cockroach/pkg/util/timeutil"
13
15
"github.com/cockroachdb/errors"
14
16
"github.com/gogo/protobuf/proto"
15
17
io_prometheus_client "github.com/prometheus/client_model/go"
@@ -521,3 +523,212 @@ func (scg *SQLChildGauge) Inc(i int64) {
521
523
func (scg * SQLChildGauge ) Dec (i int64 ) {
522
524
scg .gauge .Dec (i )
523
525
}
526
+
527
+ // HighCardinalityGauge is similar to AggGauge but uses cache storage instead of B-tree,
528
+ // allowing for automatic eviction of less frequently used child metrics.
529
+ // This is useful when dealing with high cardinality metrics that might exceed resource limits.
530
+ type HighCardinalityGauge struct {
531
+ g metric.Gauge
532
+ childSet
533
+ labelSliceCache * metric.LabelSliceCache
534
+ }
535
+
536
+ var _ metric.Iterable = (* HighCardinalityGauge )(nil )
537
+ var _ metric.PrometheusEvictable = (* HighCardinalityGauge )(nil )
538
+
539
+ // NewHighCardinalityGauge constructs a new HighCardinalityGauge that uses cache storage
540
+ // with eviction for child metrics.
541
+ func NewHighCardinalityGauge (
542
+ metadata metric.Metadata , childLabels ... string ,
543
+ ) * HighCardinalityGauge {
544
+ g := & HighCardinalityGauge {g : * metric .NewGauge (metadata )}
545
+ g .initWithCacheStorageType (childLabels , metadata .Name )
546
+ return g
547
+ }
548
+
549
+ // GetName is part of the metric.Iterable interface.
550
+ func (g * HighCardinalityGauge ) GetName (useStaticLabels bool ) string {
551
+ return g .g .GetName (useStaticLabels )
552
+ }
553
+
554
+ // GetHelp is part of the metric.Iterable interface.
555
+ func (g * HighCardinalityGauge ) GetHelp () string { return g .g .GetHelp () }
556
+
557
+ // GetMeasurement is part of the metric.Iterable interface.
558
+ func (g * HighCardinalityGauge ) GetMeasurement () string { return g .g .GetMeasurement () }
559
+
560
+ // GetUnit is part of the metric.Iterable interface.
561
+ func (g * HighCardinalityGauge ) GetUnit () metric.Unit { return g .g .GetUnit () }
562
+
563
+ // GetMetadata is part of the metric.Iterable interface.
564
+ func (g * HighCardinalityGauge ) GetMetadata () metric.Metadata { return g .g .GetMetadata () }
565
+
566
+ // Inspect is part of the metric.Iterable interface.
567
+ func (g * HighCardinalityGauge ) Inspect (f func (interface {})) { f (g ) }
568
+
569
+ // GetType is part of the metric.PrometheusExportable interface.
570
+ func (g * HighCardinalityGauge ) GetType () * io_prometheus_client.MetricType {
571
+ return g .g .GetType ()
572
+ }
573
+
574
+ // GetLabels is part of the metric.PrometheusExportable interface.
575
+ func (g * HighCardinalityGauge ) GetLabels (useStaticLabels bool ) []* io_prometheus_client.LabelPair {
576
+ return g .g .GetLabels (useStaticLabels )
577
+ }
578
+
579
+ // ToPrometheusMetric is part of the metric.PrometheusExportable interface.
580
+ func (g * HighCardinalityGauge ) ToPrometheusMetric () * io_prometheus_client.Metric {
581
+ return g .g .ToPrometheusMetric ()
582
+ }
583
+
584
+ // Value returns the aggregate sum of all of its current children.
585
+ func (g * HighCardinalityGauge ) Value () int64 {
586
+ return g .g .Value ()
587
+ }
588
+
589
+ // Inc increments the gauge value by i for the given label values. If a
590
+ // gauge with the given label values doesn't exist yet, it creates a new
591
+ // gauge and increments it. Inc increments parent metrics as well.
592
+ func (g * HighCardinalityGauge ) Inc (i int64 , labelValues ... string ) {
593
+ g .g .Inc (i )
594
+
595
+ childMetric := g .GetOrAddChild (labelValues ... )
596
+
597
+ if childMetric != nil {
598
+ childMetric .Inc (i )
599
+ }
600
+ }
601
+
602
+ // Dec decrements the gauge value by i for the given label values. If a
603
+ // gauge with the given label values doesn't exist yet, it creates a new
604
+ // gauge and decrements it. Dec decrements parent metrics as well.
605
+ func (g * HighCardinalityGauge ) Dec (i int64 , labelValues ... string ) {
606
+ g .g .Dec (i )
607
+
608
+ childMetric := g .GetOrAddChild (labelValues ... )
609
+
610
+ if childMetric != nil {
611
+ childMetric .Dec (i )
612
+ }
613
+ }
614
+
615
+ // Update sets the gauge value to val for the given label values. If a
616
+ // gauge with the given label values doesn't exist yet, it creates a new
617
+ // gauge and sets it. Update updates parent metrics as well.
618
+ //
619
+ // The parent metric value represents the sum of all children. The parent
620
+ // metric is updated by the delta (new - old) to maintain the aggregate
621
+ // sum of all children.
622
+ // For example, if a child gauge changes from 10 to 25, the
623
+ // parent is incremented by 15, preserving the total sum across all children.
624
+ func (g * HighCardinalityGauge ) Update (val int64 , labelValues ... string ) {
625
+ childMetric := g .GetOrAddChild (labelValues ... )
626
+
627
+ if childMetric != nil {
628
+ old := childMetric .Value ()
629
+ // Increment the parent by the delta of the child metric.
630
+ g .g .Inc (val - old )
631
+ childMetric .Update (val )
632
+ }
633
+ }
634
+
635
+ // Each is part of the metric.PrometheusIterable interface.
636
+ func (g * HighCardinalityGauge ) Each (
637
+ labels []* io_prometheus_client.LabelPair , f func (metric * io_prometheus_client.Metric ),
638
+ ) {
639
+ g .EachWithLabels (labels , f , g .labelSliceCache )
640
+ }
641
+
642
+ // InitializeMetrics is part of the PrometheusEvictable interface.
643
+ func (g * HighCardinalityGauge ) InitializeMetrics (labelCache * metric.LabelSliceCache ) {
644
+ g .mu .Lock ()
645
+ defer g .mu .Unlock ()
646
+
647
+ g .labelSliceCache = labelCache
648
+ }
649
+
650
+ // GetOrAddChild returns the existing child gauge for the given label values,
651
+ // or creates a new one if it doesn't exist. This is the preferred method for
652
+ // cache-based storage to avoid panics on existing keys.
653
+ func (g * HighCardinalityGauge ) GetOrAddChild (labelVals ... string ) * HighCardinalityChildGauge {
654
+
655
+ if len (labelVals ) == 0 {
656
+ return nil
657
+ }
658
+
659
+ // Create a LabelSliceCacheKey from the tenantID.
660
+ key := metric .LabelSliceCacheKey (metricKey (labelVals ... ))
661
+
662
+ child := g .getOrAddWithLabelSliceCache (g .GetMetadata ().Name , g .createHighCardinalityChildGauge , g .labelSliceCache , labelVals ... )
663
+
664
+ g .labelSliceCache .Upsert (key , & metric.LabelSliceCacheValue {
665
+ LabelValues : labelVals ,
666
+ })
667
+
668
+ return child .(* HighCardinalityChildGauge )
669
+ }
670
+
671
+ func (g * HighCardinalityGauge ) createHighCardinalityChildGauge (
672
+ key uint64 , cache * metric.LabelSliceCache ,
673
+ ) LabelSliceCachedChildMetric {
674
+ return & HighCardinalityChildGauge {
675
+ LabelSliceCacheKey : metric .LabelSliceCacheKey (key ),
676
+ LabelSliceCache : cache ,
677
+ createdAt : timeutil .Now (),
678
+ }
679
+ }
680
+
681
+ // HighCardinalityChildGauge is a child of a HighCardinalityGauge. When metrics are
682
+ // collected by prometheus, each of the children will appear with a distinct label,
683
+ // however, when cockroach internally collects metrics, only the parent is collected.
684
+ type HighCardinalityChildGauge struct {
685
+ metric.LabelSliceCacheKey
686
+ value metric.Gauge
687
+ * metric.LabelSliceCache
688
+ createdAt time.Time
689
+ }
690
+
691
+ func (g * HighCardinalityChildGauge ) CreatedAt () time.Time {
692
+ return g .createdAt
693
+ }
694
+
695
+ func (g * HighCardinalityChildGauge ) DecrementLabelSliceCacheReference () {
696
+ g .LabelSliceCache .DecrementAndDeleteIfZero (g .LabelSliceCacheKey )
697
+ }
698
+
699
+ // ToPrometheusMetric constructs a prometheus metric for this HighCardinalityChildGauge.
700
+ func (g * HighCardinalityChildGauge ) ToPrometheusMetric () * io_prometheus_client.Metric {
701
+ return & io_prometheus_client.Metric {
702
+ Gauge : & io_prometheus_client.Gauge {
703
+ Value : proto .Float64 (float64 (g .Value ())),
704
+ },
705
+ }
706
+ }
707
+
708
+ func (g * HighCardinalityChildGauge ) labelValues () []string {
709
+ lv , ok := g .LabelSliceCache .Get (g .LabelSliceCacheKey )
710
+ if ! ok {
711
+ return nil
712
+ }
713
+ return lv .LabelValues
714
+ }
715
+
716
+ // Value returns the HighCardinalityChildGauge's current value.
717
+ func (g * HighCardinalityChildGauge ) Value () int64 {
718
+ return g .value .Value ()
719
+ }
720
+
721
+ // Inc increments the HighCardinalityChildGauge's value.
722
+ func (g * HighCardinalityChildGauge ) Inc (i int64 ) {
723
+ g .value .Inc (i )
724
+ }
725
+
726
+ // Dec decrements the HighCardinalityChildGauge's value.
727
+ func (g * HighCardinalityChildGauge ) Dec (i int64 ) {
728
+ g .value .Dec (i )
729
+ }
730
+
731
+ // Update sets the HighCardinalityChildGauge's value.
732
+ func (g * HighCardinalityChildGauge ) Update (val int64 ) {
733
+ g .value .Update (val )
734
+ }
0 commit comments