...

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		"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