@@ -8,10 +8,12 @@ package rpc
8
8
import (
9
9
"context"
10
10
"math"
11
+ "sort"
11
12
"time"
12
13
13
14
"github.com/VividCortex/ewma"
14
15
"github.com/cockroachdb/cockroach/pkg/roachpb"
16
+ "github.com/cockroachdb/cockroach/pkg/util/buildutil"
15
17
"github.com/cockroachdb/cockroach/pkg/util/hlc"
16
18
"github.com/cockroachdb/cockroach/pkg/util/log"
17
19
"github.com/cockroachdb/cockroach/pkg/util/metric"
@@ -340,7 +342,7 @@ func (r *RemoteClockMonitor) VerifyClockOffset(ctx context.Context) error {
340
342
341
343
now := r .clock .Now ()
342
344
healthyOffsetCount := 0
343
-
345
+ sum := float64 ( 0 )
344
346
offsets , numClocks := func () (stats.Float64Data , int ) {
345
347
r .mu .Lock ()
346
348
defer r .mu .Unlock ()
@@ -351,31 +353,25 @@ func (r *RemoteClockMonitor) VerifyClockOffset(ctx context.Context) error {
351
353
delete (r .mu .offsets , id )
352
354
continue
353
355
}
354
- offs = append (offs , float64 (offset .Offset + offset .Uncertainty ))
355
- offs = append (offs , float64 (offset .Offset - offset .Uncertainty ))
356
+ off1 := float64 (offset .Offset + offset .Uncertainty )
357
+ off2 := float64 (offset .Offset - offset .Uncertainty )
358
+ sum += off1 + off2
359
+ offs = append (offs , off1 , off2 )
356
360
if offset .isHealthy (ctx , r .toleratedOffset ) {
357
361
healthyOffsetCount ++
358
362
}
359
363
}
360
364
return offs , len (r .mu .offsets )
361
365
}()
362
366
363
- mean , err := offsets .Mean ()
364
- if err != nil && ! errors .Is (err , stats .EmptyInput ) {
365
- return err
366
- }
367
- stdDev , err := offsets .StandardDeviation ()
368
- if err != nil && ! errors .Is (err , stats .EmptyInput ) {
369
- return err
370
- }
371
- median , err := offsets .Median ()
372
- if err != nil && ! errors .Is (err , stats .EmptyInput ) {
373
- return err
374
- }
375
- medianAbsoluteDeviation , err := offsets .MedianAbsoluteDeviation ()
376
- if err != nil && ! errors .Is (err , stats .EmptyInput ) {
377
- return err
378
- }
367
+ sort .Float64s (offsets )
368
+
369
+ mean := sum / float64 (len (offsets ))
370
+ stdDev := StandardDeviationPopulationKnownMean (offsets , mean )
371
+ median := MedianSortedInput (offsets )
372
+ // WARNING: offsets is unusable after this point as it has been modified.
373
+ medianAbsoluteDeviation := MedianAbsoluteDeviationPopulationSortedInputMutatesInput (offsets )
374
+
379
375
r .metrics .ClockOffsetMeanNanos .Update (int64 (mean ))
380
376
r .metrics .ClockOffsetStdDevNanos .Update (int64 (stdDev ))
381
377
r .metrics .ClockOffsetMedianNanos .Update (int64 (median ))
@@ -458,3 +454,84 @@ func updateClockOffsetTracking(
458
454
remoteClocks .UpdateOffset (ctx , nodeID , offset , pingDuration )
459
455
return pingDuration , offset , remoteClocks .VerifyClockOffset (ctx )
460
456
}
457
+
458
+ // The following statistics functions are re-implementations of similar
459
+ // functions provided by github.com/montanaflynn/stats. Those original functions
460
+ // were originally offered under:
461
+ //
462
+ // The MIT License (MIT)
463
+ //
464
+ // Copyright (c) 2014-2023 Montana Flynn (https://montanaflynn.com)
465
+ //
466
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
467
+ // of this software and associated documentation files (the "Software"), to deal
468
+ // in the Software without restriction, including without limitation the rights
469
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
470
+ // copies of the Software, and to permit persons to whom the Software is
471
+ // furnished to do so, subject to the following conditions:
472
+ //
473
+ // The above copyright notice and this permission notice shall be included in all
474
+ // copies or substantial portions of the Software.
475
+ //
476
+
477
+ // StandardDeviationPopulationKnownMean calculates the standard deviation
478
+ // assuming the input is the population and that the given mean is the mean of
479
+ // the input.
480
+ func StandardDeviationPopulationKnownMean (input stats.Float64Data , mean float64 ) float64 {
481
+ if input .Len () == 0 {
482
+ return math .NaN ()
483
+ }
484
+ return math .Sqrt (PopulationVarianceKnownMean (input , mean ))
485
+ }
486
+
487
+ // PopulationVarianceKnownMean calculates the variance assuming the input is the
488
+ // population and that the given mean is the mean of the input.
489
+ func PopulationVarianceKnownMean (input stats.Float64Data , mean float64 ) float64 {
490
+ if input .Len () == 0 {
491
+ return math .NaN ()
492
+ }
493
+ variance := float64 (0 )
494
+ for _ , n := range input {
495
+ diff := n - mean
496
+ variance += diff * diff
497
+ }
498
+ return variance / float64 (input .Len ())
499
+ }
500
+
501
+ // MedianSortedInput calculates the median of the input, assuming it is already
502
+ // sorted.
503
+ func MedianSortedInput (sortedInput stats.Float64Data ) float64 {
504
+ if buildutil .CrdbTestBuild {
505
+ if ! sort .IsSorted (sortedInput ) {
506
+ panic ("MedianSortedInput expects sorted input" )
507
+ }
508
+ }
509
+
510
+ l := len (sortedInput )
511
+ if l == 0 {
512
+ return math .NaN ()
513
+ } else if l % 2 == 0 {
514
+ return (sortedInput [(l / 2 )- 1 ] + sortedInput [(l / 2 )]) / 2.0
515
+ } else {
516
+ return sortedInput [l / 2 ]
517
+ }
518
+ }
519
+
520
+ // MedianAbsoluteDeviationPopulationSortedInputMutatesInput calculates the
521
+ // median absolute deviation from a pre-sorted population.
522
+ //
523
+ // WARNING: This function mutates its input to avoid further allocations.
524
+ func MedianAbsoluteDeviationPopulationSortedInputMutatesInput (
525
+ sortedInput stats.Float64Data ,
526
+ ) float64 {
527
+ if sortedInput .Len () == 0 {
528
+ return math .NaN ()
529
+ }
530
+
531
+ m := MedianSortedInput (sortedInput )
532
+ for key , value := range sortedInput {
533
+ sortedInput [key ] = math .Abs (value - m )
534
+ }
535
+ sort .Float64s (sortedInput )
536
+ return MedianSortedInput (sortedInput )
537
+ }
0 commit comments