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  // generateRawReport is allowed to modify p.
    63  func generateRawReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) (*command, *report.Report, error) {
    64  	// Identify units of numeric tags in profile.
    65  	numLabelUnits := identifyNumLabelUnits(p, o.UI)
    66  
    67  	// Get report output format
    68  	c := pprofCommands[cmd[0]]
    69  	if c == nil {
    70  		panic("unexpected nil command")
    71  	}
    72  
    73  	cfg = applyCommandOverrides(cmd[0], c.format, cfg)
    74  
    75  	// Create label pseudo nodes before filtering, in case the filters use
    76  	// the generated nodes.
    77  	generateTagRootsLeaves(p, cfg, o.UI)
    78  
    79  	// Delay focus after configuring report to get percentages on all samples.
    80  	relative := cfg.RelativePercentages
    81  	if relative {
    82  		if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {
    83  			return nil, nil, err
    84  		}
    85  	}
    86  	ropt, err := reportOptions(p, numLabelUnits, cfg)
    87  	if err != nil {
    88  		return nil, nil, err
    89  	}
    90  	ropt.OutputFormat = c.format
    91  	if len(cmd) == 2 {
    92  		s, err := regexp.Compile(cmd[1])
    93  		if err != nil {
    94  			return nil, nil, fmt.Errorf("parsing argument regexp %s: %v", cmd[1], err)
    95  		}
    96  		ropt.Symbol = s
    97  	}
    98  
    99  	rpt := report.New(p, ropt)
   100  	if !relative {
   101  		if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {
   102  			return nil, nil, err
   103  		}
   104  	}
   105  	if err := aggregate(p, cfg); err != nil {
   106  		return nil, nil, err
   107  	}
   108  
   109  	return c, rpt, nil
   110  }
   111  
   112  // generateReport is allowed to modify p.
   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  	}
   205  
   206  	if !trim {
   207  		cfg.NodeCount = 0
   208  		cfg.NodeFraction = 0
   209  		cfg.EdgeFraction = 0
   210  	}
   211  	return cfg
   212  }
   213  
   214  // generateTagRootsLeaves generates extra nodes from the tagroot and tagleaf options.
   215  func generateTagRootsLeaves(prof *profile.Profile, cfg config, ui plugin.UI) {
   216  	tagRootLabelKeys := dropEmptyStrings(strings.Split(cfg.TagRoot, ","))
   217  	tagLeafLabelKeys := dropEmptyStrings(strings.Split(cfg.TagLeaf, ","))
   218  	rootm, leafm := addLabelNodes(prof, tagRootLabelKeys, tagLeafLabelKeys, cfg.Unit)
   219  	warnNoMatches(cfg.TagRoot == "" || rootm, "TagRoot", ui)
   220  	warnNoMatches(cfg.TagLeaf == "" || leafm, "TagLeaf", ui)
   221  }
   222  
   223  // dropEmptyStrings filters a slice to only non-empty strings
   224  func dropEmptyStrings(in []string) (out []string) {
   225  	for _, s := range in {
   226  		if s != "" {
   227  			out = append(out, s)
   228  		}
   229  	}
   230  	return
   231  }
   232  
   233  func aggregate(prof *profile.Profile, cfg config) error {
   234  	var function, filename, linenumber, address bool
   235  	inlines := !cfg.NoInlines
   236  	switch cfg.Granularity {
   237  	case "addresses":
   238  		if inlines {
   239  			return nil
   240  		}
   241  		function = true
   242  		filename = true
   243  		linenumber = true
   244  		address = true
   245  	case "lines":
   246  		function = true
   247  		filename = true
   248  		linenumber = true
   249  	case "files":
   250  		filename = true
   251  	case "functions":
   252  		function = true
   253  	case "filefunctions":
   254  		function = true
   255  		filename = true
   256  	default:
   257  		return fmt.Errorf("unexpected granularity")
   258  	}
   259  	return prof.Aggregate(inlines, function, filename, linenumber, address)
   260  }
   261  
   262  func reportOptions(p *profile.Profile, numLabelUnits map[string]string, cfg config) (*report.Options, error) {
   263  	si, mean := cfg.SampleIndex, cfg.Mean
   264  	value, meanDiv, sample, err := sampleFormat(p, si, mean)
   265  	if err != nil {
   266  		return nil, err
   267  	}
   268  
   269  	stype := sample.Type
   270  	if mean {
   271  		stype = "mean_" + stype
   272  	}
   273  
   274  	if cfg.DivideBy == 0 {
   275  		return nil, fmt.Errorf("zero divisor specified")
   276  	}
   277  
   278  	var filters []string
   279  	addFilter := func(k string, v string) {
   280  		if v != "" {
   281  			filters = append(filters, k+"="+v)
   282  		}
   283  	}
   284  	addFilter("focus", cfg.Focus)
   285  	addFilter("ignore", cfg.Ignore)
   286  	addFilter("hide", cfg.Hide)
   287  	addFilter("show", cfg.Show)
   288  	addFilter("show_from", cfg.ShowFrom)
   289  	addFilter("tagfocus", cfg.TagFocus)
   290  	addFilter("tagignore", cfg.TagIgnore)
   291  	addFilter("tagshow", cfg.TagShow)
   292  	addFilter("taghide", cfg.TagHide)
   293  
   294  	ropt := &report.Options{
   295  		CumSort:      cfg.Sort == "cum",
   296  		CallTree:     cfg.CallTree,
   297  		DropNegative: cfg.DropNegative,
   298  
   299  		CompactLabels: cfg.CompactLabels,
   300  		Ratio:         1 / cfg.DivideBy,
   301  
   302  		NodeCount:    cfg.NodeCount,
   303  		NodeFraction: cfg.NodeFraction,
   304  		EdgeFraction: cfg.EdgeFraction,
   305  
   306  		ActiveFilters: filters,
   307  		NumLabelUnits: numLabelUnits,
   308  
   309  		SampleValue:       value,
   310  		SampleMeanDivisor: meanDiv,
   311  		SampleType:        stype,
   312  		SampleUnit:        sample.Unit,
   313  
   314  		OutputUnit: cfg.Unit,
   315  
   316  		SourcePath: cfg.SourcePath,
   317  		TrimPath:   cfg.TrimPath,
   318  
   319  		IntelSyntax: cfg.IntelSyntax,
   320  	}
   321  
   322  	if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
   323  		ropt.Title = filepath.Base(p.Mapping[0].File)
   324  	}
   325  
   326  	return ropt, nil
   327  }
   328  
   329  // identifyNumLabelUnits returns a map of numeric label keys to the units
   330  // associated with those keys.
   331  func identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string {
   332  	numLabelUnits, ignoredUnits := p.NumLabelUnits()
   333  
   334  	// Print errors for tags with multiple units associated with
   335  	// a single key.
   336  	for k, units := range ignoredUnits {
   337  		ui.PrintErr(fmt.Sprintf("For tag %s used unit %s, also encountered unit(s) %s", k, numLabelUnits[k], strings.Join(units, ", ")))
   338  	}
   339  	return numLabelUnits
   340  }
   341  
   342  type sampleValueFunc func([]int64) int64
   343  
   344  // sampleFormat returns a function to extract values out of a profile.Sample,
   345  // and the type/units of those values.
   346  func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (value, meanDiv sampleValueFunc, v *profile.ValueType, err error) {
   347  	if len(p.SampleType) == 0 {
   348  		return nil, nil, nil, fmt.Errorf("profile has no samples")
   349  	}
   350  	index, err := p.SampleIndexByName(sampleIndex)
   351  	if err != nil {
   352  		return nil, nil, nil, err
   353  	}
   354  	value = valueExtractor(index)
   355  	if mean {
   356  		meanDiv = valueExtractor(0)
   357  	}
   358  	v = p.SampleType[index]
   359  	return
   360  }
   361  
   362  func valueExtractor(ix int) sampleValueFunc {
   363  	return func(v []int64) int64 {
   364  		return v[ix]
   365  	}
   366  }
   367  
   368  // profileCopier can be used to obtain a fresh copy of a profile.
   369  // It is useful since reporting code may mutate the profile handed to it.
   370  type profileCopier []byte
   371  
   372  func makeProfileCopier(src *profile.Profile) profileCopier {
   373  	// Pre-serialize the profile. We will deserialize every time a fresh copy is needed.
   374  	var buf bytes.Buffer
   375  	src.WriteUncompressed(&buf)
   376  	return profileCopier(buf.Bytes())
   377  }
   378  
   379  // newCopy returns a new copy of the profile.
   380  func (c profileCopier) newCopy() *profile.Profile {
   381  	p, err := profile.ParseUncompressed([]byte(c))
   382  	if err != nil {
   383  		panic(err)
   384  	}
   385  	return p
   386  }
   387  

View as plain text