github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/replay/sampled_metric.go (about) 1 // Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package replay 6 7 import ( 8 "math" 9 "time" 10 11 "github.com/guptarohit/asciigraph" 12 ) 13 14 // SampledMetric holds a metric that is sampled at various points of workload 15 // replay. Samples are collected when a new step in the workload is applied to 16 // the database, and whenever a compaction completes. 17 type SampledMetric struct { 18 samples []sample 19 first time.Time 20 } 21 22 type sample struct { 23 since time.Duration 24 value int64 25 } 26 27 func (m *SampledMetric) record(v int64) { 28 if m.first.IsZero() { 29 m.first = time.Now() 30 } 31 m.samples = append(m.samples, sample{ 32 since: time.Since(m.first), 33 value: v, 34 }) 35 } 36 37 // Plot returns an ASCII graph plot of the metric over time, with the provided 38 // width and height determining the size of the graph and the number of representable discrete x and y 39 // points. All values are first 40 // multiplied by the provided scale parameter before graphing. 41 func (m *SampledMetric) Plot(width, height int, scale float64) string { 42 values := m.Values(width) 43 for i := range values { 44 values[i] *= scale 45 } 46 return asciigraph.Plot(values, asciigraph.Height(height)) 47 } 48 49 // PlotIncreasingPerSec returns an ASCII graph plot of the increasing delta of a 50 // metric over time, per-second. The provided width and height determine the 51 // size of the graph and the number of representable discrete x and y points. 52 // All deltas are multiplied by the provided scale parameter and scaled to 53 // per-second before graphing. 54 func (m *SampledMetric) PlotIncreasingPerSec(width, height int, scale float64) string { 55 bucketDur, values := m.values(width) 56 deltas := make([]float64, width) 57 for i := range values { 58 if i == 0 { 59 deltas[i] = (values[i] * scale) / bucketDur.Seconds() 60 } else if values[i] > values[i-1] { 61 deltas[i] = (values[i] - values[i-1]) * scale / bucketDur.Seconds() 62 } 63 } 64 return asciigraph.Plot(deltas, asciigraph.Height(height)) 65 } 66 67 // Mean calculates the mean value of the metric. 68 func (m *SampledMetric) Mean() float64 { 69 var sum float64 70 if len(m.samples) == 0 { 71 return 0.0 72 } 73 for _, s := range m.samples { 74 sum += float64(s.value) 75 } 76 return sum / float64(len(m.samples)) 77 } 78 79 // Min calculates the mininum value of the metric. 80 func (m *SampledMetric) Min() int64 { 81 min := int64(math.MaxInt64) 82 for _, s := range m.samples { 83 if min > s.value { 84 min = s.value 85 } 86 } 87 return min 88 } 89 90 // Max calculates the maximum value of the metric. 91 func (m *SampledMetric) Max() int64 { 92 var max int64 93 for _, s := range m.samples { 94 if max < s.value { 95 max = s.value 96 } 97 } 98 return max 99 } 100 101 // Values returns the values of the metric, distributed across n discrete 102 // buckets that are equally spaced over time. If multiple values fall within a 103 // bucket, the latest recorded value is used. If no values fall within a bucket, 104 // the next recorded value is used. 105 func (m *SampledMetric) Values(n int) []float64 { 106 _, values := m.values(n) 107 return values 108 } 109 110 func (m *SampledMetric) values(buckets int) (bucketDur time.Duration, values []float64) { 111 if len(m.samples) == 0 || buckets < 1 { 112 return bucketDur, nil 113 } 114 115 values = make([]float64, buckets) 116 totalDur := m.samples[len(m.samples)-1].since 117 bucketDur = totalDur / time.Duration(buckets) 118 119 for i, b := 0, 0; i < len(m.samples); i++ { 120 // Fill any buckets that precede this value with the previous value. 121 bi := int(m.samples[i].since / bucketDur) 122 if bi == buckets { 123 bi = buckets - 1 124 } 125 if b < bi { 126 b++ 127 for ; b < bi; b++ { 128 values[b] = float64(m.samples[i].value) 129 } 130 } 131 values[bi] = float64(m.samples[i].value) 132 b = bi 133 } 134 return bucketDur, values 135 }