From 07bd759e280882ec8bd57ed7e793489bbd0f0040 Mon Sep 17 00:00:00 2001 From: Sambhav Jain Date: Tue, 16 Sep 2025 09:34:12 +0000 Subject: [PATCH] feat(summary): add count in percentiles To measure the quality of the derived metric, it is necessary to also store the count of the samples from which the percentile calculation is being done. This commit aims to add this feature in cadvisor. --- info/v2/container.go | 2 ++ summary/percentiles.go | 3 +++ summary/percentiles_test.go | 9 +++++++++ 3 files changed, 14 insertions(+) diff --git a/info/v2/container.go b/info/v2/container.go index f0824027a3..7845fe4c48 100644 --- a/info/v2/container.go +++ b/info/v2/container.go @@ -200,6 +200,8 @@ type Percentiles struct { Ninety uint64 `json:"ninety"` // 95th percentile over the collected sample. NinetyFive uint64 `json:"ninetyfive"` + // Number of samples used to calculate these percentiles. + Count uint64 `json:"count"` } type Usage struct { diff --git a/summary/percentiles.go b/summary/percentiles.go index 8ce706ad9e..bca056e9f0 100644 --- a/summary/percentiles.go +++ b/summary/percentiles.go @@ -109,6 +109,7 @@ func (r *resource) AddSample(val uint64) { Fifty: val, Ninety: val, NinetyFive: val, + Count: 1, } r.Add(sample) } @@ -121,6 +122,8 @@ func (r *resource) GetAllPercentiles() info.Percentiles { p.Fifty = r.samples.GetPercentile(0.5) p.Ninety = r.samples.GetPercentile(0.9) p.NinetyFive = r.samples.GetPercentile(0.95) + // len(samples) is equal to count stored in mean. + p.Count = r.mean.count p.Present = true return p } diff --git a/summary/percentiles_test.go b/summary/percentiles_test.go index 4dbe3665d3..b4cf6206a5 100644 --- a/summary/percentiles_test.go +++ b/summary/percentiles_test.go @@ -84,6 +84,8 @@ func TestAggregates(t *testing.T) { Fifty: 1000, Ninety: 1000, NinetyFive: 1000, + // Since cpu is calculated between samples, we lose 1 sample. + Count: N - 2, } if usage.Cpu != cpuExpected { t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected) @@ -95,6 +97,7 @@ func TestAggregates(t *testing.T) { Fifty: 50 * 1024, Ninety: 90 * 1024, NinetyFive: 95 * 1024, + Count: N - 1, } if usage.Memory != memExpected { t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected) @@ -133,6 +136,8 @@ func TestSamplesCloseInTimeIgnored(t *testing.T) { Fifty: 1000, Ninety: 1000, NinetyFive: 1000, + // Since cpu is calculated between samples, we lose 1 sample. + Count: N - 2, } if usage.Cpu != cpuExpected { t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected) @@ -144,6 +149,7 @@ func TestSamplesCloseInTimeIgnored(t *testing.T) { Fifty: 50 * 1024, Ninety: 90 * 1024, NinetyFive: 95 * 1024, + Count: N - 1, } if usage.Memory != memExpected { t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected) @@ -184,6 +190,8 @@ func TestDerivedStats(t *testing.T) { Fifty: 50 * Nanosecond, Ninety: 90 * Nanosecond, NinetyFive: 95 * Nanosecond, + // GetDerivedPercentiles calculates directly from samples, hence we don't lose any samples. + Count: N - 1, } if usage.Cpu != cpuExpected { t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected) @@ -195,6 +203,7 @@ func TestDerivedStats(t *testing.T) { Fifty: 50 * 1024, Ninety: 90 * 1024, NinetyFive: 95 * 1024, + Count: N - 1, } if usage.Memory != memExpected { t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected)