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  	"io"
    24  	"os"
    25  	"path/filepath"
    26  	"regexp"
    27  	"strings"
    28  
    29  	"github.com/google/pprof/internal/plugin"
    30  	"github.com/google/pprof/internal/report"
    31  	"github.com/google/pprof/profile"
    32  )
    33  
    34  // PProf acquires a profile, and symbolizes it using a profile
    35  // manager. Then it generates a report formatted according to the
    36  // options selected through the flags package.
    37  func PProf(eo *plugin.Options) error {
    38  	// Remove any temporary files created during pprof processing.
    39  	defer cleanupTempFiles()
    40  
    41  	o := setDefaults(eo)
    42  
    43  	src, cmd, err := parseFlags(o)
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	p, err := fetchProfiles(src, o)
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	if cmd != nil {
    54  		return generateReport(p, cmd, currentConfig(), o)
    55  	}
    56  
    57  	if src.HTTPHostport != "" {
    58  		return serveWebInterface(src.HTTPHostport, p, o, src.HTTPDisableBrowser)
    59  	}
    60  	return interactive(p, o)
    61  }
    62  
    63  // generateRawReport is allowed to modify p.
    64  func generateRawReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) (*command, *report.Report, error) {
    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  // generateReport is allowed to modify p.
   114  func generateReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) error {
   115  	c, rpt, err := generateRawReport(p, cmd, cfg, o)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	// Generate the report.
   121  	dst := new(bytes.Buffer)
   122  	switch rpt.OutputFormat() {
   123  	case report.WebList:
   124  		// We need template expansion, so generate here instead of in report.
   125  		err = printWebList(dst, rpt, o.Obj)
   126  	default:
   127  		err = report.Generate(dst, rpt, o.Obj)
   128  	}
   129  	if err != nil {
   130  		return err
   131  	}
   132  	src := dst
   133  
   134  	// If necessary, perform any data post-processing.
   135  	if c.postProcess != nil {
   136  		dst = new(bytes.Buffer)
   137  		if err := c.postProcess(src, dst, o.UI); err != nil {
   138  			return err
   139  		}
   140  		src = dst
   141  	}
   142  
   143  	// If no output is specified, use default visualizer.
   144  	output := cfg.Output
   145  	if output == "" {
   146  		if c.visualizer != nil {
   147  			return c.visualizer(src, os.Stdout, o.UI)
   148  		}
   149  		_, err := src.WriteTo(os.Stdout)
   150  		return err
   151  	}
   152  
   153  	// Output to specified file.
   154  	o.UI.PrintErr("Generating report in ", output)
   155  	out, err := o.Writer.Open(output)
   156  	if err != nil {
   157  		return err
   158  	}
   159  	if _, err := src.WriteTo(out); err != nil {
   160  		out.Close()
   161  		return err
   162  	}
   163  	return out.Close()
   164  }
   165  
   166  func printWebList(dst io.Writer, rpt *report.Report, obj plugin.ObjTool) error {
   167  	listing, err := report.MakeWebList(rpt, obj, -1)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	legend := report.ProfileLabels(rpt)
   172  	return renderHTML(dst, "sourcelisting", rpt, nil, legend, webArgs{
   173  		Standalone: true,
   174  		Listing:    listing,
   175  	})
   176  }
   177  
   178  func applyCommandOverrides(cmd string, outputFormat int, cfg config) config {
   179  	// Some report types override the trim flag to false below. This is to make
   180  	// sure the default heuristics of excluding insignificant nodes and edges
   181  	// from the call graph do not apply. One example where it is important is
   182  	// annotated source or disassembly listing. Those reports run on a specific
   183  	// function (or functions), but the trimming is applied before the function
   184  	// data is selected. So, with trimming enabled, the report could end up
   185  	// showing no data if the specified function is "uninteresting" as far as the
   186  	// trimming is concerned.
   187  	trim := cfg.Trim
   188  
   189  	switch cmd {
   190  	case "disasm":
   191  		trim = false
   192  		cfg.Granularity = "addresses"
   193  		// Force the 'noinlines' mode so that source locations for a given address
   194  		// collapse and there is only one for the given address. Without this
   195  		// cumulative metrics would be double-counted when annotating the assembly.
   196  		// This is because the merge is done by address and in case of an inlined
   197  		// stack each of the inlined entries is a separate callgraph node.
   198  		cfg.NoInlines = true
   199  	case "weblist":
   200  		trim = false
   201  		cfg.Granularity = "addresses"
   202  		cfg.NoInlines = false // Need inline info to support call expansion
   203  	case "peek":
   204  		trim = false
   205  	case "list":
   206  		trim = false
   207  		cfg.Granularity = "lines"
   208  		// Do not force 'noinlines' to be false so that specifying
   209  		// "-list foo -noinlines" is supported and works as expected.
   210  	case "text", "top", "topproto":
   211  		if cfg.NodeCount == -1 {
   212  			cfg.NodeCount = 0
   213  		}
   214  	default:
   215  		if cfg.NodeCount == -1 {
   216  			cfg.NodeCount = 80
   217  		}
   218  	}
   219  
   220  	switch outputFormat {
   221  	case report.Proto, report.Raw, report.Callgrind:
   222  		trim = false
   223  		cfg.Granularity = "addresses"
   224  	}
   225  
   226  	if !trim {
   227  		cfg.NodeCount = 0
   228  		cfg.NodeFraction = 0
   229  		cfg.EdgeFraction = 0
   230  	}
   231  	return cfg
   232  }
   233  
   234  // generateTagRootsLeaves generates extra nodes from the tagroot and tagleaf options.
   235  func generateTagRootsLeaves(prof *profile.Profile, cfg config, ui plugin.UI) {
   236  	tagRootLabelKeys := dropEmptyStrings(strings.Split(cfg.TagRoot, ","))
   237  	tagLeafLabelKeys := dropEmptyStrings(strings.Split(cfg.TagLeaf, ","))
   238  	rootm, leafm := addLabelNodes(prof, tagRootLabelKeys, tagLeafLabelKeys, cfg.Unit)
   239  	warnNoMatches(cfg.TagRoot == "" || rootm, "TagRoot", ui)
   240  	warnNoMatches(cfg.TagLeaf == "" || leafm, "TagLeaf", ui)
   241  }
   242  
   243  // dropEmptyStrings filters a slice to only non-empty strings
   244  func dropEmptyStrings(in []string) (out []string) {
   245  	for _, s := range in {
   246  		if s != "" {
   247  			out = append(out, s)
   248  		}
   249  	}
   250  	return
   251  }
   252  
   253  func aggregate(prof *profile.Profile, cfg config) error {
   254  	var function, filename, linenumber, address bool
   255  	inlines := !cfg.NoInlines
   256  	switch cfg.Granularity {
   257  	case "addresses":
   258  		if inlines {
   259  			return nil
   260  		}
   261  		function = true
   262  		filename = true
   263  		linenumber = true
   264  		address = true
   265  	case "lines":
   266  		function = true
   267  		filename = true
   268  		linenumber = true
   269  	case "files":
   270  		filename = true
   271  	case "functions":
   272  		function = true
   273  	case "filefunctions":
   274  		function = true
   275  		filename = true
   276  	default:
   277  		return fmt.Errorf("unexpected granularity")
   278  	}
   279  	return prof.Aggregate(inlines, function, filename, linenumber, cfg.ShowColumns, address)
   280  }
   281  
   282  func reportOptions(p *profile.Profile, numLabelUnits map[string]string, cfg config) (*report.Options, error) {
   283  	si, mean := cfg.SampleIndex, cfg.Mean
   284  	value, meanDiv, sample, err := sampleFormat(p, si, mean)
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  
   289  	stype := sample.Type
   290  	if mean {
   291  		stype = "mean_" + stype
   292  	}
   293  
   294  	if cfg.DivideBy == 0 {
   295  		return nil, fmt.Errorf("zero divisor specified")
   296  	}
   297  
   298  	var filters []string
   299  	addFilter := func(k string, v string) {
   300  		if v != "" {
   301  			filters = append(filters, k+"="+v)
   302  		}
   303  	}
   304  	addFilter("focus", cfg.Focus)
   305  	addFilter("ignore", cfg.Ignore)
   306  	addFilter("hide", cfg.Hide)
   307  	addFilter("show", cfg.Show)
   308  	addFilter("show_from", cfg.ShowFrom)
   309  	addFilter("tagfocus", cfg.TagFocus)
   310  	addFilter("tagignore", cfg.TagIgnore)
   311  	addFilter("tagshow", cfg.TagShow)
   312  	addFilter("taghide", cfg.TagHide)
   313  
   314  	ropt := &report.Options{
   315  		CumSort:      cfg.Sort == "cum",
   316  		CallTree:     cfg.CallTree,
   317  		DropNegative: cfg.DropNegative,
   318  
   319  		CompactLabels: cfg.CompactLabels,
   320  		Ratio:         1 / cfg.DivideBy,
   321  
   322  		NodeCount:    cfg.NodeCount,
   323  		NodeFraction: cfg.NodeFraction,
   324  		EdgeFraction: cfg.EdgeFraction,
   325  
   326  		ActiveFilters: filters,
   327  		NumLabelUnits: numLabelUnits,
   328  
   329  		SampleValue:       value,
   330  		SampleMeanDivisor: meanDiv,
   331  		SampleType:        stype,
   332  		SampleUnit:        sample.Unit,
   333  
   334  		OutputUnit: cfg.Unit,
   335  
   336  		SourcePath: cfg.SourcePath,
   337  		TrimPath:   cfg.TrimPath,
   338  
   339  		IntelSyntax: cfg.IntelSyntax,
   340  	}
   341  
   342  	if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
   343  		ropt.Title = filepath.Base(p.Mapping[0].File)
   344  	}
   345  
   346  	return ropt, nil
   347  }
   348  
   349  // identifyNumLabelUnits returns a map of numeric label keys to the units
   350  // associated with those keys.
   351  func identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string {
   352  	numLabelUnits, ignoredUnits := p.NumLabelUnits()
   353  
   354  	// Print errors for tags with multiple units associated with
   355  	// a single key.
   356  	for k, units := range ignoredUnits {
   357  		ui.PrintErr(fmt.Sprintf("For tag %s used unit %s, also encountered unit(s) %s", k, numLabelUnits[k], strings.Join(units, ", ")))
   358  	}
   359  	return numLabelUnits
   360  }
   361  
   362  type sampleValueFunc func([]int64) int64
   363  
   364  // sampleFormat returns a function to extract values out of a profile.Sample,
   365  // and the type/units of those values.
   366  func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (value, meanDiv sampleValueFunc, v *profile.ValueType, err error) {
   367  	if len(p.SampleType) == 0 {
   368  		return nil, nil, nil, fmt.Errorf("profile has no samples")
   369  	}
   370  	index, err := p.SampleIndexByName(sampleIndex)
   371  	if err != nil {
   372  		return nil, nil, nil, err
   373  	}
   374  	value = valueExtractor(index)
   375  	if mean {
   376  		meanDiv = valueExtractor(0)
   377  	}
   378  	v = p.SampleType[index]
   379  	return
   380  }
   381  
   382  func valueExtractor(ix int) sampleValueFunc {
   383  	return func(v []int64) int64 {
   384  		return v[ix]
   385  	}
   386  }
   387  
   388  // profileCopier can be used to obtain a fresh copy of a profile.
   389  // It is useful since reporting code may mutate the profile handed to it.
   390  type profileCopier []byte
   391  
   392  func makeProfileCopier(src *profile.Profile) profileCopier {
   393  	// Pre-serialize the profile. We will deserialize every time a fresh copy is needed.
   394  	var buf bytes.Buffer
   395  	src.WriteUncompressed(&buf)
   396  	return profileCopier(buf.Bytes())
   397  }
   398  
   399  // newCopy returns a new copy of the profile.
   400  func (c profileCopier) newCopy() *profile.Profile {
   401  	p, err := profile.ParseUncompressed([]byte(c))
   402  	if err != nil {
   403  		panic(err)
   404  	}
   405  	return p
   406  }
   407  

View as plain text