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