@@ -357,3 +357,220 @@ func (sch *SQLChildHistogram) RecordValue(v int64) {
357
357
func (sch * SQLChildHistogram ) Value () metric.HistogramSnapshot {
358
358
return sch .h .CumulativeSnapshot ()
359
359
}
360
+
361
+ // HighCardinalityHistogram is similar to AggHistogram but uses cache storage instead of B-tree,
362
+ // allowing for automatic eviction of less frequently used child metrics.
363
+ // This is useful when dealing with high cardinality metrics that might exceed resource limits.
364
+ type HighCardinalityHistogram struct {
365
+ h metric.IHistogram
366
+ create func () metric.IHistogram
367
+ childSet
368
+ labelSliceCache * metric.LabelSliceCache
369
+ ticker struct {
370
+ // We use a RWMutex, because we don't want child histograms to contend when
371
+ // recording values, unless we're rotating histograms for the parent & children.
372
+ // In this instance, the "writer" for the RWMutex is the ticker, and the "readers"
373
+ // are all the child histograms recording their values.
374
+ syncutil.RWMutex
375
+ * tick.Ticker
376
+ }
377
+ }
378
+
379
+ var _ metric.Iterable = (* HighCardinalityHistogram )(nil )
380
+ var _ metric.PrometheusEvictable = (* HighCardinalityHistogram )(nil )
381
+ var _ metric.WindowedHistogram = (* HighCardinalityHistogram )(nil )
382
+ var _ metric.CumulativeHistogram = (* HighCardinalityHistogram )(nil )
383
+
384
+ // NewHighCardinalityHistogram constructs a new HighCardinalityHistogram that uses cache storage
385
+ // with eviction for child metrics.
386
+ func NewHighCardinalityHistogram (
387
+ opts metric.HistogramOptions , childLabels ... string ,
388
+ ) * HighCardinalityHistogram {
389
+ create := func () metric.IHistogram {
390
+ return metric .NewHistogram (opts )
391
+ }
392
+ h := & HighCardinalityHistogram {
393
+ h : create (),
394
+ create : create ,
395
+ }
396
+ h .ticker .Ticker = tick .NewTicker (
397
+ now (),
398
+ opts .Duration / metric .WindowedHistogramWrapNum ,
399
+ func () {
400
+ // Atomically rotate the histogram window for the
401
+ // parent histogram, and all the child histograms.
402
+ h .h .Tick ()
403
+ h .childSet .apply (func (childItem MetricItem ) {
404
+ childHist , ok := childItem .(* HighCardinalityChildHistogram )
405
+ if ! ok {
406
+ panic (errors .AssertionFailedf (
407
+ "unable to assert type of child for histogram %q when rotating histogram windows" ,
408
+ opts .Metadata .Name ))
409
+ }
410
+ childHist .h .Tick ()
411
+ })
412
+ })
413
+ h .initWithCacheStorageType (childLabels , opts .Metadata .Name )
414
+ return h
415
+ }
416
+
417
+ // GetName is part of the metric.Iterable interface.
418
+ func (h * HighCardinalityHistogram ) GetName (useStaticLabels bool ) string {
419
+ return h .h .GetName (useStaticLabels )
420
+ }
421
+
422
+ // GetHelp is part of the metric.Iterable interface.
423
+ func (h * HighCardinalityHistogram ) GetHelp () string { return h .h .GetHelp () }
424
+
425
+ // GetMeasurement is part of the metric.Iterable interface.
426
+ func (h * HighCardinalityHistogram ) GetMeasurement () string { return h .h .GetMeasurement () }
427
+
428
+ // GetUnit is part of the metric.Iterable interface.
429
+ func (h * HighCardinalityHistogram ) GetUnit () metric.Unit { return h .h .GetUnit () }
430
+
431
+ // GetMetadata is part of the metric.Iterable interface.
432
+ func (h * HighCardinalityHistogram ) GetMetadata () metric.Metadata { return h .h .GetMetadata () }
433
+
434
+ // Inspect is part of the metric.Iterable interface.
435
+ func (h * HighCardinalityHistogram ) Inspect (f func (interface {})) {
436
+ func () {
437
+ h .ticker .Lock ()
438
+ defer h .ticker .Unlock ()
439
+ tick .MaybeTick (& h .ticker )
440
+ }()
441
+ f (h )
442
+ }
443
+
444
+ // CumulativeSnapshot is part of the metric.CumulativeHistogram interface.
445
+ func (h * HighCardinalityHistogram ) CumulativeSnapshot () metric.HistogramSnapshot {
446
+ return h .h .CumulativeSnapshot ()
447
+ }
448
+
449
+ // WindowedSnapshot is part of the metric.WindowedHistogram interface.
450
+ func (h * HighCardinalityHistogram ) WindowedSnapshot () metric.HistogramSnapshot {
451
+ return h .h .WindowedSnapshot ()
452
+ }
453
+
454
+ // GetType is part of the metric.PrometheusExportable interface.
455
+ func (h * HighCardinalityHistogram ) GetType () * prometheusgo.MetricType {
456
+ return h .h .GetType ()
457
+ }
458
+
459
+ // GetLabels is part of the metric.PrometheusExportable interface.
460
+ func (h * HighCardinalityHistogram ) GetLabels (useStaticLabels bool ) []* prometheusgo.LabelPair {
461
+ return h .h .GetLabels (useStaticLabels )
462
+ }
463
+
464
+ // ToPrometheusMetric is part of the metric.PrometheusExportable interface.
465
+ func (h * HighCardinalityHistogram ) ToPrometheusMetric () * prometheusgo.Metric {
466
+ return h .h .ToPrometheusMetric ()
467
+ }
468
+
469
+ // RecordValue records the histogram value for the given label values. If a
470
+ // histogram with the given label values doesn't exist yet, it creates a new
471
+ // histogram and records against it. RecordValue records value in parent metrics as well.
472
+ func (h * HighCardinalityHistogram ) RecordValue (v int64 , labelValues ... string ) {
473
+ childMetric := h .GetOrAddChild (labelValues ... )
474
+
475
+ h .ticker .RLock ()
476
+ defer h .ticker .RUnlock ()
477
+
478
+ h .h .RecordValue (v )
479
+ if childMetric != nil {
480
+ childMetric .RecordValue (v )
481
+ }
482
+ }
483
+
484
+ // Each is part of the metric.PrometheusIterable interface.
485
+ func (h * HighCardinalityHistogram ) Each (
486
+ labels []* prometheusgo.LabelPair , f func (metric * prometheusgo.Metric ),
487
+ ) {
488
+ h .EachWithLabels (labels , f , h .labelSliceCache )
489
+ }
490
+
491
+ // InitializeMetrics is part of the PrometheusEvictable interface.
492
+ func (h * HighCardinalityHistogram ) InitializeMetrics (labelCache * metric.LabelSliceCache ) {
493
+ h .mu .Lock ()
494
+ defer h .mu .Unlock ()
495
+
496
+ h .labelSliceCache = labelCache
497
+ }
498
+
499
+ // GetOrAddChild returns the existing child histogram for the given label values,
500
+ // or creates a new one if it doesn't exist. This is the preferred method for
501
+ // cache-based storage to avoid panics on existing keys.
502
+ func (h * HighCardinalityHistogram ) GetOrAddChild (
503
+ labelVals ... string ,
504
+ ) * HighCardinalityChildHistogram {
505
+ if len (labelVals ) == 0 {
506
+ return nil
507
+ }
508
+
509
+ // Create a LabelSliceCacheKey from the labelVals.
510
+ key := metric .LabelSliceCacheKey (metricKey (labelVals ... ))
511
+
512
+ child := h .getOrAddWithLabelSliceCache (h .GetMetadata ().Name , h .createHighCardinalityChildHistogram , h .labelSliceCache , labelVals ... )
513
+
514
+ h .labelSliceCache .Upsert (key , & metric.LabelSliceCacheValue {
515
+ LabelValues : labelVals ,
516
+ })
517
+
518
+ return child .(* HighCardinalityChildHistogram )
519
+ }
520
+
521
+ func (h * HighCardinalityHistogram ) createHighCardinalityChildHistogram (
522
+ key uint64 , cache * metric.LabelSliceCache ,
523
+ ) LabelSliceCachedChildMetric {
524
+ return & HighCardinalityChildHistogram {
525
+ LabelSliceCacheKey : metric .LabelSliceCacheKey (key ),
526
+ LabelSliceCache : cache ,
527
+ h : h .create (),
528
+ createdAt : timeutil .Now (),
529
+ }
530
+ }
531
+
532
+ // HighCardinalityChildHistogram is a child of a HighCardinalityHistogram. When metrics are
533
+ // collected by prometheus, each of the children will appear with a distinct label,
534
+ // however, when cockroach internally collects metrics, only the parent is collected.
535
+ type HighCardinalityChildHistogram struct {
536
+ metric.LabelSliceCacheKey
537
+ h metric.IHistogram
538
+ * metric.LabelSliceCache
539
+ createdAt time.Time
540
+ }
541
+
542
+ func (h * HighCardinalityChildHistogram ) CreatedAt () time.Time {
543
+ return h .createdAt
544
+ }
545
+
546
+ func (h * HighCardinalityChildHistogram ) DecrementLabelSliceCacheReference () {
547
+ h .LabelSliceCache .DecrementAndDeleteIfZero (h .LabelSliceCacheKey )
548
+ }
549
+
550
+ // ToPrometheusMetric constructs a prometheus metric for this HighCardinalityChildHistogram.
551
+ func (h * HighCardinalityChildHistogram ) ToPrometheusMetric () * prometheusgo.Metric {
552
+ return h .h .ToPrometheusMetric ()
553
+ }
554
+
555
+ func (h * HighCardinalityChildHistogram ) labelValues () []string {
556
+ lv , ok := h .LabelSliceCache .Get (h .LabelSliceCacheKey )
557
+ if ! ok {
558
+ return nil
559
+ }
560
+ return lv .LabelValues
561
+ }
562
+
563
+ // RecordValue records the histogram value.
564
+ func (h * HighCardinalityChildHistogram ) RecordValue (v int64 ) {
565
+ h .h .RecordValue (v )
566
+ }
567
+
568
+ // CumulativeSnapshot returns the cumulative histogram snapshot.
569
+ func (h * HighCardinalityChildHistogram ) CumulativeSnapshot () metric.HistogramSnapshot {
570
+ return h .h .CumulativeSnapshot ()
571
+ }
572
+
573
+ // WindowedSnapshot returns the windowed histogram snapshot.
574
+ func (h * HighCardinalityChildHistogram ) WindowedSnapshot () metric.HistogramSnapshot {
575
+ return h .h .WindowedSnapshot ()
576
+ }
0 commit comments