1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package measurement
17
18 import (
19 "fmt"
20 "math"
21 "strings"
22 "time"
23
24 "github.com/google/pprof/profile"
25 )
26
27
28
29
30 func ScaleProfiles(profiles []*profile.Profile) error {
31 if len(profiles) == 0 {
32 return nil
33 }
34 periodTypes := make([]*profile.ValueType, 0, len(profiles))
35 for _, p := range profiles {
36 if p.PeriodType != nil {
37 periodTypes = append(periodTypes, p.PeriodType)
38 }
39 }
40 periodType, err := CommonValueType(periodTypes)
41 if err != nil {
42 return fmt.Errorf("period type: %v", err)
43 }
44
45
46 numSampleTypes := len(profiles[0].SampleType)
47 for _, p := range profiles[1:] {
48 if numSampleTypes != len(p.SampleType) {
49 return fmt.Errorf("inconsistent samples type count: %d != %d", numSampleTypes, len(p.SampleType))
50 }
51 }
52 sampleType := make([]*profile.ValueType, numSampleTypes)
53 for i := 0; i < numSampleTypes; i++ {
54 sampleTypes := make([]*profile.ValueType, len(profiles))
55 for j, p := range profiles {
56 sampleTypes[j] = p.SampleType[i]
57 }
58 sampleType[i], err = CommonValueType(sampleTypes)
59 if err != nil {
60 return fmt.Errorf("sample types: %v", err)
61 }
62 }
63
64 for _, p := range profiles {
65 if p.PeriodType != nil && periodType != nil {
66 period, _ := Scale(p.Period, p.PeriodType.Unit, periodType.Unit)
67 p.Period, p.PeriodType.Unit = int64(period), periodType.Unit
68 }
69 ratios := make([]float64, len(p.SampleType))
70 for i, st := range p.SampleType {
71 if sampleType[i] == nil {
72 ratios[i] = 1
73 continue
74 }
75 ratios[i], _ = Scale(1, st.Unit, sampleType[i].Unit)
76 p.SampleType[i].Unit = sampleType[i].Unit
77 }
78 if err := p.ScaleN(ratios); err != nil {
79 return fmt.Errorf("scale: %v", err)
80 }
81 }
82 return nil
83 }
84
85
86
87 func CommonValueType(ts []*profile.ValueType) (*profile.ValueType, error) {
88 if len(ts) <= 1 {
89 return nil, nil
90 }
91 minType := ts[0]
92 for _, t := range ts[1:] {
93 if !compatibleValueTypes(minType, t) {
94 return nil, fmt.Errorf("incompatible types: %v %v", *minType, *t)
95 }
96 if ratio, _ := Scale(1, t.Unit, minType.Unit); ratio < 1 {
97 minType = t
98 }
99 }
100 rcopy := *minType
101 return &rcopy, nil
102 }
103
104 func compatibleValueTypes(v1, v2 *profile.ValueType) bool {
105 if v1 == nil || v2 == nil {
106 return true
107 }
108
109 if t1, t2 := strings.TrimSuffix(v1.Type, "s"), strings.TrimSuffix(v2.Type, "s"); t1 != t2 {
110 return false
111 }
112
113 return v1.Unit == v2.Unit ||
114 (timeUnits.sniffUnit(v1.Unit) != nil && timeUnits.sniffUnit(v2.Unit) != nil) ||
115 (memoryUnits.sniffUnit(v1.Unit) != nil && memoryUnits.sniffUnit(v2.Unit) != nil) ||
116 (gcuUnits.sniffUnit(v1.Unit) != nil && gcuUnits.sniffUnit(v2.Unit) != nil)
117 }
118
119
120
121
122 func Scale(value int64, fromUnit, toUnit string) (float64, string) {
123
124 if value < 0 && -value > 0 {
125 v, u := Scale(-value, fromUnit, toUnit)
126 return -v, u
127 }
128 if m, u, ok := memoryUnits.convertUnit(value, fromUnit, toUnit); ok {
129 return m, u
130 }
131 if t, u, ok := timeUnits.convertUnit(value, fromUnit, toUnit); ok {
132 return t, u
133 }
134 if g, u, ok := gcuUnits.convertUnit(value, fromUnit, toUnit); ok {
135 return g, u
136 }
137
138 switch toUnit {
139 case "count", "sample", "unit", "minimum", "auto":
140 return float64(value), ""
141 default:
142 return float64(value), toUnit
143 }
144 }
145
146
147 func Label(value int64, unit string) string {
148 return ScaledLabel(value, unit, "auto")
149 }
150
151
152
153 func ScaledLabel(value int64, fromUnit, toUnit string) string {
154 v, u := Scale(value, fromUnit, toUnit)
155 sv := strings.TrimSuffix(fmt.Sprintf("%.2f", v), ".00")
156 if sv == "0" || sv == "-0" {
157 return "0"
158 }
159 return sv + u
160 }
161
162
163
164 func Percentage(value, total int64) string {
165 var ratio float64
166 if total != 0 {
167 ratio = math.Abs(float64(value)/float64(total)) * 100
168 }
169 switch {
170 case math.Abs(ratio) >= 99.95 && math.Abs(ratio) <= 100.05:
171 return " 100%"
172 case math.Abs(ratio) >= 1.0:
173 return fmt.Sprintf("%5.2f%%", ratio)
174 default:
175 return fmt.Sprintf("%5.2g%%", ratio)
176 }
177 }
178
179
180
181
182 type unit struct {
183 canonicalName string
184 aliases []string
185 factor float64
186 }
187
188
189
190 type unitType struct {
191 defaultUnit unit
192 units []unit
193 }
194
195
196
197 func (ut unitType) findByAlias(alias string) *unit {
198 for _, u := range ut.units {
199 for _, a := range u.aliases {
200 if alias == a {
201 return &u
202 }
203 }
204 }
205 return nil
206 }
207
208
209
210 func (ut unitType) sniffUnit(unit string) *unit {
211 unit = strings.ToLower(unit)
212 if len(unit) > 2 {
213 unit = strings.TrimSuffix(unit, "s")
214 }
215 return ut.findByAlias(unit)
216 }
217
218
219
220
221 func (ut unitType) autoScale(value float64) (float64, string, bool) {
222 var f float64
223 var unit string
224 for _, u := range ut.units {
225 if u.factor >= f && (value/u.factor) >= 1.0 {
226 f = u.factor
227 unit = u.canonicalName
228 }
229 }
230 if f == 0 {
231 return 0, "", false
232 }
233 return value / f, unit, true
234 }
235
236
237
238
239
240
241 func (ut unitType) convertUnit(value int64, fromUnitStr, toUnitStr string) (float64, string, bool) {
242 fromUnit := ut.sniffUnit(fromUnitStr)
243 if fromUnit == nil {
244 return 0, "", false
245 }
246 v := float64(value) * fromUnit.factor
247 if toUnitStr == "minimum" || toUnitStr == "auto" {
248 if v, u, ok := ut.autoScale(v); ok {
249 return v, u, true
250 }
251 return v / ut.defaultUnit.factor, ut.defaultUnit.canonicalName, true
252 }
253 toUnit := ut.sniffUnit(toUnitStr)
254 if toUnit == nil {
255 return v / ut.defaultUnit.factor, ut.defaultUnit.canonicalName, true
256 }
257 return v / toUnit.factor, toUnit.canonicalName, true
258 }
259
260 var memoryUnits = unitType{
261 units: []unit{
262 {"B", []string{"b", "byte"}, 1},
263 {"kB", []string{"kb", "kbyte", "kilobyte"}, float64(1 << 10)},
264 {"MB", []string{"mb", "mbyte", "megabyte"}, float64(1 << 20)},
265 {"GB", []string{"gb", "gbyte", "gigabyte"}, float64(1 << 30)},
266 {"TB", []string{"tb", "tbyte", "terabyte"}, float64(1 << 40)},
267 {"PB", []string{"pb", "pbyte", "petabyte"}, float64(1 << 50)},
268 },
269 defaultUnit: unit{"B", []string{"b", "byte"}, 1},
270 }
271
272 var timeUnits = unitType{
273 units: []unit{
274 {"ns", []string{"ns", "nanosecond"}, float64(time.Nanosecond)},
275 {"us", []string{"μs", "us", "microsecond"}, float64(time.Microsecond)},
276 {"ms", []string{"ms", "millisecond"}, float64(time.Millisecond)},
277 {"s", []string{"s", "sec", "second"}, float64(time.Second)},
278 {"hrs", []string{"hour", "hr"}, float64(time.Hour)},
279 },
280 defaultUnit: unit{"s", []string{}, float64(time.Second)},
281 }
282
283 var gcuUnits = unitType{
284 units: []unit{
285 {"n*GCU", []string{"nanogcu"}, 1e-9},
286 {"u*GCU", []string{"microgcu"}, 1e-6},
287 {"m*GCU", []string{"milligcu"}, 1e-3},
288 {"GCU", []string{"gcu"}, 1},
289 {"k*GCU", []string{"kilogcu"}, 1e3},
290 {"M*GCU", []string{"megagcu"}, 1e6},
291 {"G*GCU", []string{"gigagcu"}, 1e9},
292 {"T*GCU", []string{"teragcu"}, 1e12},
293 {"P*GCU", []string{"petagcu"}, 1e15},
294 },
295 defaultUnit: unit{"GCU", []string{}, 1.0},
296 }
297
View as plain text