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 if v1.Unit == v2.Unit {
114 return true
115 }
116 for _, ut := range UnitTypes {
117 if ut.sniffUnit(v1.Unit) != nil && ut.sniffUnit(v2.Unit) != nil {
118 return true
119 }
120 }
121 return false
122 }
123
124
125
126
127 func Scale(value int64, fromUnit, toUnit string) (float64, string) {
128
129 if value < 0 && -value > 0 {
130 v, u := Scale(-value, fromUnit, toUnit)
131 return -v, u
132 }
133 for _, ut := range UnitTypes {
134 if v, u, ok := ut.convertUnit(value, fromUnit, toUnit); ok {
135 return v, u
136 }
137 }
138
139 switch toUnit {
140 case "count", "sample", "unit", "minimum", "auto":
141 return float64(value), ""
142 default:
143 return float64(value), toUnit
144 }
145 }
146
147
148 func Label(value int64, unit string) string {
149 return ScaledLabel(value, unit, "auto")
150 }
151
152
153
154 func ScaledLabel(value int64, fromUnit, toUnit string) string {
155 v, u := Scale(value, fromUnit, toUnit)
156 sv := strings.TrimSuffix(fmt.Sprintf("%.2f", v), ".00")
157 if sv == "0" || sv == "-0" {
158 return "0"
159 }
160 return sv + u
161 }
162
163
164
165 func Percentage(value, total int64) string {
166 var ratio float64
167 if total != 0 {
168 ratio = math.Abs(float64(value)/float64(total)) * 100
169 }
170 switch {
171 case math.Abs(ratio) >= 99.95 && math.Abs(ratio) <= 100.05:
172 return " 100%"
173 case math.Abs(ratio) >= 1.0:
174 return fmt.Sprintf("%5.2f%%", ratio)
175 default:
176 return fmt.Sprintf("%5.2g%%", ratio)
177 }
178 }
179
180
181
182
183 type Unit struct {
184 CanonicalName string
185 aliases []string
186 Factor float64
187 }
188
189
190
191 type UnitType struct {
192 DefaultUnit Unit
193 Units []Unit
194 }
195
196
197
198 func (ut UnitType) findByAlias(alias string) *Unit {
199 for _, u := range ut.Units {
200 for _, a := range u.aliases {
201 if alias == a {
202 return &u
203 }
204 }
205 }
206 return nil
207 }
208
209
210
211 func (ut UnitType) sniffUnit(unit string) *Unit {
212 unit = strings.ToLower(unit)
213 if len(unit) > 2 {
214 unit = strings.TrimSuffix(unit, "s")
215 }
216 return ut.findByAlias(unit)
217 }
218
219
220
221
222 func (ut UnitType) autoScale(value float64) (float64, string, bool) {
223 var f float64
224 var unit string
225 for _, u := range ut.Units {
226 if u.Factor >= f && (value/u.Factor) >= 1.0 {
227 f = u.Factor
228 unit = u.CanonicalName
229 }
230 }
231 if f == 0 {
232 return 0, "", false
233 }
234 return value / f, unit, true
235 }
236
237
238
239
240
241
242 func (ut UnitType) convertUnit(value int64, fromUnitStr, toUnitStr string) (float64, string, bool) {
243 fromUnit := ut.sniffUnit(fromUnitStr)
244 if fromUnit == nil {
245 return 0, "", false
246 }
247 v := float64(value) * fromUnit.Factor
248 if toUnitStr == "minimum" || toUnitStr == "auto" {
249 if v, u, ok := ut.autoScale(v); ok {
250 return v, u, true
251 }
252 return v / ut.DefaultUnit.Factor, ut.DefaultUnit.CanonicalName, true
253 }
254 toUnit := ut.sniffUnit(toUnitStr)
255 if toUnit == nil {
256 return v / ut.DefaultUnit.Factor, ut.DefaultUnit.CanonicalName, true
257 }
258 return v / toUnit.Factor, toUnit.CanonicalName, true
259 }
260
261
262 var UnitTypes = []UnitType{{
263 Units: []Unit{
264 {"B", []string{"b", "byte"}, 1},
265 {"kB", []string{"kb", "kbyte", "kilobyte"}, float64(1 << 10)},
266 {"MB", []string{"mb", "mbyte", "megabyte"}, float64(1 << 20)},
267 {"GB", []string{"gb", "gbyte", "gigabyte"}, float64(1 << 30)},
268 {"TB", []string{"tb", "tbyte", "terabyte"}, float64(1 << 40)},
269 {"PB", []string{"pb", "pbyte", "petabyte"}, float64(1 << 50)},
270 },
271 DefaultUnit: Unit{"B", []string{"b", "byte"}, 1},
272 }, {
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 Units: []Unit{
283 {"n*GCU", []string{"nanogcu"}, 1e-9},
284 {"u*GCU", []string{"microgcu"}, 1e-6},
285 {"m*GCU", []string{"milligcu"}, 1e-3},
286 {"GCU", []string{"gcu"}, 1},
287 {"k*GCU", []string{"kilogcu"}, 1e3},
288 {"M*GCU", []string{"megagcu"}, 1e6},
289 {"G*GCU", []string{"gigagcu"}, 1e9},
290 {"T*GCU", []string{"teragcu"}, 1e12},
291 {"P*GCU", []string{"petagcu"}, 1e15},
292 },
293 DefaultUnit: Unit{"GCU", []string{}, 1.0},
294 }}
295
View as plain text