123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- // Copyright 2014 Google Inc. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- // Package measurement export utility functions to manipulate/format performance profile sample values.
- package measurement
-
- import (
- "fmt"
- "math"
- "strings"
- "time"
-
- "github.com/google/pprof/profile"
- )
-
- // ScaleProfiles updates the units in a set of profiles to make them
- // compatible. It scales the profiles to the smallest unit to preserve
- // data.
- func ScaleProfiles(profiles []*profile.Profile) error {
- if len(profiles) == 0 {
- return nil
- }
- periodTypes := make([]*profile.ValueType, 0, len(profiles))
- for _, p := range profiles {
- if p.PeriodType != nil {
- periodTypes = append(periodTypes, p.PeriodType)
- }
- }
- periodType, err := CommonValueType(periodTypes)
- if err != nil {
- return fmt.Errorf("period type: %v", err)
- }
-
- // Identify common sample types
- numSampleTypes := len(profiles[0].SampleType)
- for _, p := range profiles[1:] {
- if numSampleTypes != len(p.SampleType) {
- return fmt.Errorf("inconsistent samples type count: %d != %d", numSampleTypes, len(p.SampleType))
- }
- }
- sampleType := make([]*profile.ValueType, numSampleTypes)
- for i := 0; i < numSampleTypes; i++ {
- sampleTypes := make([]*profile.ValueType, len(profiles))
- for j, p := range profiles {
- sampleTypes[j] = p.SampleType[i]
- }
- sampleType[i], err = CommonValueType(sampleTypes)
- if err != nil {
- return fmt.Errorf("sample types: %v", err)
- }
- }
-
- for _, p := range profiles {
- if p.PeriodType != nil && periodType != nil {
- period, _ := Scale(p.Period, p.PeriodType.Unit, periodType.Unit)
- p.Period, p.PeriodType.Unit = int64(period), periodType.Unit
- }
- ratios := make([]float64, len(p.SampleType))
- for i, st := range p.SampleType {
- if sampleType[i] == nil {
- ratios[i] = 1
- continue
- }
- ratios[i], _ = Scale(1, st.Unit, sampleType[i].Unit)
- p.SampleType[i].Unit = sampleType[i].Unit
- }
- if err := p.ScaleN(ratios); err != nil {
- return fmt.Errorf("scale: %v", err)
- }
- }
- return nil
- }
-
- // CommonValueType returns the finest type from a set of compatible
- // types.
- func CommonValueType(ts []*profile.ValueType) (*profile.ValueType, error) {
- if len(ts) <= 1 {
- return nil, nil
- }
- minType := ts[0]
- for _, t := range ts[1:] {
- if !compatibleValueTypes(minType, t) {
- return nil, fmt.Errorf("incompatible types: %v %v", *minType, *t)
- }
- if ratio, _ := Scale(1, t.Unit, minType.Unit); ratio < 1 {
- minType = t
- }
- }
- rcopy := *minType
- return &rcopy, nil
- }
-
- func compatibleValueTypes(v1, v2 *profile.ValueType) bool {
- if v1 == nil || v2 == nil {
- return true // No grounds to disqualify.
- }
- // Remove trailing 's' to permit minor mismatches.
- if t1, t2 := strings.TrimSuffix(v1.Type, "s"), strings.TrimSuffix(v2.Type, "s"); t1 != t2 {
- return false
- }
-
- return v1.Unit == v2.Unit ||
- (isTimeUnit(v1.Unit) && isTimeUnit(v2.Unit)) ||
- (isMemoryUnit(v1.Unit) && isMemoryUnit(v2.Unit))
- }
-
- // Scale a measurement from an unit to a different unit and returns
- // the scaled value and the target unit. The returned target unit
- // will be empty if uninteresting (could be skipped).
- func Scale(value int64, fromUnit, toUnit string) (float64, string) {
- // Avoid infinite recursion on overflow.
- if value < 0 && -value > 0 {
- v, u := Scale(-value, fromUnit, toUnit)
- return -v, u
- }
- if m, u, ok := memoryLabel(value, fromUnit, toUnit); ok {
- return m, u
- }
- if t, u, ok := timeLabel(value, fromUnit, toUnit); ok {
- return t, u
- }
- // Skip non-interesting units.
- switch toUnit {
- case "count", "sample", "unit", "minimum", "auto":
- return float64(value), ""
- default:
- return float64(value), toUnit
- }
- }
-
- // Label returns the label used to describe a certain measurement.
- func Label(value int64, unit string) string {
- return ScaledLabel(value, unit, "auto")
- }
-
- // ScaledLabel scales the passed-in measurement (if necessary) and
- // returns the label used to describe a float measurement.
- func ScaledLabel(value int64, fromUnit, toUnit string) string {
- v, u := Scale(value, fromUnit, toUnit)
- sv := strings.TrimSuffix(fmt.Sprintf("%.2f", v), ".00")
- if sv == "0" || sv == "-0" {
- return "0"
- }
- return sv + u
- }
-
- // Percentage computes the percentage of total of a value, and encodes
- // it as a string. At least two digits of precision are printed.
- func Percentage(value, total int64) string {
- var ratio float64
- if total != 0 {
- ratio = math.Abs(float64(value)/float64(total)) * 100
- }
- switch {
- case math.Abs(ratio) >= 99.95 && math.Abs(ratio) <= 100.05:
- return " 100%"
- case math.Abs(ratio) >= 1.0:
- return fmt.Sprintf("%5.2f%%", ratio)
- default:
- return fmt.Sprintf("%5.2g%%", ratio)
- }
- }
-
- // isMemoryUnit returns whether a name is recognized as a memory size
- // unit.
- func isMemoryUnit(unit string) bool {
- switch strings.TrimSuffix(strings.ToLower(unit), "s") {
- case "byte", "b", "kilobyte", "kb", "megabyte", "mb", "gigabyte", "gb":
- return true
- }
- return false
- }
-
- func memoryLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
- fromUnit = strings.TrimSuffix(strings.ToLower(fromUnit), "s")
- toUnit = strings.TrimSuffix(strings.ToLower(toUnit), "s")
-
- switch fromUnit {
- case "byte", "b":
- case "kb", "kbyte", "kilobyte":
- value *= 1024
- case "mb", "mbyte", "megabyte":
- value *= 1024 * 1024
- case "gb", "gbyte", "gigabyte":
- value *= 1024 * 1024 * 1024
- case "tb", "tbyte", "terabyte":
- value *= 1024 * 1024 * 1024 * 1024
- case "pb", "pbyte", "petabyte":
- value *= 1024 * 1024 * 1024 * 1024 * 1024
- default:
- return 0, "", false
- }
-
- if toUnit == "minimum" || toUnit == "auto" {
- switch {
- case value < 1024:
- toUnit = "b"
- case value < 1024*1024:
- toUnit = "kb"
- case value < 1024*1024*1024:
- toUnit = "mb"
- case value < 1024*1024*1024*1024:
- toUnit = "gb"
- case value < 1024*1024*1024*1024*1024:
- toUnit = "tb"
- default:
- toUnit = "pb"
- }
- }
-
- var output float64
- switch toUnit {
- default:
- output, toUnit = float64(value), "B"
- case "kb", "kbyte", "kilobyte":
- output, toUnit = float64(value)/1024, "kB"
- case "mb", "mbyte", "megabyte":
- output, toUnit = float64(value)/(1024*1024), "MB"
- case "gb", "gbyte", "gigabyte":
- output, toUnit = float64(value)/(1024*1024*1024), "GB"
- case "tb", "tbyte", "terabyte":
- output, toUnit = float64(value)/(1024*1024*1024*1024), "TB"
- case "pb", "pbyte", "petabyte":
- output, toUnit = float64(value)/(1024*1024*1024*1024*1024), "PB"
- }
- return output, toUnit, true
- }
-
- // isTimeUnit returns whether a name is recognized as a time unit.
- func isTimeUnit(unit string) bool {
- unit = strings.ToLower(unit)
- if len(unit) > 2 {
- unit = strings.TrimSuffix(unit, "s")
- }
-
- switch unit {
- case "nanosecond", "ns", "microsecond", "millisecond", "ms", "s", "second", "sec", "hr", "day", "week", "year":
- return true
- }
- return false
- }
-
- func timeLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
- fromUnit = strings.ToLower(fromUnit)
- if len(fromUnit) > 2 {
- fromUnit = strings.TrimSuffix(fromUnit, "s")
- }
-
- toUnit = strings.ToLower(toUnit)
- if len(toUnit) > 2 {
- toUnit = strings.TrimSuffix(toUnit, "s")
- }
-
- var d time.Duration
- switch fromUnit {
- case "nanosecond", "ns":
- d = time.Duration(value) * time.Nanosecond
- case "microsecond":
- d = time.Duration(value) * time.Microsecond
- case "millisecond", "ms":
- d = time.Duration(value) * time.Millisecond
- case "second", "sec", "s":
- d = time.Duration(value) * time.Second
- case "cycle":
- return float64(value), "", true
- default:
- return 0, "", false
- }
-
- if toUnit == "minimum" || toUnit == "auto" {
- switch {
- case d < 1*time.Microsecond:
- toUnit = "ns"
- case d < 1*time.Millisecond:
- toUnit = "us"
- case d < 1*time.Second:
- toUnit = "ms"
- case d < 1*time.Minute:
- toUnit = "sec"
- case d < 1*time.Hour:
- toUnit = "min"
- case d < 24*time.Hour:
- toUnit = "hour"
- case d < 15*24*time.Hour:
- toUnit = "day"
- case d < 120*24*time.Hour:
- toUnit = "week"
- default:
- toUnit = "year"
- }
- }
-
- var output float64
- dd := float64(d)
- switch toUnit {
- case "ns", "nanosecond":
- output, toUnit = dd/float64(time.Nanosecond), "ns"
- case "us", "microsecond":
- output, toUnit = dd/float64(time.Microsecond), "us"
- case "ms", "millisecond":
- output, toUnit = dd/float64(time.Millisecond), "ms"
- case "min", "minute":
- output, toUnit = dd/float64(time.Minute), "mins"
- case "hour", "hr":
- output, toUnit = dd/float64(time.Hour), "hrs"
- case "day":
- output, toUnit = dd/float64(24*time.Hour), "days"
- case "week", "wk":
- output, toUnit = dd/float64(7*24*time.Hour), "wks"
- case "year", "yr":
- output, toUnit = dd/float64(365*24*time.Hour), "yrs"
- default:
- fallthrough
- case "sec", "second", "s":
- output, toUnit = dd/float64(time.Second), "s"
- }
- return output, toUnit, true
- }
|