Black Lives Matter. Support the Equal Justice Initiative.

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

Documentation: cmd/vendor/github.com/google/pprof/internal/measurement

     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  	"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.
    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  	// 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.
    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 // 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  	return v1.Unit == v2.Unit ||
   114  		(isTimeUnit(v1.Unit) && isTimeUnit(v2.Unit)) ||
   115  		(isMemoryUnit(v1.Unit) && isMemoryUnit(v2.Unit))
   116  }
   117  
   118  // Scale a measurement from an unit to a different unit and returns
   119  // the scaled value and the target unit. The returned target unit
   120  // will be empty if uninteresting (could be skipped).
   121  func Scale(value int64, fromUnit, toUnit string) (float64, string) {
   122  	// Avoid infinite recursion on overflow.
   123  	if value < 0 && -value > 0 {
   124  		v, u := Scale(-value, fromUnit, toUnit)
   125  		return -v, u
   126  	}
   127  	if m, u, ok := memoryLabel(value, fromUnit, toUnit); ok {
   128  		return m, u
   129  	}
   130  	if t, u, ok := timeLabel(value, fromUnit, toUnit); ok {
   131  		return t, u
   132  	}
   133  	// Skip non-interesting units.
   134  	switch toUnit {
   135  	case "count", "sample", "unit", "minimum", "auto":
   136  		return float64(value), ""
   137  	default:
   138  		return float64(value), toUnit
   139  	}
   140  }
   141  
   142  // Label returns the label used to describe a certain measurement.
   143  func Label(value int64, unit string) string {
   144  	return ScaledLabel(value, unit, "auto")
   145  }
   146  
   147  // ScaledLabel scales the passed-in measurement (if necessary) and
   148  // returns the label used to describe a float measurement.
   149  func ScaledLabel(value int64, fromUnit, toUnit string) string {
   150  	v, u := Scale(value, fromUnit, toUnit)
   151  	sv := strings.TrimSuffix(fmt.Sprintf("%.2f", v), ".00")
   152  	if sv == "0" || sv == "-0" {
   153  		return "0"
   154  	}
   155  	return sv + u
   156  }
   157  
   158  // Percentage computes the percentage of total of a value, and encodes
   159  // it as a string. At least two digits of precision are printed.
   160  func Percentage(value, total int64) string {
   161  	var ratio float64
   162  	if total != 0 {
   163  		ratio = math.Abs(float64(value)/float64(total)) * 100
   164  	}
   165  	switch {
   166  	case math.Abs(ratio) >= 99.95 && math.Abs(ratio) <= 100.05:
   167  		return "  100%"
   168  	case math.Abs(ratio) >= 1.0:
   169  		return fmt.Sprintf("%5.2f%%", ratio)
   170  	default:
   171  		return fmt.Sprintf("%5.2g%%", ratio)
   172  	}
   173  }
   174  
   175  // isMemoryUnit returns whether a name is recognized as a memory size
   176  // unit.
   177  func isMemoryUnit(unit string) bool {
   178  	switch strings.TrimSuffix(strings.ToLower(unit), "s") {
   179  	case "byte", "b", "kilobyte", "kb", "megabyte", "mb", "gigabyte", "gb":
   180  		return true
   181  	}
   182  	return false
   183  }
   184  
   185  func memoryLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
   186  	fromUnit = strings.TrimSuffix(strings.ToLower(fromUnit), "s")
   187  	toUnit = strings.TrimSuffix(strings.ToLower(toUnit), "s")
   188  
   189  	switch fromUnit {
   190  	case "byte", "b":
   191  	case "kb", "kbyte", "kilobyte":
   192  		value *= 1024
   193  	case "mb", "mbyte", "megabyte":
   194  		value *= 1024 * 1024
   195  	case "gb", "gbyte", "gigabyte":
   196  		value *= 1024 * 1024 * 1024
   197  	case "tb", "tbyte", "terabyte":
   198  		value *= 1024 * 1024 * 1024 * 1024
   199  	case "pb", "pbyte", "petabyte":
   200  		value *= 1024 * 1024 * 1024 * 1024 * 1024
   201  	default:
   202  		return 0, "", false
   203  	}
   204  
   205  	if toUnit == "minimum" || toUnit == "auto" {
   206  		switch {
   207  		case value < 1024:
   208  			toUnit = "b"
   209  		case value < 1024*1024:
   210  			toUnit = "kb"
   211  		case value < 1024*1024*1024:
   212  			toUnit = "mb"
   213  		case value < 1024*1024*1024*1024:
   214  			toUnit = "gb"
   215  		case value < 1024*1024*1024*1024*1024:
   216  			toUnit = "tb"
   217  		default:
   218  			toUnit = "pb"
   219  		}
   220  	}
   221  
   222  	var output float64
   223  	switch toUnit {
   224  	default:
   225  		output, toUnit = float64(value), "B"
   226  	case "kb", "kbyte", "kilobyte":
   227  		output, toUnit = float64(value)/1024, "kB"
   228  	case "mb", "mbyte", "megabyte":
   229  		output, toUnit = float64(value)/(1024*1024), "MB"
   230  	case "gb", "gbyte", "gigabyte":
   231  		output, toUnit = float64(value)/(1024*1024*1024), "GB"
   232  	case "tb", "tbyte", "terabyte":
   233  		output, toUnit = float64(value)/(1024*1024*1024*1024), "TB"
   234  	case "pb", "pbyte", "petabyte":
   235  		output, toUnit = float64(value)/(1024*1024*1024*1024*1024), "PB"
   236  	}
   237  	return output, toUnit, true
   238  }
   239  
   240  // isTimeUnit returns whether a name is recognized as a time unit.
   241  func isTimeUnit(unit string) bool {
   242  	unit = strings.ToLower(unit)
   243  	if len(unit) > 2 {
   244  		unit = strings.TrimSuffix(unit, "s")
   245  	}
   246  
   247  	switch unit {
   248  	case "nanosecond", "ns", "microsecond", "millisecond", "ms", "s", "second", "sec", "hr", "day", "week", "year":
   249  		return true
   250  	}
   251  	return false
   252  }
   253  
   254  func timeLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
   255  	fromUnit = strings.ToLower(fromUnit)
   256  	if len(fromUnit) > 2 {
   257  		fromUnit = strings.TrimSuffix(fromUnit, "s")
   258  	}
   259  
   260  	toUnit = strings.ToLower(toUnit)
   261  	if len(toUnit) > 2 {
   262  		toUnit = strings.TrimSuffix(toUnit, "s")
   263  	}
   264  
   265  	var d time.Duration
   266  	switch fromUnit {
   267  	case "nanosecond", "ns":
   268  		d = time.Duration(value) * time.Nanosecond
   269  	case "microsecond":
   270  		d = time.Duration(value) * time.Microsecond
   271  	case "millisecond", "ms":
   272  		d = time.Duration(value) * time.Millisecond
   273  	case "second", "sec", "s":
   274  		d = time.Duration(value) * time.Second
   275  	case "cycle":
   276  		return float64(value), "", true
   277  	default:
   278  		return 0, "", false
   279  	}
   280  
   281  	if toUnit == "minimum" || toUnit == "auto" {
   282  		switch {
   283  		case d < 1*time.Microsecond:
   284  			toUnit = "ns"
   285  		case d < 1*time.Millisecond:
   286  			toUnit = "us"
   287  		case d < 1*time.Second:
   288  			toUnit = "ms"
   289  		case d < 1*time.Minute:
   290  			toUnit = "sec"
   291  		case d < 1*time.Hour:
   292  			toUnit = "min"
   293  		case d < 24*time.Hour:
   294  			toUnit = "hour"
   295  		case d < 15*24*time.Hour:
   296  			toUnit = "day"
   297  		case d < 120*24*time.Hour:
   298  			toUnit = "week"
   299  		default:
   300  			toUnit = "year"
   301  		}
   302  	}
   303  
   304  	var output float64
   305  	dd := float64(d)
   306  	switch toUnit {
   307  	case "ns", "nanosecond":
   308  		output, toUnit = dd/float64(time.Nanosecond), "ns"
   309  	case "us", "microsecond":
   310  		output, toUnit = dd/float64(time.Microsecond), "us"
   311  	case "ms", "millisecond":
   312  		output, toUnit = dd/float64(time.Millisecond), "ms"
   313  	case "min", "minute":
   314  		output, toUnit = dd/float64(time.Minute), "mins"
   315  	case "hour", "hr":
   316  		output, toUnit = dd/float64(time.Hour), "hrs"
   317  	case "day":
   318  		output, toUnit = dd/float64(24*time.Hour), "days"
   319  	case "week", "wk":
   320  		output, toUnit = dd/float64(7*24*time.Hour), "wks"
   321  	case "year", "yr":
   322  		output, toUnit = dd/float64(365*24*time.Hour), "yrs"
   323  	default:
   324  		// "sec", "second", "s" handled by default case.
   325  		output, toUnit = dd/float64(time.Second), "s"
   326  	}
   327  	return output, toUnit, true
   328  }
   329  

View as plain text