...

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, pprofVariables, 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, vars variables, 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		vars = applyCommandOverrides(cmd[0], c.format, vars)
    75	
    76		// Delay focus after configuring report to get percentages on all samples.
    77		relative := vars["relative_percentages"].boolValue()
    78		if relative {
    79			if err := applyFocus(p, numLabelUnits, vars, o.UI); err != nil {
    80				return nil, nil, err
    81			}
    82		}
    83		ropt, err := reportOptions(p, numLabelUnits, vars)
    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, vars, o.UI); err != nil {
    99				return nil, nil, err
   100			}
   101		}
   102		if err := aggregate(p, vars); err != nil {
   103			return nil, nil, err
   104		}
   105	
   106		return c, rpt, nil
   107	}
   108	
   109	func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error {
   110		c, rpt, err := generateRawReport(p, cmd, vars, 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 := vars["output"].value
   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, v variables) variables {
   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 := v["trim"].boolValue()
   164	
   165		switch cmd {
   166		case "disasm", "weblist":
   167			trim = false
   168			v.set("addresses", "t")
   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			v.set("noinlines", "t")
   175		case "peek":
   176			trim = false
   177		case "list":
   178			trim = false
   179			v.set("lines", "t")
   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 v["nodecount"].intValue() == -1 {
   184				v.set("nodecount", "0")
   185			}
   186		default:
   187			if v["nodecount"].intValue() == -1 {
   188				v.set("nodecount", "80")
   189			}
   190		}
   191	
   192		switch outputFormat {
   193		case report.Proto, report.Raw, report.Callgrind:
   194			trim = false
   195			v.set("addresses", "t")
   196			v.set("noinlines", "f")
   197		}
   198	
   199		if !trim {
   200			v.set("nodecount", "0")
   201			v.set("nodefraction", "0")
   202			v.set("edgefraction", "0")
   203		}
   204		return v
   205	}
   206	
   207	func aggregate(prof *profile.Profile, v variables) error {
   208		var function, filename, linenumber, address bool
   209		inlines := !v["noinlines"].boolValue()
   210		switch {
   211		case v["addresses"].boolValue():
   212			if inlines {
   213				return nil
   214			}
   215			function = true
   216			filename = true
   217			linenumber = true
   218			address = true
   219		case v["lines"].boolValue():
   220			function = true
   221			filename = true
   222			linenumber = true
   223		case v["files"].boolValue():
   224			filename = true
   225		case v["functions"].boolValue():
   226			function = true
   227		case v["filefunctions"].boolValue():
   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, vars variables) (*report.Options, error) {
   237		si, mean := vars["sample_index"].value, vars["mean"].boolValue()
   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 vars["divide_by"].floatValue() == 0 {
   249			return nil, fmt.Errorf("zero divisor specified")
   250		}
   251	
   252		var filters []string
   253		for _, k := range []string{"focus", "ignore", "hide", "show", "show_from", "tagfocus", "tagignore", "tagshow", "taghide"} {
   254			v := vars[k].value
   255			if v != "" {
   256				filters = append(filters, k+"="+v)
   257			}
   258		}
   259	
   260		ropt := &report.Options{
   261			CumSort:      vars["cum"].boolValue(),
   262			CallTree:     vars["call_tree"].boolValue(),
   263			DropNegative: vars["drop_negative"].boolValue(),
   264	
   265			CompactLabels: vars["compact_labels"].boolValue(),
   266			Ratio:         1 / vars["divide_by"].floatValue(),
   267	
   268			NodeCount:    vars["nodecount"].intValue(),
   269			NodeFraction: vars["nodefraction"].floatValue(),
   270			EdgeFraction: vars["edgefraction"].floatValue(),
   271	
   272			ActiveFilters: filters,
   273			NumLabelUnits: numLabelUnits,
   274	
   275			SampleValue:       value,
   276			SampleMeanDivisor: meanDiv,
   277			SampleType:        stype,
   278			SampleUnit:        sample.Unit,
   279	
   280			OutputUnit: vars["unit"].value,
   281	
   282			SourcePath: vars["source_path"].stringValue(),
   283			TrimPath:   vars["trim_path"].stringValue(),
   284		}
   285	
   286		if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
   287			ropt.Title = filepath.Base(p.Mapping[0].File)
   288		}
   289	
   290		return ropt, nil
   291	}
   292	
   293	// identifyNumLabelUnits returns a map of numeric label keys to the units
   294	// associated with those keys.
   295	func identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string {
   296		numLabelUnits, ignoredUnits := p.NumLabelUnits()
   297	
   298		// Print errors for tags with multiple units associated with
   299		// a single key.
   300		for k, units := range ignoredUnits {
   301			ui.PrintErr(fmt.Sprintf("For tag %s used unit %s, also encountered unit(s) %s", k, numLabelUnits[k], strings.Join(units, ", ")))
   302		}
   303		return numLabelUnits
   304	}
   305	
   306	type sampleValueFunc func([]int64) int64
   307	
   308	// sampleFormat returns a function to extract values out of a profile.Sample,
   309	// and the type/units of those values.
   310	func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (value, meanDiv sampleValueFunc, v *profile.ValueType, err error) {
   311		if len(p.SampleType) == 0 {
   312			return nil, nil, nil, fmt.Errorf("profile has no samples")
   313		}
   314		index, err := p.SampleIndexByName(sampleIndex)
   315		if err != nil {
   316			return nil, nil, nil, err
   317		}
   318		value = valueExtractor(index)
   319		if mean {
   320			meanDiv = valueExtractor(0)
   321		}
   322		v = p.SampleType[index]
   323		return
   324	}
   325	
   326	func valueExtractor(ix int) sampleValueFunc {
   327		return func(v []int64) int64 {
   328			return v[ix]
   329		}
   330	}
   331	

View as plain text