Source file src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go

     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.
    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  // ScaleProfiles updates the units in a set of profiles to make them
    29  // compatible. It scales the profiles to the smallest unit to preserve
    30  // data.
    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  	// Identify common sample types
    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  // CommonValueType returns the finest type from a set of compatible
    87  // types.
    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 // No grounds to disqualify.
   108  	}
   109  	// Remove trailing 's' to permit minor mismatches.
   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  // Scale a measurement from a unit to a different unit and returns
   126  // the scaled value and the target unit. The returned target unit
   127  // will be empty if uninteresting (could be skipped).
   128  func Scale(value int64, fromUnit, toUnit string) (float64, string) {
   129  	// Avoid infinite recursion on overflow.
   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  	// Skip non-interesting units.
   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  // Label returns the label used to describe a certain measurement.
   149  func Label(value int64, unit string) string {
   150  	return ScaledLabel(value, unit, "auto")
   151  }
   152  
   153  // ScaledLabel scales the passed-in measurement (if necessary) and
   154  // returns the label used to describe a float measurement.
   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  // Percentage computes the percentage of total of a value, and encodes
   165  // it as a string. At least two digits of precision are printed.
   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  // Unit includes a list of aliases representing a specific unit and a factor
   182  // which one can multiple a value in the specified unit by to get the value
   183  // in terms of the base unit.
   184  type Unit struct {
   185  	CanonicalName string
   186  	aliases       []string
   187  	Factor        float64
   188  }
   189  
   190  // UnitType includes a list of units that are within the same category (i.e.
   191  // memory or time units) and a default unit to use for this type of unit.
   192  type UnitType struct {
   193  	DefaultUnit Unit
   194  	Units       []Unit
   195  }
   196  
   197  // findByAlias returns the unit associated with the specified alias. It returns
   198  // nil if the unit with such alias is not found.
   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  // sniffUnit simplifies the input alias and returns the unit associated with the
   209  // specified alias. It returns nil if the unit with such alias is not found.
   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  // autoScale takes in the value with units of the base unit and returns
   219  // that value scaled to a reasonable unit if a reasonable unit is
   220  // found.
   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  // convertUnit converts a value from the fromUnit to the toUnit, autoscaling
   237  // the value if the toUnit is "minimum" or "auto". If the fromUnit is not
   238  // included in the unitType, then a false boolean will be returned. If the
   239  // toUnit is not in the unitType, the value will be returned in terms of the
   240  // default unitType.
   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  // UnitTypes holds the definition of units known to pprof.
   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