github.com/google/cadvisor@v0.49.1/summary/percentiles.go (about) 1 // Copyright 2015 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Utility methods to calculate percentiles. 16 17 package summary 18 19 import ( 20 "fmt" 21 "math" 22 "sort" 23 24 info "github.com/google/cadvisor/info/v2" 25 ) 26 27 const secondsToMilliSeconds = 1000 28 const milliSecondsToNanoSeconds = 1000000 29 const secondsToNanoSeconds = secondsToMilliSeconds * milliSecondsToNanoSeconds 30 31 type Uint64Slice []uint64 32 33 func (s Uint64Slice) Len() int { return len(s) } 34 func (s Uint64Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 35 func (s Uint64Slice) Less(i, j int) bool { return s[i] < s[j] } 36 37 // Get percentile of the provided samples. Round to integer. 38 func (s Uint64Slice) GetPercentile(d float64) uint64 { 39 if d < 0.0 || d > 1.0 { 40 return 0 41 } 42 count := s.Len() 43 if count == 0 { 44 return 0 45 } 46 sort.Sort(s) 47 n := float64(d * (float64(count) + 1)) 48 idx, frac := math.Modf(n) 49 index := int(idx) 50 percentile := float64(s[index-1]) 51 if index > 1 && index < count { 52 percentile += frac * float64(s[index]-s[index-1]) 53 } 54 return uint64(percentile) 55 } 56 57 type mean struct { 58 // current count. 59 count uint64 60 // current mean. 61 Mean float64 62 } 63 64 func (m *mean) Add(value uint64) { 65 m.count++ 66 if m.count == 1 { 67 m.Mean = float64(value) 68 return 69 } 70 c := float64(m.count) 71 v := float64(value) 72 m.Mean = (m.Mean*(c-1) + v) / c 73 } 74 75 type Percentile interface { 76 Add(info.Percentiles) 77 AddSample(uint64) 78 GetAllPercentiles() info.Percentiles 79 } 80 81 type resource struct { 82 // list of samples being tracked. 83 samples Uint64Slice 84 // average from existing samples. 85 mean mean 86 // maximum value seen so far in the added samples. 87 max uint64 88 } 89 90 // Adds a new percentile sample. 91 func (r *resource) Add(p info.Percentiles) { 92 if !p.Present { 93 return 94 } 95 if p.Max > r.max { 96 r.max = p.Max 97 } 98 r.mean.Add(p.Mean) 99 // Selecting 90p of 90p :( 100 r.samples = append(r.samples, p.Ninety) 101 } 102 103 // Add a single sample. Internally, we convert it to a fake percentile sample. 104 func (r *resource) AddSample(val uint64) { 105 sample := info.Percentiles{ 106 Present: true, 107 Mean: val, 108 Max: val, 109 Fifty: val, 110 Ninety: val, 111 NinetyFive: val, 112 } 113 r.Add(sample) 114 } 115 116 // Get max, average, and 90p from existing samples. 117 func (r *resource) GetAllPercentiles() info.Percentiles { 118 p := info.Percentiles{} 119 p.Mean = uint64(r.mean.Mean) 120 p.Max = r.max 121 p.Fifty = r.samples.GetPercentile(0.5) 122 p.Ninety = r.samples.GetPercentile(0.9) 123 p.NinetyFive = r.samples.GetPercentile(0.95) 124 p.Present = true 125 return p 126 } 127 128 func NewResource(size int) Percentile { 129 return &resource{ 130 samples: make(Uint64Slice, 0, size), 131 mean: mean{count: 0, Mean: 0}, 132 } 133 } 134 135 // Return aggregated percentiles from the provided percentile samples. 136 func GetDerivedPercentiles(stats []*info.Usage) info.Usage { 137 cpu := NewResource(len(stats)) 138 memory := NewResource(len(stats)) 139 for _, stat := range stats { 140 cpu.Add(stat.Cpu) 141 memory.Add(stat.Memory) 142 } 143 usage := info.Usage{} 144 usage.Cpu = cpu.GetAllPercentiles() 145 usage.Memory = memory.GetAllPercentiles() 146 return usage 147 } 148 149 // Calculate part of a minute this sample set represent. 150 func getPercentComplete(stats []*secondSample) (percent int32) { 151 numSamples := len(stats) 152 if numSamples > 1 { 153 percent = 100 154 timeRange := stats[numSamples-1].Timestamp.Sub(stats[0].Timestamp).Nanoseconds() 155 // allow some slack 156 if timeRange < 58*secondsToNanoSeconds { 157 percent = int32((timeRange * 100) / 60 * secondsToNanoSeconds) 158 } 159 } 160 return 161 } 162 163 // Calculate cpurate from two consecutive total cpu usage samples. 164 func getCPURate(latest, previous secondSample) (uint64, error) { 165 elapsed := latest.Timestamp.Sub(previous.Timestamp).Nanoseconds() 166 if elapsed < 10*milliSecondsToNanoSeconds { 167 return 0, fmt.Errorf("elapsed time too small: %d ns: time now %s last %s", elapsed, latest.Timestamp.String(), previous.Timestamp.String()) 168 } 169 if latest.Cpu < previous.Cpu { 170 return 0, fmt.Errorf("bad sample: cumulative cpu usage dropped from %d to %d", latest.Cpu, previous.Cpu) 171 } 172 // Cpurate is calculated in cpu-milliseconds per second. 173 cpuRate := (latest.Cpu - previous.Cpu) * secondsToMilliSeconds / uint64(elapsed) 174 return cpuRate, nil 175 } 176 177 // Returns a percentile sample for a minute by aggregating seconds samples. 178 func GetMinutePercentiles(stats []*secondSample) info.Usage { 179 lastSample := secondSample{} 180 cpu := NewResource(len(stats)) 181 memory := NewResource(len(stats)) 182 for _, stat := range stats { 183 if !lastSample.Timestamp.IsZero() { 184 cpuRate, err := getCPURate(*stat, lastSample) 185 if err != nil { 186 continue 187 } 188 cpu.AddSample(cpuRate) 189 memory.AddSample(stat.Memory) 190 } else { 191 memory.AddSample(stat.Memory) 192 } 193 lastSample = *stat 194 } 195 percent := getPercentComplete(stats) 196 return info.Usage{ 197 PercentComplete: percent, 198 Cpu: cpu.GetAllPercentiles(), 199 Memory: memory.GetAllPercentiles(), 200 } 201 }