mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
gVisor metrics library: Add distribution statistics to Prometheus Histogram.
This plumbs the distribution statistics out to the data consumed from the gVisor sandbox process by the `runsc metric-server` process. PiperOrigin-RevId: 537990236
This commit is contained in:
committed by
gVisor bot
parent
2f0e2dfeb9
commit
f8d8cc45ab
@@ -1505,8 +1505,11 @@ func GetSnapshot(options SnapshotOptions) (*prometheus.Snapshot, error) {
|
||||
Metric: m.prometheusMetric,
|
||||
Labels: labels,
|
||||
HistogramValue: &prometheus.Histogram{
|
||||
Total: prometheus.Number{Int: statistics[fieldKey].sampleSum},
|
||||
Buckets: buckets,
|
||||
Total: prometheus.Number{Int: statistics[fieldKey].sampleSum},
|
||||
SumOfSquaredDeviations: prometheus.Number{Float: statistics[fieldKey].sumOfSquaredDeviations},
|
||||
Min: prometheus.Number{Int: statistics[fieldKey].min},
|
||||
Max: prometheus.Number{Int: statistics[fieldKey].max},
|
||||
Buckets: buckets,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -181,6 +181,8 @@ func NewFloat(val float64) *Number {
|
||||
// IsInteger returns whether this number contains an integer value.
|
||||
// This is defined as either having the `Float` part set to zero (in which case the `Int` part takes
|
||||
// precedence), or having `Float` be a value equal to its own rounding and not a special float.
|
||||
//
|
||||
//go:nosplit
|
||||
func (n *Number) IsInteger() bool {
|
||||
if n.Float == 0 {
|
||||
return true
|
||||
@@ -202,6 +204,8 @@ func (n *Number) String() string {
|
||||
|
||||
// SameType returns true if `n` and `other` are either both floating-point or both integers.
|
||||
// If a `Number` is zero, it is considered of the same type as any other zero `Number`.
|
||||
//
|
||||
//go:nosplit
|
||||
func (n *Number) SameType(other *Number) bool {
|
||||
// Within `n` and `other`, at least one of `Int` or `Float` must be set to zero.
|
||||
// Therefore, this verifies that there is at least one shared zero between the two.
|
||||
@@ -210,6 +214,8 @@ func (n *Number) SameType(other *Number) bool {
|
||||
|
||||
// GreaterThan returns true if n > other.
|
||||
// Precondition: n.SameType(other) is true. Panics otherwise.
|
||||
//
|
||||
//go:nosplit
|
||||
func (n *Number) GreaterThan(other *Number) bool {
|
||||
if !n.SameType(other) {
|
||||
panic("tried to compare two numbers of different types")
|
||||
@@ -298,6 +304,12 @@ type Bucket struct {
|
||||
type Histogram struct {
|
||||
// Total is the sum of sample values across all buckets.
|
||||
Total Number `json:"total"`
|
||||
// Min is the minimum sample ever recorded in this histogram.
|
||||
Min Number `json:"min"`
|
||||
// Max is the maximum sample ever recorded in this histogram.
|
||||
Max Number `json:"max"`
|
||||
// SumOfSquaredDeviations is the number of squared deviations of all samples.
|
||||
SumOfSquaredDeviations Number `json:"ssd"`
|
||||
// Buckets contains per-bucket data.
|
||||
// A distribution with n finite-boundary buckets should have n+2 entries here.
|
||||
// The 0th entry is the underflow bucket (i.e. the one with -inf as lower bound),
|
||||
@@ -764,6 +776,15 @@ func (d *Data) writeTo(w io.Writer, when time.Time, options SnapshotExportOption
|
||||
if err := d.writeMetricLine(w, "_count", &samples, when, options, nil, metricsWritten); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.writeMetricLine(w, "_min", &d.HistogramValue.Min, when, options, nil, metricsWritten); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.writeMetricLine(w, "_max", &d.HistogramValue.Max, when, options, nil, metricsWritten); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.writeMetricLine(w, "_ssd", &d.HistogramValue.SumOfSquaredDeviations, when, options, nil, metricsWritten); err != nil {
|
||||
return err
|
||||
}
|
||||
// Empty line after the histogram.
|
||||
if _, err := io.WriteString(w, "\n"); err != nil {
|
||||
return err
|
||||
|
||||
@@ -171,14 +171,29 @@ func (m *metricMetadata) float(val float64) *Data {
|
||||
// value, and this value will be used as the value for that field.
|
||||
// If a field accepts multiple values, the function will panic.
|
||||
func (m *metricMetadata) dist(samples ...int64) *Data {
|
||||
var total int64
|
||||
var total, min, max int64
|
||||
var ssd float64
|
||||
buckets := make([]Bucket, len(m.PB.GetDistributionBucketLowerBounds())+1)
|
||||
var bucket *Bucket
|
||||
for i, lowerBound := range m.PB.GetDistributionBucketLowerBounds() {
|
||||
(&buckets[i]).UpperBound = Number{Int: lowerBound}
|
||||
}
|
||||
(&buckets[len(buckets)-1]).UpperBound = Number{Float: math.Inf(1)}
|
||||
for _, sample := range samples {
|
||||
for i, sample := range samples {
|
||||
if i == 0 {
|
||||
min = sample
|
||||
max = sample
|
||||
} else {
|
||||
if sample < min {
|
||||
min = sample
|
||||
}
|
||||
if sample > max {
|
||||
max = sample
|
||||
}
|
||||
oldMean := float64(total) / float64(i+1)
|
||||
newMean := float64(total+sample) / float64(i+2)
|
||||
ssd += (float64(sample) - oldMean) * (float64(sample) - newMean)
|
||||
}
|
||||
total += sample
|
||||
bucket = &buckets[0]
|
||||
for i, lowerBound := range m.PB.GetDistributionBucketLowerBounds() {
|
||||
@@ -194,8 +209,11 @@ func (m *metricMetadata) dist(samples ...int64) *Data {
|
||||
Metric: m.metric(),
|
||||
Labels: m.labels(),
|
||||
HistogramValue: &Histogram{
|
||||
Total: Number{Int: total},
|
||||
Buckets: buckets,
|
||||
Total: Number{Int: total},
|
||||
Buckets: buckets,
|
||||
Min: Number{Int: min},
|
||||
Max: Number{Int: max},
|
||||
SumOfSquaredDeviations: Number{Float: ssd},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1165,6 +1183,9 @@ func TestSnapshotToPrometheus(t *testing.T) {
|
||||
foo_dist_bucket{le="+inf"} 8 {TIMESTAMP}
|
||||
foo_dist_sum 126 {TIMESTAMP}
|
||||
foo_dist_count 8 {TIMESTAMP}
|
||||
foo_dist_min -1 {TIMESTAMP}
|
||||
foo_dist_max 99 {TIMESTAMP}
|
||||
foo_dist_ssd 8187.5 {TIMESTAMP}
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -1193,6 +1214,9 @@ func TestSnapshotToPrometheus(t *testing.T) {
|
||||
foo_dist_bucket{le="+inf"} 0 {TIMESTAMP}
|
||||
foo_dist_sum 0 {TIMESTAMP}
|
||||
foo_dist_count 0 {TIMESTAMP}
|
||||
foo_dist_min 0 {TIMESTAMP}
|
||||
foo_dist_max 0 {TIMESTAMP}
|
||||
foo_dist_ssd 0 {TIMESTAMP}
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -1214,6 +1238,9 @@ func TestSnapshotToPrometheus(t *testing.T) {
|
||||
foo_dist_bucket{field1="val1a",le="+inf"} 8 {TIMESTAMP}
|
||||
foo_dist_sum{field1="val1a"} 126 {TIMESTAMP}
|
||||
foo_dist_count{field1="val1a"} 8 {TIMESTAMP}
|
||||
foo_dist_min{field1="val1a"} -1 {TIMESTAMP}
|
||||
foo_dist_max{field1="val1a"} 99 {TIMESTAMP}
|
||||
foo_dist_ssd{field1="val1a"} 8187.5 {TIMESTAMP}
|
||||
foo_dist_bucket{field1="val1b",le="0"} 0 {TIMESTAMP}
|
||||
foo_dist_bucket{field1="val1b",le="1"} 0 {TIMESTAMP}
|
||||
foo_dist_bucket{field1="val1b",le="2"} 0 {TIMESTAMP}
|
||||
@@ -1222,6 +1249,9 @@ func TestSnapshotToPrometheus(t *testing.T) {
|
||||
foo_dist_bucket{field1="val1b",le="+inf"} 3 {TIMESTAMP}
|
||||
foo_dist_sum{field1="val1b"} 11 {TIMESTAMP}
|
||||
foo_dist_count{field1="val1b"} 3 {TIMESTAMP}
|
||||
foo_dist_min{field1="val1b"} 3 {TIMESTAMP}
|
||||
foo_dist_max{field1="val1b"} 5 {TIMESTAMP}
|
||||
foo_dist_ssd{field1="val1b"} 8.25 {TIMESTAMP}
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -1250,6 +1280,9 @@ func TestSnapshotToPrometheus(t *testing.T) {
|
||||
some_prefix_foo_dist_bucket{field1="val1a",field2="val2a",le="+inf"} 8 {TIMESTAMP}
|
||||
some_prefix_foo_dist_sum{field1="val1a",field2="val2a"} 126 {TIMESTAMP}
|
||||
some_prefix_foo_dist_count{field1="val1a",field2="val2a"} 8 {TIMESTAMP}
|
||||
some_prefix_foo_dist_min{field1="val1a",field2="val2a"} -1 {TIMESTAMP}
|
||||
some_prefix_foo_dist_max{field1="val1a",field2="val2a"} 99 {TIMESTAMP}
|
||||
some_prefix_foo_dist_ssd{field1="val1a",field2="val2a"} 8187.5 {TIMESTAMP}
|
||||
some_prefix_foo_dist_bucket{field1="val1b",field2="val2a",le="0"} 0 {TIMESTAMP}
|
||||
some_prefix_foo_dist_bucket{field1="val1b",field2="val2a",le="1"} 0 {TIMESTAMP}
|
||||
some_prefix_foo_dist_bucket{field1="val1b",field2="val2a",le="2"} 0 {TIMESTAMP}
|
||||
@@ -1258,6 +1291,9 @@ func TestSnapshotToPrometheus(t *testing.T) {
|
||||
some_prefix_foo_dist_bucket{field1="val1b",field2="val2a",le="+inf"} 3 {TIMESTAMP}
|
||||
some_prefix_foo_dist_sum{field1="val1b",field2="val2a"} 11 {TIMESTAMP}
|
||||
some_prefix_foo_dist_count{field1="val1b",field2="val2a"} 3 {TIMESTAMP}
|
||||
some_prefix_foo_dist_min{field1="val1b",field2="val2a"} 3 {TIMESTAMP}
|
||||
some_prefix_foo_dist_max{field1="val1b",field2="val2a"} 5 {TIMESTAMP}
|
||||
some_prefix_foo_dist_ssd{field1="val1b",field2="val2a"} 8.25 {TIMESTAMP}
|
||||
`,
|
||||
},
|
||||
} {
|
||||
@@ -1473,7 +1509,7 @@ func TestGroupSameNameMetrics(t *testing.T) {
|
||||
t.Fatalf("invalid line: %q", line)
|
||||
}
|
||||
metricName := line[:len(line)-len(strippedMetricName)]
|
||||
for _, distribSuffix := range []string{"_sum", "_count", "_bucket"} {
|
||||
for _, distribSuffix := range []string{"_sum", "_count", "_bucket", "_min", "_max", "_ssd"} {
|
||||
metricName = strings.TrimSuffix(metricName, distribSuffix)
|
||||
}
|
||||
if lastMetric != "" && lastMetric != metricName && seenMetrics[metricName] {
|
||||
|
||||
Reference in New Issue
Block a user