Add 50th and 95th percentiles

This commit is contained in:
Daniel Martí 2015-07-16 11:52:32 -07:00
parent 5853f97295
commit 752228ef62
3 changed files with 89 additions and 58 deletions

View File

@ -115,8 +115,12 @@ type Percentiles struct {
Mean uint64 `json:"mean"` Mean uint64 `json:"mean"`
// Max seen over the collected sample. // Max seen over the collected sample.
Max uint64 `json:"max"` Max uint64 `json:"max"`
// 50th percentile over the collected sample.
Fifty uint64 `json:"fifty"`
// 90th percentile over the collected sample. // 90th percentile over the collected sample.
Ninety uint64 `json:"ninety"` Ninety uint64 `json:"ninety"`
// 95th percentile over the collected sample.
NinetyFive uint64 `json:"ninetyfive"`
} }
type Usage struct { type Usage struct {

View File

@ -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) 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] } func (a uint64Slice) Less(i, j int) bool { return a[i] < a[j] }
// Get 90th percentile of the provided samples. Round to integer. // Get percentile of the provided samples. Round to integer.
func (self uint64Slice) Get90Percentile() uint64 { func (self uint64Slice) GetPercentile(d float64) uint64 {
if d < 0.0 || d > 1.0 {
return 0
}
count := self.Len() count := self.Len()
if count == 0 { if count == 0 {
return 0 return 0
} }
sort.Sort(self) sort.Sort(self)
n := float64(0.9 * (float64(count) + 1)) n := float64(d * (float64(count) + 1))
idx, frac := math.Modf(n) idx, frac := math.Modf(n)
index := int(idx) index := int(idx)
percentile := float64(self[index-1]) 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. // Add a single sample. Internally, we convert it to a fake percentile sample.
func (self *resource) AddSample(val uint64) { func (self *resource) AddSample(val uint64) {
sample := info.Percentiles{ sample := info.Percentiles{
Present: true, Present: true,
Mean: val, Mean: val,
Max: val, Max: val,
Ninety: val, Fifty: val,
Ninety: val,
NinetyFive: val,
} }
self.Add(sample) self.Add(sample)
} }
// Get max, average, and 90p from existing samples. // Get max, average, and 90p from existing samples.
func (self *resource) GetPercentile() info.Percentiles { func (self *resource) GetAllPercentiles() info.Percentiles {
p := info.Percentiles{} p := info.Percentiles{}
p.Mean = uint64(self.mean.Mean) p.Mean = uint64(self.mean.Mean)
p.Max = self.max 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 p.Present = true
return p return p
} }
@ -128,8 +135,8 @@ func GetDerivedPercentiles(stats []*info.Usage) info.Usage {
memory.Add(stat.Memory) memory.Add(stat.Memory)
} }
usage := info.Usage{} usage := info.Usage{}
usage.Cpu = cpu.GetPercentile() usage.Cpu = cpu.GetAllPercentiles()
usage.Memory = memory.GetPercentile() usage.Memory = memory.GetAllPercentiles()
return usage return usage
} }
@ -183,7 +190,7 @@ func GetMinutePercentiles(stats []*secondSample) info.Usage {
percent := getPercentComplete(stats) percent := getPercentComplete(stats)
return info.Usage{ return info.Usage{
PercentComplete: percent, PercentComplete: percent,
Cpu: cpu.GetPercentile(), Cpu: cpu.GetAllPercentiles(),
Memory: memory.GetPercentile(), Memory: memory.GetAllPercentiles(),
} }
} }

View File

@ -23,25 +23,29 @@ import (
const Nanosecond = 1000000000 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 N := 100
stats := make(uint64Slice, 0, N) s := make(uint64Slice, 0, N)
for i := N; i > 0; i-- { for i := N; i > 0; i-- {
stats = append(stats, uint64(i)) s = append(s, uint64(i))
} }
p := stats.Get90Percentile() assertPercentile(t, s, 0.2, 20)
if p != 90 { assertPercentile(t, s, 0.7, 70)
t.Errorf("90th percentile is %d, should be 90.", p) assertPercentile(t, s, 0.9, 90)
}
// 90p should be between 94 and 95. Promoted to 95.
N = 105 N = 105
for i := 101; i <= N; i++ { for i := 101; i <= N; i++ {
stats = append(stats, uint64(i)) s = append(s, uint64(i))
}
p = stats.Get90Percentile()
if p != 95 {
t.Errorf("90th percentile is %d, should be 95.", p)
} }
// 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) { func TestMean(t *testing.T) {
@ -74,19 +78,23 @@ func TestAggregates(t *testing.T) {
usage := GetMinutePercentiles(stats) usage := GetMinutePercentiles(stats)
// Cpu mean, max, and 90p should all be 1000 ms/s. // Cpu mean, max, and 90p should all be 1000 ms/s.
cpuExpected := info.Percentiles{ cpuExpected := info.Percentiles{
Present: true, Present: true,
Mean: 1000, Mean: 1000,
Max: 1000, Max: 1000,
Ninety: 1000, Fifty: 1000,
Ninety: 1000,
NinetyFive: 1000,
} }
if usage.Cpu != cpuExpected { if usage.Cpu != cpuExpected {
t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected) t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected)
} }
memExpected := info.Percentiles{ memExpected := info.Percentiles{
Present: true, Present: true,
Mean: 50 * 1024, Mean: 50 * 1024,
Max: 99 * 1024, Max: 99 * 1024,
Ninety: 90 * 1024, Fifty: 50 * 1024,
Ninety: 90 * 1024,
NinetyFive: 95 * 1024,
} }
if usage.Memory != memExpected { if usage.Memory != memExpected {
t.Errorf("memory stats are mean %+v. Expected %+v", 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) usage := GetMinutePercentiles(stats)
// Cpu mean, max, and 90p should all be 1000 ms/s. All high-value samples are discarded. // Cpu mean, max, and 90p should all be 1000 ms/s. All high-value samples are discarded.
cpuExpected := info.Percentiles{ cpuExpected := info.Percentiles{
Present: true, Present: true,
Mean: 1000, Mean: 1000,
Max: 1000, Max: 1000,
Ninety: 1000, Fifty: 1000,
Ninety: 1000,
NinetyFive: 1000,
} }
if usage.Cpu != cpuExpected { if usage.Cpu != cpuExpected {
t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected) t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected)
} }
memExpected := info.Percentiles{ memExpected := info.Percentiles{
Present: true, Present: true,
Mean: 50 * 1024, Mean: 50 * 1024,
Max: 99 * 1024, Max: 99 * 1024,
Ninety: 90 * 1024, Fifty: 50 * 1024,
Ninety: 90 * 1024,
NinetyFive: 95 * 1024,
} }
if usage.Memory != memExpected { if usage.Memory != memExpected {
t.Errorf("memory stats are mean %+v. Expected %+v", 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{ s := &info.Usage{
PercentComplete: 100, PercentComplete: 100,
Cpu: info.Percentiles{ Cpu: info.Percentiles{
Present: true, Present: true,
Mean: i * Nanosecond, Mean: i * Nanosecond,
Max: i * Nanosecond, Max: i * Nanosecond,
Ninety: i * Nanosecond, Fifty: i * Nanosecond,
Ninety: i * Nanosecond,
NinetyFive: i * Nanosecond,
}, },
Memory: info.Percentiles{ Memory: info.Percentiles{
Present: true, Present: true,
Mean: i * 1024, Mean: i * 1024,
Max: i * 1024, Max: i * 1024,
Ninety: i * 1024, Fifty: i * 1024,
Ninety: i * 1024,
NinetyFive: i * 1024,
}, },
} }
stats = append(stats, s) stats = append(stats, s)
} }
usage := GetDerivedPercentiles(stats) usage := GetDerivedPercentiles(stats)
cpuExpected := info.Percentiles{ cpuExpected := info.Percentiles{
Present: true, Present: true,
Mean: 50 * Nanosecond, Mean: 50 * Nanosecond,
Max: 99 * Nanosecond, Max: 99 * Nanosecond,
Ninety: 90 * Nanosecond, Fifty: 50 * Nanosecond,
Ninety: 90 * Nanosecond,
NinetyFive: 95 * Nanosecond,
} }
if usage.Cpu != cpuExpected { if usage.Cpu != cpuExpected {
t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected) t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected)
} }
memExpected := info.Percentiles{ memExpected := info.Percentiles{
Present: true, Present: true,
Mean: 50 * 1024, Mean: 50 * 1024,
Max: 99 * 1024, Max: 99 * 1024,
Ninety: 90 * 1024, Fifty: 50 * 1024,
Ninety: 90 * 1024,
NinetyFive: 95 * 1024,
} }
if usage.Memory != memExpected { if usage.Memory != memExpected {
t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected) t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected)