diff --git a/info/v2/container.go b/info/v2/container.go index 782389f6..320f7775 100644 --- a/info/v2/container.go +++ b/info/v2/container.go @@ -115,8 +115,12 @@ type Percentiles struct { Mean uint64 `json:"mean"` // Max seen over the collected sample. Max uint64 `json:"max"` + // 50th percentile over the collected sample. + Fifty uint64 `json:"fifty"` // 90th percentile over the collected sample. Ninety uint64 `json:"ninety"` + // 95th percentile over the collected sample. + NinetyFive uint64 `json:"ninetyfive"` } type Usage struct { diff --git a/summary/percentiles.go b/summary/percentiles.go index 1893a65c..f32857a9 100644 --- a/summary/percentiles.go +++ b/summary/percentiles.go @@ -34,14 +34,17 @@ func (a uint64Slice) Len() int { return len(a) } func (a uint64Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a uint64Slice) Less(i, j int) bool { return a[i] < a[j] } -// Get 90th percentile of the provided samples. Round to integer. -func (self uint64Slice) Get90Percentile() uint64 { +// Get percentile of the provided samples. Round to integer. +func (self uint64Slice) GetPercentile(d float64) uint64 { + if d < 0.0 || d > 1.0 { + return 0 + } count := self.Len() if count == 0 { return 0 } sort.Sort(self) - n := float64(0.9 * (float64(count) + 1)) + n := float64(d * (float64(count) + 1)) idx, frac := math.Modf(n) index := int(idx) percentile := float64(self[index-1]) @@ -94,20 +97,24 @@ func (self *resource) Add(p info.Percentiles) { // Add a single sample. Internally, we convert it to a fake percentile sample. func (self *resource) AddSample(val uint64) { sample := info.Percentiles{ - Present: true, - Mean: val, - Max: val, - Ninety: val, + Present: true, + Mean: val, + Max: val, + Fifty: val, + Ninety: val, + NinetyFive: val, } self.Add(sample) } // Get max, average, and 90p from existing samples. -func (self *resource) GetPercentile() info.Percentiles { +func (self *resource) GetAllPercentiles() info.Percentiles { p := info.Percentiles{} p.Mean = uint64(self.mean.Mean) p.Max = self.max - p.Ninety = self.samples.Get90Percentile() + p.Fifty = self.samples.GetPercentile(0.5) + p.Ninety = self.samples.GetPercentile(0.9) + p.NinetyFive = self.samples.GetPercentile(0.95) p.Present = true return p } @@ -128,8 +135,8 @@ func GetDerivedPercentiles(stats []*info.Usage) info.Usage { memory.Add(stat.Memory) } usage := info.Usage{} - usage.Cpu = cpu.GetPercentile() - usage.Memory = memory.GetPercentile() + usage.Cpu = cpu.GetAllPercentiles() + usage.Memory = memory.GetAllPercentiles() return usage } @@ -183,7 +190,7 @@ func GetMinutePercentiles(stats []*secondSample) info.Usage { percent := getPercentComplete(stats) return info.Usage{ PercentComplete: percent, - Cpu: cpu.GetPercentile(), - Memory: memory.GetPercentile(), + Cpu: cpu.GetAllPercentiles(), + Memory: memory.GetAllPercentiles(), } } diff --git a/summary/percentiles_test.go b/summary/percentiles_test.go index 53b6a29f..1885c664 100644 --- a/summary/percentiles_test.go +++ b/summary/percentiles_test.go @@ -23,25 +23,29 @@ import ( const Nanosecond = 1000000000 -func Test90Percentile(t *testing.T) { +func assertPercentile(t *testing.T, s uint64Slice, f float64, want uint64) { + if got := s.GetPercentile(f); got != want { + t.Errorf("GetPercentile(%f) is %d, should be %d.", f, got, want) + } +} + +func TestPercentile(t *testing.T) { N := 100 - stats := make(uint64Slice, 0, N) + s := make(uint64Slice, 0, N) for i := N; i > 0; i-- { - stats = append(stats, uint64(i)) + s = append(s, uint64(i)) } - p := stats.Get90Percentile() - if p != 90 { - t.Errorf("90th percentile is %d, should be 90.", p) - } - // 90p should be between 94 and 95. Promoted to 95. + assertPercentile(t, s, 0.2, 20) + assertPercentile(t, s, 0.7, 70) + assertPercentile(t, s, 0.9, 90) N = 105 for i := 101; i <= N; i++ { - stats = append(stats, uint64(i)) - } - p = stats.Get90Percentile() - if p != 95 { - t.Errorf("90th percentile is %d, should be 95.", p) + s = append(s, uint64(i)) } + // 90p should be between 94 and 95. Promoted to 95. + assertPercentile(t, s, 0.2, 21) + assertPercentile(t, s, 0.7, 74) + assertPercentile(t, s, 0.9, 95) } func TestMean(t *testing.T) { @@ -74,19 +78,23 @@ func TestAggregates(t *testing.T) { usage := GetMinutePercentiles(stats) // Cpu mean, max, and 90p should all be 1000 ms/s. cpuExpected := info.Percentiles{ - Present: true, - Mean: 1000, - Max: 1000, - Ninety: 1000, + Present: true, + Mean: 1000, + Max: 1000, + Fifty: 1000, + Ninety: 1000, + NinetyFive: 1000, } if usage.Cpu != cpuExpected { t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected) } memExpected := info.Percentiles{ - Present: true, - Mean: 50 * 1024, - Max: 99 * 1024, - Ninety: 90 * 1024, + Present: true, + Mean: 50 * 1024, + Max: 99 * 1024, + Fifty: 50 * 1024, + Ninety: 90 * 1024, + NinetyFive: 95 * 1024, } if usage.Memory != memExpected { t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected) @@ -119,19 +127,23 @@ func TestSamplesCloseInTimeIgnored(t *testing.T) { usage := GetMinutePercentiles(stats) // Cpu mean, max, and 90p should all be 1000 ms/s. All high-value samples are discarded. cpuExpected := info.Percentiles{ - Present: true, - Mean: 1000, - Max: 1000, - Ninety: 1000, + Present: true, + Mean: 1000, + Max: 1000, + Fifty: 1000, + Ninety: 1000, + NinetyFive: 1000, } if usage.Cpu != cpuExpected { t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected) } memExpected := info.Percentiles{ - Present: true, - Mean: 50 * 1024, - Max: 99 * 1024, - Ninety: 90 * 1024, + Present: true, + Mean: 50 * 1024, + Max: 99 * 1024, + Fifty: 50 * 1024, + Ninety: 90 * 1024, + NinetyFive: 95 * 1024, } if usage.Memory != memExpected { t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected) @@ -146,35 +158,43 @@ func TestDerivedStats(t *testing.T) { s := &info.Usage{ PercentComplete: 100, Cpu: info.Percentiles{ - Present: true, - Mean: i * Nanosecond, - Max: i * Nanosecond, - Ninety: i * Nanosecond, + Present: true, + Mean: i * Nanosecond, + Max: i * Nanosecond, + Fifty: i * Nanosecond, + Ninety: i * Nanosecond, + NinetyFive: i * Nanosecond, }, Memory: info.Percentiles{ - Present: true, - Mean: i * 1024, - Max: i * 1024, - Ninety: i * 1024, + Present: true, + Mean: i * 1024, + Max: i * 1024, + Fifty: i * 1024, + Ninety: i * 1024, + NinetyFive: i * 1024, }, } stats = append(stats, s) } usage := GetDerivedPercentiles(stats) cpuExpected := info.Percentiles{ - Present: true, - Mean: 50 * Nanosecond, - Max: 99 * Nanosecond, - Ninety: 90 * Nanosecond, + Present: true, + Mean: 50 * Nanosecond, + Max: 99 * Nanosecond, + Fifty: 50 * Nanosecond, + Ninety: 90 * Nanosecond, + NinetyFive: 95 * Nanosecond, } if usage.Cpu != cpuExpected { t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected) } memExpected := info.Percentiles{ - Present: true, - Mean: 50 * 1024, - Max: 99 * 1024, - Ninety: 90 * 1024, + Present: true, + Mean: 50 * 1024, + Max: 99 * 1024, + Fifty: 50 * 1024, + Ninety: 90 * 1024, + NinetyFive: 95 * 1024, } if usage.Memory != memExpected { t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected)