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 "":
   258  		function = true // Default granularity is "functions"
   259  	case "addresses":
   260  		if inlines {
   261  			return nil
   262  		}
   263  		function = true
   264  		filename = true
   265  		linenumber = true
   266  		address = true
   267  	case "lines":
   268  		function = true
   269  		filename = true
   270  		linenumber = true
   271  	case "files":
   272  		filename = true
   273  	case "functions":
   274  		function = true
   275  	case "filefunctions":
   276  		function = true
   277  		filename = true
   278  	default:
   279  		return fmt.Errorf("unexpected granularity")
   280  	}
   281  	return prof.Aggregate(inlines, function, filename, linenumber, cfg.ShowColumns, address)
   282  }
   283  
   284  func reportOptions(p *profile.Profile, numLabelUnits map[string]string, cfg config) (*report.Options, error) {
   285  	si, mean := cfg.SampleIndex, cfg.Mean
   286  	value, meanDiv, sample, err := sampleFormat(p, si, mean)
   287  	if err != nil {
   288  		return nil, err
   289  	}
   290  
   291  	stype := sample.Type
   292  	if mean {
   293  		stype = "mean_" + stype
   294  	}
   295  
   296  	if cfg.DivideBy == 0 {
   297  		return nil, fmt.Errorf("zero divisor specified")
   298  	}
   299  
   300  	var filters []string
   301  	addFilter := func(k string, v string) {
   302  		if v != "" {
   303  			filters = append(filters, k+"="+v)
   304  		}
   305  	}
   306  	addFilter("focus", cfg.Focus)
   307  	addFilter("ignore", cfg.Ignore)
   308  	addFilter("hide", cfg.Hide)
   309  	addFilter("show", cfg.Show)
   310  	addFilter("show_from", cfg.ShowFrom)
   311  	addFilter("tagfocus", cfg.TagFocus)
   312  	addFilter("tagignore", cfg.TagIgnore)
   313  	addFilter("tagshow", cfg.TagShow)
   314  	addFilter("taghide", cfg.TagHide)
   315  
   316  	ropt := &report.Options{
   317  		CumSort:      cfg.Sort == "cum",
   318  		CallTree:     cfg.CallTree,
   319  		DropNegative: cfg.DropNegative,
   320  
   321  		CompactLabels: cfg.CompactLabels,
   322  		Ratio:         1 / cfg.DivideBy,
   323  
   324  		NodeCount:    cfg.NodeCount,
   325  		NodeFraction: cfg.NodeFraction,
   326  		EdgeFraction: cfg.EdgeFraction,
   327  
   328  		ActiveFilters: filters,
   329  		NumLabelUnits: numLabelUnits,
   330  
   331  		SampleValue:       value,
   332  		SampleMeanDivisor: meanDiv,
   333  		SampleType:        stype,
   334  		SampleUnit:        sample.Unit,
   335  
   336  		OutputUnit: cfg.Unit,
   337  
   338  		SourcePath: cfg.SourcePath,
   339  		TrimPath:   cfg.TrimPath,
   340  
   341  		IntelSyntax: cfg.IntelSyntax,
   342  	}
   343  
   344  	if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
   345  		ropt.Title = filepath.Base(p.Mapping[0].File)
   346  	}
   347  
   348  	return ropt, nil
   349  }
   350  
   351  // identifyNumLabelUnits returns a map of numeric label keys to the units
   352  // associated with those keys.
   353  func identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string {
   354  	numLabelUnits, ignoredUnits := p.NumLabelUnits()
   355  
   356  	// Print errors for tags with multiple units associated with
   357  	// a single key.
   358  	for k, units := range ignoredUnits {
   359  		ui.PrintErr(fmt.Sprintf("For tag %s used unit %s, also encountered unit(s) %s", k, numLabelUnits[k], strings.Join(units, ", ")))
   360  	}
   361  	return numLabelUnits
   362  }
   363  
   364  type sampleValueFunc func([]int64) int64
   365  
   366  // sampleFormat returns a function to extract values out of a profile.Sample,
   367  // and the type/units of those values.
   368  func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (value, meanDiv sampleValueFunc, v *profile.ValueType, err error) {
   369  	if len(p.SampleType) == 0 {
   370  		return nil, nil, nil, fmt.Errorf("profile has no samples")
   371  	}
   372  	index, err := p.SampleIndexByName(sampleIndex)
   373  	if err != nil {
   374  		return nil, nil, nil, err
   375  	}
   376  	value = valueExtractor(index)
   377  	if mean {
   378  		meanDiv = valueExtractor(0)
   379  	}
   380  	v = p.SampleType[index]
   381  	return
   382  }
   383  
   384  func valueExtractor(ix int) sampleValueFunc {
   385  	return func(v []int64) int64 {
   386  		return v[ix]
   387  	}
   388  }
   389  
   390  // profileCopier can be used to obtain a fresh copy of a profile.
   391  // It is useful since reporting code may mutate the profile handed to it.
   392  type profileCopier []byte
   393  
   394  func makeProfileCopier(src *profile.Profile) profileCopier {
   395  	// Pre-serialize the profile. We will deserialize every time a fresh copy is needed.
   396  	var buf bytes.Buffer
   397  	src.WriteUncompressed(&buf)
   398  	return profileCopier(buf.Bytes())
   399  }
   400  
   401  // newCopy returns a new copy of the profile.
   402  func (c profileCopier) newCopy() *profile.Profile {
   403  	p, err := profile.ParseUncompressed([]byte(c))
   404  	if err != nil {
   405  		panic(err)
   406  	}
   407  	return p
   408  }
   409  

View as plain text