golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go (about) 1 // Copyright 2014 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 // Package measurement export utility functions to manipulate/format performance profile sample values. 16 package measurement 17 18 import ( 19 "fmt" 20 "strings" 21 "time" 22 23 "github.com/google/pprof/profile" 24 ) 25 26 // ScaleProfiles updates the units in a set of profiles to make them 27 // compatible. It scales the profiles to the smallest unit to preserve 28 // data. 29 func ScaleProfiles(profiles []*profile.Profile) error { 30 if len(profiles) == 0 { 31 return nil 32 } 33 periodTypes := make([]*profile.ValueType, 0, len(profiles)) 34 for _, p := range profiles { 35 if p.PeriodType != nil { 36 periodTypes = append(periodTypes, p.PeriodType) 37 } 38 } 39 periodType, err := CommonValueType(periodTypes) 40 if err != nil { 41 return fmt.Errorf("period type: %v", err) 42 } 43 44 // Identify common sample types 45 numSampleTypes := len(profiles[0].SampleType) 46 for _, p := range profiles[1:] { 47 if numSampleTypes != len(p.SampleType) { 48 return fmt.Errorf("inconsistent samples type count: %d != %d", numSampleTypes, len(p.SampleType)) 49 } 50 } 51 sampleType := make([]*profile.ValueType, numSampleTypes) 52 for i := 0; i < numSampleTypes; i++ { 53 sampleTypes := make([]*profile.ValueType, len(profiles)) 54 for j, p := range profiles { 55 sampleTypes[j] = p.SampleType[i] 56 } 57 sampleType[i], err = CommonValueType(sampleTypes) 58 if err != nil { 59 return fmt.Errorf("sample types: %v", err) 60 } 61 } 62 63 for _, p := range profiles { 64 if p.PeriodType != nil && periodType != nil { 65 period, _ := Scale(p.Period, p.PeriodType.Unit, periodType.Unit) 66 p.Period, p.PeriodType.Unit = int64(period), periodType.Unit 67 } 68 ratios := make([]float64, len(p.SampleType)) 69 for i, st := range p.SampleType { 70 if sampleType[i] == nil { 71 ratios[i] = 1 72 continue 73 } 74 ratios[i], _ = Scale(1, st.Unit, sampleType[i].Unit) 75 p.SampleType[i].Unit = sampleType[i].Unit 76 } 77 if err := p.ScaleN(ratios); err != nil { 78 return fmt.Errorf("scale: %v", err) 79 } 80 } 81 return nil 82 } 83 84 // CommonValueType returns the finest type from a set of compatible 85 // types. 86 func CommonValueType(ts []*profile.ValueType) (*profile.ValueType, error) { 87 if len(ts) <= 1 { 88 return nil, nil 89 } 90 minType := ts[0] 91 for _, t := range ts[1:] { 92 if !compatibleValueTypes(minType, t) { 93 return nil, fmt.Errorf("incompatible types: %v %v", *minType, *t) 94 } 95 if ratio, _ := Scale(1, t.Unit, minType.Unit); ratio < 1 { 96 minType = t 97 } 98 } 99 rcopy := *minType 100 return &rcopy, nil 101 } 102 103 func compatibleValueTypes(v1, v2 *profile.ValueType) bool { 104 if v1 == nil || v2 == nil { 105 return true // No grounds to disqualify. 106 } 107 // Remove trailing 's' to permit minor mismatches. 108 if t1, t2 := strings.TrimSuffix(v1.Type, "s"), strings.TrimSuffix(v2.Type, "s"); t1 != t2 { 109 return false 110 } 111 112 return v1.Unit == v2.Unit || 113 (isTimeUnit(v1.Unit) && isTimeUnit(v2.Unit)) || 114 (isMemoryUnit(v1.Unit) && isMemoryUnit(v2.Unit)) 115 } 116 117 // Scale a measurement from an unit to a different unit and returns 118 // the scaled value and the target unit. The returned target unit 119 // will be empty if uninteresting (could be skipped). 120 func Scale(value int64, fromUnit, toUnit string) (float64, string) { 121 // Avoid infinite recursion on overflow. 122 if value < 0 && -value > 0 { 123 v, u := Scale(-value, fromUnit, toUnit) 124 return -v, u 125 } 126 if m, u, ok := memoryLabel(value, fromUnit, toUnit); ok { 127 return m, u 128 } 129 if t, u, ok := timeLabel(value, fromUnit, toUnit); ok { 130 return t, u 131 } 132 // Skip non-interesting units. 133 switch toUnit { 134 case "count", "sample", "unit", "minimum", "auto": 135 return float64(value), "" 136 default: 137 return float64(value), toUnit 138 } 139 } 140 141 // Label returns the label used to describe a certain measurement. 142 func Label(value int64, unit string) string { 143 return ScaledLabel(value, unit, "auto") 144 } 145 146 // ScaledLabel scales the passed-in measurement (if necessary) and 147 // returns the label used to describe a float measurement. 148 func ScaledLabel(value int64, fromUnit, toUnit string) string { 149 v, u := Scale(value, fromUnit, toUnit) 150 sv := strings.TrimSuffix(fmt.Sprintf("%.2f", v), ".00") 151 if sv == "0" || sv == "-0" { 152 return "0" 153 } 154 return sv + u 155 } 156 157 // isMemoryUnit returns whether a name is recognized as a memory size 158 // unit. 159 func isMemoryUnit(unit string) bool { 160 switch strings.TrimSuffix(strings.ToLower(unit), "s") { 161 case "byte", "b", "kilobyte", "kb", "megabyte", "mb", "gigabyte", "gb": 162 return true 163 } 164 return false 165 } 166 167 func memoryLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) { 168 fromUnit = strings.TrimSuffix(strings.ToLower(fromUnit), "s") 169 toUnit = strings.TrimSuffix(strings.ToLower(toUnit), "s") 170 171 switch fromUnit { 172 case "byte", "b": 173 case "kilobyte", "kb": 174 value *= 1024 175 case "megabyte", "mb": 176 value *= 1024 * 1024 177 case "gigabyte", "gb": 178 value *= 1024 * 1024 * 1024 179 default: 180 return 0, "", false 181 } 182 183 if toUnit == "minimum" || toUnit == "auto" { 184 switch { 185 case value < 1024: 186 toUnit = "b" 187 case value < 1024*1024: 188 toUnit = "kb" 189 case value < 1024*1024*1024: 190 toUnit = "mb" 191 default: 192 toUnit = "gb" 193 } 194 } 195 196 var output float64 197 switch toUnit { 198 default: 199 output, toUnit = float64(value), "B" 200 case "kb", "kbyte", "kilobyte": 201 output, toUnit = float64(value)/1024, "kB" 202 case "mb", "mbyte", "megabyte": 203 output, toUnit = float64(value)/(1024*1024), "MB" 204 case "gb", "gbyte", "gigabyte": 205 output, toUnit = float64(value)/(1024*1024*1024), "GB" 206 } 207 return output, toUnit, true 208 } 209 210 // isTimeUnit returns whether a name is recognized as a time unit. 211 func isTimeUnit(unit string) bool { 212 unit = strings.ToLower(unit) 213 if len(unit) > 2 { 214 unit = strings.TrimSuffix(unit, "s") 215 } 216 217 switch unit { 218 case "nanosecond", "ns", "microsecond", "millisecond", "ms", "s", "second", "sec", "hr", "day", "week", "year": 219 return true 220 } 221 return false 222 } 223 224 func timeLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) { 225 fromUnit = strings.ToLower(fromUnit) 226 if len(fromUnit) > 2 { 227 fromUnit = strings.TrimSuffix(fromUnit, "s") 228 } 229 230 toUnit = strings.ToLower(toUnit) 231 if len(toUnit) > 2 { 232 toUnit = strings.TrimSuffix(toUnit, "s") 233 } 234 235 var d time.Duration 236 switch fromUnit { 237 case "nanosecond", "ns": 238 d = time.Duration(value) * time.Nanosecond 239 case "microsecond": 240 d = time.Duration(value) * time.Microsecond 241 case "millisecond", "ms": 242 d = time.Duration(value) * time.Millisecond 243 case "second", "sec", "s": 244 d = time.Duration(value) * time.Second 245 case "cycle": 246 return float64(value), "", true 247 default: 248 return 0, "", false 249 } 250 251 if toUnit == "minimum" || toUnit == "auto" { 252 switch { 253 case d < 1*time.Microsecond: 254 toUnit = "ns" 255 case d < 1*time.Millisecond: 256 toUnit = "us" 257 case d < 1*time.Second: 258 toUnit = "ms" 259 case d < 1*time.Minute: 260 toUnit = "sec" 261 case d < 1*time.Hour: 262 toUnit = "min" 263 case d < 24*time.Hour: 264 toUnit = "hour" 265 case d < 15*24*time.Hour: 266 toUnit = "day" 267 case d < 120*24*time.Hour: 268 toUnit = "week" 269 default: 270 toUnit = "year" 271 } 272 } 273 274 var output float64 275 dd := float64(d) 276 switch toUnit { 277 case "ns", "nanosecond": 278 output, toUnit = dd/float64(time.Nanosecond), "ns" 279 case "us", "microsecond": 280 output, toUnit = dd/float64(time.Microsecond), "us" 281 case "ms", "millisecond": 282 output, toUnit = dd/float64(time.Millisecond), "ms" 283 case "min", "minute": 284 output, toUnit = dd/float64(time.Minute), "mins" 285 case "hour", "hr": 286 output, toUnit = dd/float64(time.Hour), "hrs" 287 case "day": 288 output, toUnit = dd/float64(24*time.Hour), "days" 289 case "week", "wk": 290 output, toUnit = dd/float64(7*24*time.Hour), "wks" 291 case "year", "yr": 292 output, toUnit = dd/float64(365*7*24*time.Hour), "yrs" 293 default: 294 fallthrough 295 case "sec", "second", "s": 296 output, toUnit = dd/float64(time.Second), "s" 297 } 298 return output, toUnit, true 299 }