mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-27 09:05:18 +00:00
136 lines
3.6 KiB
Go
136 lines
3.6 KiB
Go
|
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||
|
// of this source code is governed by a BSD-style license that can be found in
|
||
|
// the LICENSE file.
|
||
|
|
||
|
package replay
|
||
|
|
||
|
import (
|
||
|
"math"
|
||
|
"time"
|
||
|
|
||
|
"github.com/guptarohit/asciigraph"
|
||
|
)
|
||
|
|
||
|
// SampledMetric holds a metric that is sampled at various points of workload
|
||
|
// replay. Samples are collected when a new step in the workload is applied to
|
||
|
// the database, and whenever a compaction completes.
|
||
|
type SampledMetric struct {
|
||
|
samples []sample
|
||
|
first time.Time
|
||
|
}
|
||
|
|
||
|
type sample struct {
|
||
|
since time.Duration
|
||
|
value int64
|
||
|
}
|
||
|
|
||
|
func (m *SampledMetric) record(v int64) {
|
||
|
if m.first.IsZero() {
|
||
|
m.first = time.Now()
|
||
|
}
|
||
|
m.samples = append(m.samples, sample{
|
||
|
since: time.Since(m.first),
|
||
|
value: v,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Plot returns an ASCII graph plot of the metric over time, with the provided
|
||
|
// width and height determining the size of the graph and the number of representable discrete x and y
|
||
|
// points. All values are first
|
||
|
// multiplied by the provided scale parameter before graphing.
|
||
|
func (m *SampledMetric) Plot(width, height int, scale float64) string {
|
||
|
values := m.Values(width)
|
||
|
for i := range values {
|
||
|
values[i] *= scale
|
||
|
}
|
||
|
return asciigraph.Plot(values, asciigraph.Height(height))
|
||
|
}
|
||
|
|
||
|
// PlotIncreasingPerSec returns an ASCII graph plot of the increasing delta of a
|
||
|
// metric over time, per-second. The provided width and height determine the
|
||
|
// size of the graph and the number of representable discrete x and y points.
|
||
|
// All deltas are multiplied by the provided scale parameter and scaled to
|
||
|
// per-second before graphing.
|
||
|
func (m *SampledMetric) PlotIncreasingPerSec(width, height int, scale float64) string {
|
||
|
bucketDur, values := m.values(width)
|
||
|
deltas := make([]float64, width)
|
||
|
for i := range values {
|
||
|
if i == 0 {
|
||
|
deltas[i] = (values[i] * scale) / bucketDur.Seconds()
|
||
|
} else if values[i] > values[i-1] {
|
||
|
deltas[i] = (values[i] - values[i-1]) * scale / bucketDur.Seconds()
|
||
|
}
|
||
|
}
|
||
|
return asciigraph.Plot(deltas, asciigraph.Height(height))
|
||
|
}
|
||
|
|
||
|
// Mean calculates the mean value of the metric.
|
||
|
func (m *SampledMetric) Mean() float64 {
|
||
|
var sum float64
|
||
|
if len(m.samples) == 0 {
|
||
|
return 0.0
|
||
|
}
|
||
|
for _, s := range m.samples {
|
||
|
sum += float64(s.value)
|
||
|
}
|
||
|
return sum / float64(len(m.samples))
|
||
|
}
|
||
|
|
||
|
// Min calculates the mininum value of the metric.
|
||
|
func (m *SampledMetric) Min() int64 {
|
||
|
min := int64(math.MaxInt64)
|
||
|
for _, s := range m.samples {
|
||
|
if min > s.value {
|
||
|
min = s.value
|
||
|
}
|
||
|
}
|
||
|
return min
|
||
|
}
|
||
|
|
||
|
// Max calculates the maximum value of the metric.
|
||
|
func (m *SampledMetric) Max() int64 {
|
||
|
var max int64
|
||
|
for _, s := range m.samples {
|
||
|
if max < s.value {
|
||
|
max = s.value
|
||
|
}
|
||
|
}
|
||
|
return max
|
||
|
}
|
||
|
|
||
|
// Values returns the values of the metric, distributed across n discrete
|
||
|
// buckets that are equally spaced over time. If multiple values fall within a
|
||
|
// bucket, the latest recorded value is used. If no values fall within a bucket,
|
||
|
// the next recorded value is used.
|
||
|
func (m *SampledMetric) Values(n int) []float64 {
|
||
|
_, values := m.values(n)
|
||
|
return values
|
||
|
}
|
||
|
|
||
|
func (m *SampledMetric) values(buckets int) (bucketDur time.Duration, values []float64) {
|
||
|
if len(m.samples) == 0 || buckets < 1 {
|
||
|
return bucketDur, nil
|
||
|
}
|
||
|
|
||
|
values = make([]float64, buckets)
|
||
|
totalDur := m.samples[len(m.samples)-1].since
|
||
|
bucketDur = totalDur / time.Duration(buckets)
|
||
|
|
||
|
for i, b := 0, 0; i < len(m.samples); i++ {
|
||
|
// Fill any buckets that precede this value with the previous value.
|
||
|
bi := int(m.samples[i].since / bucketDur)
|
||
|
if bi == buckets {
|
||
|
bi = buckets - 1
|
||
|
}
|
||
|
if b < bi {
|
||
|
b++
|
||
|
for ; b < bi; b++ {
|
||
|
values[b] = float64(m.samples[i].value)
|
||
|
}
|
||
|
}
|
||
|
values[bi] = float64(m.samples[i].value)
|
||
|
b = bi
|
||
|
}
|
||
|
return bucketDur, values
|
||
|
}
|