@@ -8,8 +8,10 @@ package aggmetric
88import (
99 "math"
1010 "sync/atomic"
11+ "time"
1112
1213 "github.com/cockroachdb/cockroach/pkg/util/metric"
14+ "github.com/cockroachdb/cockroach/pkg/util/timeutil"
1315 "github.com/cockroachdb/errors"
1416 "github.com/gogo/protobuf/proto"
1517 io_prometheus_client "github.com/prometheus/client_model/go"
@@ -521,3 +523,212 @@ func (scg *SQLChildGauge) Inc(i int64) {
521523func (scg * SQLChildGauge ) Dec (i int64 ) {
522524 scg .gauge .Dec (i )
523525}
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