Black Lives Matter. Support the Equal Justice Initiative.

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

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

     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 driver implements the core pprof functionality. It can be
    16  // parameterized with a flag implementation, fetch and symbolize
    17  // mechanisms.
    18  package driver
    19  
    20  import (
    21  	"bytes"
    22  	"fmt"
    23  	"os"
    24  	"path/filepath"
    25  	"regexp"
    26  	"strings"
    27  
    28  	"github.com/google/pprof/internal/plugin"
    29  	"github.com/google/pprof/internal/report"
    30  	"github.com/google/pprof/profile"
    31  )
    32  
    33  // PProf acquires a profile, and symbolizes it using a profile
    34  // manager. Then it generates a report formatted according to the
    35  // options selected through the flags package.
    36  func PProf(eo *plugin.Options) error {
    37  	// Remove any temporary files created during pprof processing.
    38  	defer cleanupTempFiles()
    39  
    40  	o := setDefaults(eo)
    41  
    42  	src, cmd, err := parseFlags(o)
    43  	if err != nil {
    44  		return err
    45  	}
    46  
    47  	p, err := fetchProfiles(src, o)
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	if cmd != nil {
    53  		return generateReport(p, cmd, currentConfig(), o)
    54  	}
    55  
    56  	if src.HTTPHostport != "" {
    57  		return serveWebInterface(src.HTTPHostport, p, o, src.HTTPDisableBrowser)
    58  	}
    59  	return interactive(p, o)
    60  }
    61  
    62  func generateRawReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) (*command, *report.Report, error) {
    63  	p = p.Copy() // Prevent modification to the incoming profile.
    64  
    65  	// Identify units of numeric tags in profile.
    66  	numLabelUnits := identifyNumLabelUnits(p, o.UI)
    67  
    68  	// Get report output format
    69  	c := pprofCommands[cmd[0]]
    70  	if c == nil {
    71  		panic("unexpected nil command")
    72  	}
    73  
    74  	cfg = applyCommandOverrides(cmd[0], c.format, cfg)
    75  
    76  	// Delay focus after configuring report to get percentages on all samples.
    77  	relative := cfg.RelativePercentages
    78  	if relative {
    79  		if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {
    80  			return nil, nil, err
    81  		}
    82  	}
    83  	ropt, err := reportOptions(p, numLabelUnits, cfg)
    84  	if err != nil {
    85  		return nil, nil, err
    86  	}
    87  	ropt.OutputFormat = c.format
    88  	if len(cmd) == 2 {
    89  		s, err := regexp.Compile(cmd[1])
    90  		if err != nil {
    91  			return nil, nil, fmt.Errorf("parsing argument regexp %s: %v", cmd[1], err)
    92  		}
    93  		ropt.Symbol = s
    94  	}
    95  
    96  	rpt := report.New(p, ropt)
    97  	if !relative {
    98  		if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {
    99  			return nil, nil, err
   100  		}
   101  	}
   102  	if err := aggregate(p, cfg); err != nil {
   103  		return nil, nil, err
   104  	}
   105  
   106  	return c, rpt, nil
   107  }
   108  
   109  func generateReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) error {
   110  	c, rpt, err := generateRawReport(p, cmd, cfg, o)
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	// Generate the report.
   116  	dst := new(bytes.Buffer)
   117  	if err := report.Generate(dst, rpt, o.Obj); err != nil {
   118  		return err
   119  	}
   120  	src := dst
   121  
   122  	// If necessary, perform any data post-processing.
   123  	if c.postProcess != nil {
   124  		dst = new(bytes.Buffer)
   125  		if err := c.postProcess(src, dst, o.UI); err != nil {
   126  			return err
   127  		}
   128  		src = dst
   129  	}
   130  
   131  	// If no output is specified, use default visualizer.
   132  	output := cfg.Output
   133  	if output == "" {
   134  		if c.visualizer != nil {
   135  			return c.visualizer(src, os.Stdout, o.UI)
   136  		}
   137  		_, err := src.WriteTo(os.Stdout)
   138  		return err
   139  	}
   140  
   141  	// Output to specified file.
   142  	o.UI.PrintErr("Generating report in ", output)
   143  	out, err := o.Writer.Open(output)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	if _, err := src.WriteTo(out); err != nil {
   148  		out.Close()
   149  		return err
   150  	}
   151  	return out.Close()
   152  }
   153  
   154  func applyCommandOverrides(cmd string, outputFormat int, cfg config) config {
   155  	// Some report types override the trim flag to false below. This is to make
   156  	// sure the default heuristics of excluding insignificant nodes and edges
   157  	// from the call graph do not apply. One example where it is important is
   158  	// annotated source or disassembly listing. Those reports run on a specific
   159  	// function (or functions), but the trimming is applied before the function
   160  	// data is selected. So, with trimming enabled, the report could end up
   161  	// showing no data if the specified function is "uninteresting" as far as the
   162  	// trimming is concerned.
   163  	trim := cfg.Trim
   164  
   165  	switch cmd {
   166  	case "disasm", "weblist":
   167  		trim = false
   168  		cfg.Granularity = "addresses"
   169  		// Force the 'noinlines' mode so that source locations for a given address
   170  		// collapse and there is only one for the given address. Without this
   171  		// cumulative metrics would be double-counted when annotating the assembly.
   172  		// This is because the merge is done by address and in case of an inlined
   173  		// stack each of the inlined entries is a separate callgraph node.
   174  		cfg.NoInlines = true
   175  	case "peek":
   176  		trim = false
   177  	case "list":
   178  		trim = false
   179  		cfg.Granularity = "lines"
   180  		// Do not force 'noinlines' to be false so that specifying
   181  		// "-list foo -noinlines" is supported and works as expected.
   182  	case "text", "top", "topproto":
   183  		if cfg.NodeCount == -1 {
   184  			cfg.NodeCount = 0
   185  		}
   186  	default:
   187  		if cfg.NodeCount == -1 {
   188  			cfg.NodeCount = 80
   189  		}
   190  	}
   191  
   192  	switch outputFormat {
   193  	case report.Proto, report.Raw, report.Callgrind:
   194  		trim = false
   195  		cfg.Granularity = "addresses"
   196  		cfg.NoInlines = false
   197  	}
   198  
   199  	if !trim {
   200  		cfg.NodeCount = 0
   201  		cfg.NodeFraction = 0
   202  		cfg.EdgeFraction = 0
   203  	}
   204  	return cfg
   205  }
   206  
   207  func aggregate(prof *profile.Profile, cfg config) error {
   208  	var function, filename, linenumber, address bool
   209  	inlines := !cfg.NoInlines
   210  	switch cfg.Granularity {
   211  	case "addresses":
   212  		if inlines {
   213  			return nil
   214  		}
   215  		function = true
   216  		filename = true
   217  		linenumber = true
   218  		address = true
   219  	case "lines":
   220  		function = true
   221  		filename = true
   222  		linenumber = true
   223  	case "files":
   224  		filename = true
   225  	case "functions":
   226  		function = true
   227  	case "filefunctions":
   228  		function = true
   229  		filename = true
   230  	default:
   231  		return fmt.Errorf("unexpected granularity")
   232  	}
   233  	return prof.Aggregate(inlines, function, filename, linenumber, address)
   234  }
   235  
   236  func reportOptions(p *profile.Profile, numLabelUnits map[string]string, cfg config) (*report.Options, error) {
   237  	si, mean := cfg.SampleIndex, cfg.Mean
   238  	value, meanDiv, sample, err := sampleFormat(p, si, mean)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  
   243  	stype := sample.Type
   244  	if mean {
   245  		stype = "mean_" + stype
   246  	}
   247  
   248  	if cfg.DivideBy == 0 {
   249  		return nil, fmt.Errorf("zero divisor specified")
   250  	}
   251  
   252  	var filters []string
   253  	addFilter := func(k string, v string) {
   254  		if v != "" {
   255  			filters = append(filters, k+"="+v)
   256  		}
   257  	}
   258  	addFilter("focus", cfg.Focus)
   259  	addFilter("ignore", cfg.Ignore)
   260  	addFilter("hide", cfg.Hide)
   261  	addFilter("show", cfg.Show)
   262  	addFilter("show_from", cfg.ShowFrom)
   263  	addFilter("tagfocus", cfg.TagFocus)
   264  	addFilter("tagignore", cfg.TagIgnore)
   265  	addFilter("tagshow", cfg.TagShow)
   266  	addFilter("taghide", cfg.TagHide)
   267  
   268  	ropt := &report.Options{
   269  		CumSort:      cfg.Sort == "cum",
   270  		CallTree:     cfg.CallTree,
   271  		DropNegative: cfg.DropNegative,
   272  
   273  		CompactLabels: cfg.CompactLabels,
   274  		Ratio:         1 / cfg.DivideBy,
   275  
   276  		NodeCount:    cfg.NodeCount,
   277  		NodeFraction: cfg.NodeFraction,
   278  		EdgeFraction: cfg.EdgeFraction,
   279  
   280  		ActiveFilters: filters,
   281  		NumLabelUnits: numLabelUnits,
   282  
   283  		SampleValue:       value,
   284  		SampleMeanDivisor: meanDiv,
   285  		SampleType:        stype,
   286  		SampleUnit:        sample.Unit,
   287  
   288  		OutputUnit: cfg.Unit,
   289  
   290  		SourcePath: cfg.SourcePath,
   291  		TrimPath:   cfg.TrimPath,
   292  
   293  		IntelSyntax: cfg.IntelSyntax,
   294  	}
   295  
   296  	if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
   297  		ropt.Title = filepath.Base(p.Mapping[0].File)
   298  	}
   299  
   300  	return ropt, nil
   301  }
   302  
   303  // identifyNumLabelUnits returns a map of numeric label keys to the units
   304  // associated with those keys.
   305  func identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string {
   306  	numLabelUnits, ignoredUnits := p.NumLabelUnits()
   307  
   308  	// Print errors for tags with multiple units associated with
   309  	// a single key.
   310  	for k, units := range ignoredUnits {
   311  		ui.PrintErr(fmt.Sprintf("For tag %s used unit %s, also encountered unit(s) %s", k, numLabelUnits[k], strings.Join(units, ", ")))
   312  	}
   313  	return numLabelUnits
   314  }
   315  
   316  type sampleValueFunc func([]int64) int64
   317  
   318  // sampleFormat returns a function to extract values out of a profile.Sample,
   319  // and the type/units of those values.
   320  func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (value, meanDiv sampleValueFunc, v *profile.ValueType, err error) {
   321  	if len(p.SampleType) == 0 {
   322  		return nil, nil, nil, fmt.Errorf("profile has no samples")
   323  	}
   324  	index, err := p.SampleIndexByName(sampleIndex)
   325  	if err != nil {
   326  		return nil, nil, nil, err
   327  	}
   328  	value = valueExtractor(index)
   329  	if mean {
   330  		meanDiv = valueExtractor(0)
   331  	}
   332  	v = p.SampleType[index]
   333  	return
   334  }
   335  
   336  func valueExtractor(ix int) sampleValueFunc {
   337  	return func(v []int64) int64 {
   338  		return v[ix]
   339  	}
   340  }
   341  

View as plain text