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.
16package measurement
17
18import (
19	"fmt"
20	"math"
21	"strings"
22	"time"
23
24	"github.com/google/pprof/profile"
25)
26
27// ScaleProfiles updates the units in a set of profiles to make them
28// compatible. It scales the profiles to the smallest unit to preserve
29// data.
30func 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	// Identify common sample types
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// CommonValueType returns the finest type from a set of compatible
86// types.
87func 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
104func compatibleValueTypes(v1, v2 *profile.ValueType) bool {
105	if v1 == nil || v2 == nil {
106		return true // No grounds to disqualify.
107	}
108	// Remove trailing 's' to permit minor mismatches.
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// Scale a measurement from a unit to a different unit and returns
125// the scaled value and the target unit. The returned target unit
126// will be empty if uninteresting (could be skipped).
127func Scale(value int64, fromUnit, toUnit string) (float64, string) {
128	// Avoid infinite recursion on overflow.
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	// Skip non-interesting units.
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// Label returns the label used to describe a certain measurement.
148func Label(value int64, unit string) string {
149	return ScaledLabel(value, unit, "auto")
150}
151
152// ScaledLabel scales the passed-in measurement (if necessary) and
153// returns the label used to describe a float measurement.
154func 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// Percentage computes the percentage of total of a value, and encodes
164// it as a string. At least two digits of precision are printed.
165func 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// Unit includes a list of aliases representing a specific unit and a factor
181// which one can multiple a value in the specified unit by to get the value
182// in terms of the base unit.
183type Unit struct {
184	CanonicalName string
185	aliases       []string
186	Factor        float64
187}
188
189// UnitType includes a list of units that are within the same category (i.e.
190// memory or time units) and a default unit to use for this type of unit.
191type UnitType struct {
192	DefaultUnit Unit
193	Units       []Unit
194}
195
196// findByAlias returns the unit associated with the specified alias. It returns
197// nil if the unit with such alias is not found.
198func (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// sniffUnit simpifies the input alias and returns the unit associated with the
210// specified alias. It returns nil if the unit with such alias is not found.
211func (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// autoScale takes in the value with units of the base unit and returns
220// that value scaled to a reasonable unit if a reasonable unit is
221// found.
222func (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// convertUnit converts a value from the fromUnit to the toUnit, autoscaling
238// the value if the toUnit is "minimum" or "auto". If the fromUnit is not
239// included in the unitType, then a false boolean will be returned. If the
240// toUnit is not in the unitType, the value will be returned in terms of the
241// default unitType.
242func (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// UnitTypes holds the definition of units known to pprof.
262var 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