Source file src/cmd/vendor/github.com/google/pprof/internal/driver/driver.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 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  	// Create label pseudo nodes before filtering, in case the filters use
    77  	// the generated nodes.
    78  	generateTagRootsLeaves(p, cfg, o.UI)
    79  
    80  	// Delay focus after configuring report to get percentages on all samples.
    81  	relative := cfg.RelativePercentages
    82  	if relative {
    83  		if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {
    84  			return nil, nil, err
    85  		}
    86  	}
    87  	ropt, err := reportOptions(p, numLabelUnits, cfg)
    88  	if err != nil {
    89  		return nil, nil, err
    90  	}
    91  	ropt.OutputFormat = c.format
    92  	if len(cmd) == 2 {
    93  		s, err := regexp.Compile(cmd[1])
    94  		if err != nil {
    95  			return nil, nil, fmt.Errorf("parsing argument regexp %s: %v", cmd[1], err)
    96  		}
    97  		ropt.Symbol = s
    98  	}
    99  
   100  	rpt := report.New(p, ropt)
   101  	if !relative {
   102  		if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {
   103  			return nil, nil, err
   104  		}
   105  	}
   106  	if err := aggregate(p, cfg); err != nil {
   107  		return nil, nil, err
   108  	}
   109  
   110  	return c, rpt, nil
   111  }
   112  
   113  func generateReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) error {
   114  	c, rpt, err := generateRawReport(p, cmd, cfg, o)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	// Generate the report.
   120  	dst := new(bytes.Buffer)
   121  	if err := report.Generate(dst, rpt, o.Obj); err != nil {
   122  		return err
   123  	}
   124  	src := dst
   125  
   126  	// If necessary, perform any data post-processing.
   127  	if c.postProcess != nil {
   128  		dst = new(bytes.Buffer)
   129  		if err := c.postProcess(src, dst, o.UI); err != nil {
   130  			return err
   131  		}
   132  		src = dst
   133  	}
   134  
   135  	// If no output is specified, use default visualizer.
   136  	output := cfg.Output
   137  	if output == "" {
   138  		if c.visualizer != nil {
   139  			return c.visualizer(src, os.Stdout, o.UI)
   140  		}
   141  		_, err := src.WriteTo(os.Stdout)
   142  		return err
   143  	}
   144  
   145  	// Output to specified file.
   146  	o.UI.PrintErr("Generating report in ", output)
   147  	out, err := o.Writer.Open(output)
   148  	if err != nil {
   149  		return err
   150  	}
   151  	if _, err := src.WriteTo(out); err != nil {
   152  		out.Close()
   153  		return err
   154  	}
   155  	return out.Close()
   156  }
   157  
   158  func applyCommandOverrides(cmd string, outputFormat int, cfg config) config {
   159  	// Some report types override the trim flag to false below. This is to make
   160  	// sure the default heuristics of excluding insignificant nodes and edges
   161  	// from the call graph do not apply. One example where it is important is
   162  	// annotated source or disassembly listing. Those reports run on a specific
   163  	// function (or functions), but the trimming is applied before the function
   164  	// data is selected. So, with trimming enabled, the report could end up
   165  	// showing no data if the specified function is "uninteresting" as far as the
   166  	// trimming is concerned.
   167  	trim := cfg.Trim
   168  
   169  	switch cmd {
   170  	case "disasm":
   171  		trim = false
   172  		cfg.Granularity = "addresses"
   173  		// Force the 'noinlines' mode so that source locations for a given address
   174  		// collapse and there is only one for the given address. Without this
   175  		// cumulative metrics would be double-counted when annotating the assembly.
   176  		// This is because the merge is done by address and in case of an inlined
   177  		// stack each of the inlined entries is a separate callgraph node.
   178  		cfg.NoInlines = true
   179  	case "weblist":
   180  		trim = false
   181  		cfg.Granularity = "addresses"
   182  		cfg.NoInlines = false // Need inline info to support call expansion
   183  	case "peek":
   184  		trim = false
   185  	case "list":
   186  		trim = false
   187  		cfg.Granularity = "lines"
   188  		// Do not force 'noinlines' to be false so that specifying
   189  		// "-list foo -noinlines" is supported and works as expected.
   190  	case "text", "top", "topproto":
   191  		if cfg.NodeCount == -1 {
   192  			cfg.NodeCount = 0
   193  		}
   194  	default:
   195  		if cfg.NodeCount == -1 {
   196  			cfg.NodeCount = 80
   197  		}
   198  	}
   199  
   200  	switch outputFormat {
   201  	case report.Proto, report.Raw, report.Callgrind:
   202  		trim = false
   203  		cfg.Granularity = "addresses"
   204  		cfg.NoInlines = false
   205  	}
   206  
   207  	if !trim {
   208  		cfg.NodeCount = 0
   209  		cfg.NodeFraction = 0
   210  		cfg.EdgeFraction = 0
   211  	}
   212  	return cfg
   213  }
   214  
   215  // generateTagRootsLeaves generates extra nodes from the tagroot and tagleaf options.
   216  func generateTagRootsLeaves(prof *profile.Profile, cfg config, ui plugin.UI) {
   217  	tagRootLabelKeys := dropEmptyStrings(strings.Split(cfg.TagRoot, ","))
   218  	tagLeafLabelKeys := dropEmptyStrings(strings.Split(cfg.TagLeaf, ","))
   219  	rootm, leafm := addLabelNodes(prof, tagRootLabelKeys, tagLeafLabelKeys, cfg.Unit)
   220  	warnNoMatches(cfg.TagRoot == "" || rootm, "TagRoot", ui)
   221  	warnNoMatches(cfg.TagLeaf == "" || leafm, "TagLeaf", ui)
   222  }
   223  
   224  // dropEmptyStrings filters a slice to only non-empty strings
   225  func dropEmptyStrings(in []string) (out []string) {
   226  	for _, s := range in {
   227  		if s != "" {
   228  			out = append(out, s)
   229  		}
   230  	}
   231  	return
   232  }
   233  
   234  func aggregate(prof *profile.Profile, cfg config) error {
   235  	var function, filename, linenumber, address bool
   236  	inlines := !cfg.NoInlines
   237  	switch cfg.Granularity {
   238  	case "addresses":
   239  		if inlines {
   240  			return nil
   241  		}
   242  		function = true
   243  		filename = true
   244  		linenumber = true
   245  		address = true
   246  	case "lines":
   247  		function = true
   248  		filename = true
   249  		linenumber = true
   250  	case "files":
   251  		filename = true
   252  	case "functions":
   253  		function = true
   254  	case "filefunctions":
   255  		function = true
   256  		filename = true
   257  	default:
   258  		return fmt.Errorf("unexpected granularity")
   259  	}
   260  	return prof.Aggregate(inlines, function, filename, linenumber, address)
   261  }
   262  
   263  func reportOptions(p *profile.Profile, numLabelUnits map[string]string, cfg config) (*report.Options, error) {
   264  	si, mean := cfg.SampleIndex, cfg.Mean
   265  	value, meanDiv, sample, err := sampleFormat(p, si, mean)
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  
   270  	stype := sample.Type
   271  	if mean {
   272  		stype = "mean_" + stype
   273  	}
   274  
   275  	if cfg.DivideBy == 0 {
   276  		return nil, fmt.Errorf("zero divisor specified")
   277  	}
   278  
   279  	var filters []string
   280  	addFilter := func(k string, v string) {
   281  		if v != "" {
   282  			filters = append(filters, k+"="+v)
   283  		}
   284  	}
   285  	addFilter("focus", cfg.Focus)
   286  	addFilter("ignore", cfg.Ignore)
   287  	addFilter("hide", cfg.Hide)
   288  	addFilter("show", cfg.Show)
   289  	addFilter("show_from", cfg.ShowFrom)
   290  	addFilter("tagfocus", cfg.TagFocus)
   291  	addFilter("tagignore", cfg.TagIgnore)
   292  	addFilter("tagshow", cfg.TagShow)
   293  	addFilter("taghide", cfg.TagHide)
   294  
   295  	ropt := &report.Options{
   296  		CumSort:      cfg.Sort == "cum",
   297  		CallTree:     cfg.CallTree,
   298  		DropNegative: cfg.DropNegative,
   299  
   300  		CompactLabels: cfg.CompactLabels,
   301  		Ratio:         1 / cfg.DivideBy,
   302  
   303  		NodeCount:    cfg.NodeCount,
   304  		NodeFraction: cfg.NodeFraction,
   305  		EdgeFraction: cfg.EdgeFraction,
   306  
   307  		ActiveFilters: filters,
   308  		NumLabelUnits: numLabelUnits,
   309  
   310  		SampleValue:       value,
   311  		SampleMeanDivisor: meanDiv,
   312  		SampleType:        stype,
   313  		SampleUnit:        sample.Unit,
   314  
   315  		OutputUnit: cfg.Unit,
   316  
   317  		SourcePath: cfg.SourcePath,
   318  		TrimPath:   cfg.TrimPath,
   319  
   320  		IntelSyntax: cfg.IntelSyntax,
   321  	}
   322  
   323  	if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
   324  		ropt.Title = filepath.Base(p.Mapping[0].File)
   325  	}
   326  
   327  	return ropt, nil
   328  }
   329  
   330  // identifyNumLabelUnits returns a map of numeric label keys to the units
   331  // associated with those keys.
   332  func identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string {
   333  	numLabelUnits, ignoredUnits := p.NumLabelUnits()
   334  
   335  	// Print errors for tags with multiple units associated with
   336  	// a single key.
   337  	for k, units := range ignoredUnits {
   338  		ui.PrintErr(fmt.Sprintf("For tag %s used unit %s, also encountered unit(s) %s", k, numLabelUnits[k], strings.Join(units, ", ")))
   339  	}
   340  	return numLabelUnits
   341  }
   342  
   343  type sampleValueFunc func([]int64) int64
   344  
   345  // sampleFormat returns a function to extract values out of a profile.Sample,
   346  // and the type/units of those values.
   347  func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (value, meanDiv sampleValueFunc, v *profile.ValueType, err error) {
   348  	if len(p.SampleType) == 0 {
   349  		return nil, nil, nil, fmt.Errorf("profile has no samples")
   350  	}
   351  	index, err := p.SampleIndexByName(sampleIndex)
   352  	if err != nil {
   353  		return nil, nil, nil, err
   354  	}
   355  	value = valueExtractor(index)
   356  	if mean {
   357  		meanDiv = valueExtractor(0)
   358  	}
   359  	v = p.SampleType[index]
   360  	return
   361  }
   362  
   363  func valueExtractor(ix int) sampleValueFunc {
   364  	return func(v []int64) int64 {
   365  		return v[ix]
   366  	}
   367  }
   368  

View as plain text