golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go (about)

     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  	"strings"
    21  	"time"
    22  
    23  	"github.com/google/pprof/profile"
    24  )
    25  
    26  // ScaleProfiles updates the units in a set of profiles to make them
    27  // compatible. It scales the profiles to the smallest unit to preserve
    28  // data.
    29  func ScaleProfiles(profiles []*profile.Profile) error {
    30  	if len(profiles) == 0 {
    31  		return nil
    32  	}
    33  	periodTypes := make([]*profile.ValueType, 0, len(profiles))
    34  	for _, p := range profiles {
    35  		if p.PeriodType != nil {
    36  			periodTypes = append(periodTypes, p.PeriodType)
    37  		}
    38  	}
    39  	periodType, err := CommonValueType(periodTypes)
    40  	if err != nil {
    41  		return fmt.Errorf("period type: %v", err)
    42  	}
    43  
    44  	// Identify common sample types
    45  	numSampleTypes := len(profiles[0].SampleType)
    46  	for _, p := range profiles[1:] {
    47  		if numSampleTypes != len(p.SampleType) {
    48  			return fmt.Errorf("inconsistent samples type count: %d != %d", numSampleTypes, len(p.SampleType))
    49  		}
    50  	}
    51  	sampleType := make([]*profile.ValueType, numSampleTypes)
    52  	for i := 0; i < numSampleTypes; i++ {
    53  		sampleTypes := make([]*profile.ValueType, len(profiles))
    54  		for j, p := range profiles {
    55  			sampleTypes[j] = p.SampleType[i]
    56  		}
    57  		sampleType[i], err = CommonValueType(sampleTypes)
    58  		if err != nil {
    59  			return fmt.Errorf("sample types: %v", err)
    60  		}
    61  	}
    62  
    63  	for _, p := range profiles {
    64  		if p.PeriodType != nil && periodType != nil {
    65  			period, _ := Scale(p.Period, p.PeriodType.Unit, periodType.Unit)
    66  			p.Period, p.PeriodType.Unit = int64(period), periodType.Unit
    67  		}
    68  		ratios := make([]float64, len(p.SampleType))
    69  		for i, st := range p.SampleType {
    70  			if sampleType[i] == nil {
    71  				ratios[i] = 1
    72  				continue
    73  			}
    74  			ratios[i], _ = Scale(1, st.Unit, sampleType[i].Unit)
    75  			p.SampleType[i].Unit = sampleType[i].Unit
    76  		}
    77  		if err := p.ScaleN(ratios); err != nil {
    78  			return fmt.Errorf("scale: %v", err)
    79  		}
    80  	}
    81  	return nil
    82  }
    83  
    84  // CommonValueType returns the finest type from a set of compatible
    85  // types.
    86  func CommonValueType(ts []*profile.ValueType) (*profile.ValueType, error) {
    87  	if len(ts) <= 1 {
    88  		return nil, nil
    89  	}
    90  	minType := ts[0]
    91  	for _, t := range ts[1:] {
    92  		if !compatibleValueTypes(minType, t) {
    93  			return nil, fmt.Errorf("incompatible types: %v %v", *minType, *t)
    94  		}
    95  		if ratio, _ := Scale(1, t.Unit, minType.Unit); ratio < 1 {
    96  			minType = t
    97  		}
    98  	}
    99  	rcopy := *minType
   100  	return &rcopy, nil
   101  }
   102  
   103  func compatibleValueTypes(v1, v2 *profile.ValueType) bool {
   104  	if v1 == nil || v2 == nil {
   105  		return true // No grounds to disqualify.
   106  	}
   107  	// Remove trailing 's' to permit minor mismatches.
   108  	if t1, t2 := strings.TrimSuffix(v1.Type, "s"), strings.TrimSuffix(v2.Type, "s"); t1 != t2 {
   109  		return false
   110  	}
   111  
   112  	return v1.Unit == v2.Unit ||
   113  		(isTimeUnit(v1.Unit) && isTimeUnit(v2.Unit)) ||
   114  		(isMemoryUnit(v1.Unit) && isMemoryUnit(v2.Unit))
   115  }
   116  
   117  // Scale a measurement from an unit to a different unit and returns
   118  // the scaled value and the target unit. The returned target unit
   119  // will be empty if uninteresting (could be skipped).
   120  func Scale(value int64, fromUnit, toUnit string) (float64, string) {
   121  	// Avoid infinite recursion on overflow.
   122  	if value < 0 && -value > 0 {
   123  		v, u := Scale(-value, fromUnit, toUnit)
   124  		return -v, u
   125  	}
   126  	if m, u, ok := memoryLabel(value, fromUnit, toUnit); ok {
   127  		return m, u
   128  	}
   129  	if t, u, ok := timeLabel(value, fromUnit, toUnit); ok {
   130  		return t, u
   131  	}
   132  	// Skip non-interesting units.
   133  	switch toUnit {
   134  	case "count", "sample", "unit", "minimum", "auto":
   135  		return float64(value), ""
   136  	default:
   137  		return float64(value), toUnit
   138  	}
   139  }
   140  
   141  // Label returns the label used to describe a certain measurement.
   142  func Label(value int64, unit string) string {
   143  	return ScaledLabel(value, unit, "auto")
   144  }
   145  
   146  // ScaledLabel scales the passed-in measurement (if necessary) and
   147  // returns the label used to describe a float measurement.
   148  func ScaledLabel(value int64, fromUnit, toUnit string) string {
   149  	v, u := Scale(value, fromUnit, toUnit)
   150  	sv := strings.TrimSuffix(fmt.Sprintf("%.2f", v), ".00")
   151  	if sv == "0" || sv == "-0" {
   152  		return "0"
   153  	}
   154  	return sv + u
   155  }
   156  
   157  // isMemoryUnit returns whether a name is recognized as a memory size
   158  // unit.
   159  func isMemoryUnit(unit string) bool {
   160  	switch strings.TrimSuffix(strings.ToLower(unit), "s") {
   161  	case "byte", "b", "kilobyte", "kb", "megabyte", "mb", "gigabyte", "gb":
   162  		return true
   163  	}
   164  	return false
   165  }
   166  
   167  func memoryLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
   168  	fromUnit = strings.TrimSuffix(strings.ToLower(fromUnit), "s")
   169  	toUnit = strings.TrimSuffix(strings.ToLower(toUnit), "s")
   170  
   171  	switch fromUnit {
   172  	case "byte", "b":
   173  	case "kilobyte", "kb":
   174  		value *= 1024
   175  	case "megabyte", "mb":
   176  		value *= 1024 * 1024
   177  	case "gigabyte", "gb":
   178  		value *= 1024 * 1024 * 1024
   179  	default:
   180  		return 0, "", false
   181  	}
   182  
   183  	if toUnit == "minimum" || toUnit == "auto" {
   184  		switch {
   185  		case value < 1024:
   186  			toUnit = "b"
   187  		case value < 1024*1024:
   188  			toUnit = "kb"
   189  		case value < 1024*1024*1024:
   190  			toUnit = "mb"
   191  		default:
   192  			toUnit = "gb"
   193  		}
   194  	}
   195  
   196  	var output float64
   197  	switch toUnit {
   198  	default:
   199  		output, toUnit = float64(value), "B"
   200  	case "kb", "kbyte", "kilobyte":
   201  		output, toUnit = float64(value)/1024, "kB"
   202  	case "mb", "mbyte", "megabyte":
   203  		output, toUnit = float64(value)/(1024*1024), "MB"
   204  	case "gb", "gbyte", "gigabyte":
   205  		output, toUnit = float64(value)/(1024*1024*1024), "GB"
   206  	}
   207  	return output, toUnit, true
   208  }
   209  
   210  // isTimeUnit returns whether a name is recognized as a time unit.
   211  func isTimeUnit(unit string) bool {
   212  	unit = strings.ToLower(unit)
   213  	if len(unit) > 2 {
   214  		unit = strings.TrimSuffix(unit, "s")
   215  	}
   216  
   217  	switch unit {
   218  	case "nanosecond", "ns", "microsecond", "millisecond", "ms", "s", "second", "sec", "hr", "day", "week", "year":
   219  		return true
   220  	}
   221  	return false
   222  }
   223  
   224  func timeLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
   225  	fromUnit = strings.ToLower(fromUnit)
   226  	if len(fromUnit) > 2 {
   227  		fromUnit = strings.TrimSuffix(fromUnit, "s")
   228  	}
   229  
   230  	toUnit = strings.ToLower(toUnit)
   231  	if len(toUnit) > 2 {
   232  		toUnit = strings.TrimSuffix(toUnit, "s")
   233  	}
   234  
   235  	var d time.Duration
   236  	switch fromUnit {
   237  	case "nanosecond", "ns":
   238  		d = time.Duration(value) * time.Nanosecond
   239  	case "microsecond":
   240  		d = time.Duration(value) * time.Microsecond
   241  	case "millisecond", "ms":
   242  		d = time.Duration(value) * time.Millisecond
   243  	case "second", "sec", "s":
   244  		d = time.Duration(value) * time.Second
   245  	case "cycle":
   246  		return float64(value), "", true
   247  	default:
   248  		return 0, "", false
   249  	}
   250  
   251  	if toUnit == "minimum" || toUnit == "auto" {
   252  		switch {
   253  		case d < 1*time.Microsecond:
   254  			toUnit = "ns"
   255  		case d < 1*time.Millisecond:
   256  			toUnit = "us"
   257  		case d < 1*time.Second:
   258  			toUnit = "ms"
   259  		case d < 1*time.Minute:
   260  			toUnit = "sec"
   261  		case d < 1*time.Hour:
   262  			toUnit = "min"
   263  		case d < 24*time.Hour:
   264  			toUnit = "hour"
   265  		case d < 15*24*time.Hour:
   266  			toUnit = "day"
   267  		case d < 120*24*time.Hour:
   268  			toUnit = "week"
   269  		default:
   270  			toUnit = "year"
   271  		}
   272  	}
   273  
   274  	var output float64
   275  	dd := float64(d)
   276  	switch toUnit {
   277  	case "ns", "nanosecond":
   278  		output, toUnit = dd/float64(time.Nanosecond), "ns"
   279  	case "us", "microsecond":
   280  		output, toUnit = dd/float64(time.Microsecond), "us"
   281  	case "ms", "millisecond":
   282  		output, toUnit = dd/float64(time.Millisecond), "ms"
   283  	case "min", "minute":
   284  		output, toUnit = dd/float64(time.Minute), "mins"
   285  	case "hour", "hr":
   286  		output, toUnit = dd/float64(time.Hour), "hrs"
   287  	case "day":
   288  		output, toUnit = dd/float64(24*time.Hour), "days"
   289  	case "week", "wk":
   290  		output, toUnit = dd/float64(7*24*time.Hour), "wks"
   291  	case "year", "yr":
   292  		output, toUnit = dd/float64(365*7*24*time.Hour), "yrs"
   293  	default:
   294  		fallthrough
   295  	case "sec", "second", "s":
   296  		output, toUnit = dd/float64(time.Second), "s"
   297  	}
   298  	return output, toUnit, true
   299  }