diff options
Diffstat (limited to 'src/cmd/vendor/github.com/google/pprof')
22 files changed, 390 insertions, 327 deletions
diff --git a/src/cmd/vendor/github.com/google/pprof/driver/driver.go b/src/cmd/vendor/github.com/google/pprof/driver/driver.go index d5860036c3..6cbf66939d 100644 --- a/src/cmd/vendor/github.com/google/pprof/driver/driver.go +++ b/src/cmd/vendor/github.com/google/pprof/driver/driver.go @@ -202,7 +202,7 @@ type Sym struct { // A UI manages user interactions. type UI interface { - // Read returns a line of text (a command) read from the user. + // ReadLine returns a line of text (a command) read from the user. // prompt is printed before reading the command. ReadLine(prompt string) (string, error) diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go index efa9167af7..ed87b7e6f8 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go @@ -433,10 +433,8 @@ func (b *binrep) openELF(name string, start, limit, offset uint64, relocationSym defer ef.Close() buildID := "" - if f, err := os.Open(name); err == nil { - if id, err := elfexec.GetBuildID(f); err == nil { - buildID = fmt.Sprintf("%x", id) - } + if id, err := elfexec.GetBuildID(ef); err == nil { + buildID = fmt.Sprintf("%x", id) } var ( diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go index 74ce8cb422..18941926c5 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go @@ -20,6 +20,7 @@ package driver import ( "bytes" "fmt" + "io" "os" "path/filepath" "regexp" @@ -118,7 +119,14 @@ func generateReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Opti // Generate the report. dst := new(bytes.Buffer) - if err := report.Generate(dst, rpt, o.Obj); err != nil { + switch rpt.OutputFormat() { + case report.WebList: + // We need template expansion, so generate here instead of in report. + err = printWebList(dst, rpt, o.Obj) + default: + err = report.Generate(dst, rpt, o.Obj) + } + if err != nil { return err } src := dst @@ -155,6 +163,18 @@ func generateReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Opti return out.Close() } +func printWebList(dst io.Writer, rpt *report.Report, obj plugin.ObjTool) error { + listing, err := report.MakeWebList(rpt, obj, -1) + if err != nil { + return err + } + legend := report.ProfileLabels(rpt) + return renderHTML(dst, "sourcelisting", rpt, nil, legend, webArgs{ + Standalone: true, + Listing: listing, + }) +} + func applyCommandOverrides(cmd string, outputFormat int, cfg config) config { // Some report types override the trim flag to false below. This is to make // sure the default heuristics of excluding insignificant nodes and edges diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch.go index 95204a394f..a94ddf6adb 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch.go @@ -493,7 +493,7 @@ func fetch(source string, duration, timeout time.Duration, ui plugin.UI, tr http var f io.ReadCloser // First determine whether the source is a file, if not, it will be treated as a URL. - if _, openErr := os.Stat(source); openErr == nil { + if _, err = os.Stat(source); err == nil { if isPerfFile(source) { f, err = convertPerfData(source, ui) } else { diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/graph.css b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/graph.css new file mode 100644 index 0000000000..c756ddfdcb --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/graph.css @@ -0,0 +1,7 @@ +#graph { + cursor: grab; +} + +#graph:active { + cursor: grabbing; +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/graph.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/graph.html index a113549fc4..d17a0ea7d0 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/graph.html +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/graph.html @@ -4,6 +4,7 @@ <meta charset="utf-8"> <title>{{.Title}}</title> {{template "css" .}} + {{template "graph_css" .}} </head> <body> {{template "header" .}} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/source.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/source.html index 3212bee4a0..b676ce2054 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/source.html +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/source.html @@ -3,16 +3,70 @@ <head> <meta charset="utf-8"> <title>{{.Title}}</title> - {{template "css" .}} + {{if not .Standalone}}{{template "css" .}}{{end}} {{template "weblistcss" .}} {{template "weblistjs" .}} </head> -<body> - {{template "header" .}} - <div id="content" class="source"> - {{.HTMLBody}} - </div> - {{template "script" .}} - <script>viewer(new URL(window.location.href), null);</script> +<body>{{"\n" -}} + {{/* emit different header in standalone mode */ -}} + {{if .Standalone}}{{"\n" -}} + <div class="legend">{{"" -}} + {{range $i, $e := .Legend -}} + {{if $i}}<br>{{"\n"}}{{end}}{{. -}} + {{end}}<br>Total: {{.Listing.Total -}} + </div>{{"" -}} + {{else -}} + {{template "header" .}} + <div id="content" class="source">{{"" -}} + {{end -}} + + {{range .Listing.Files -}} + {{range .Funcs -}} + <h2>{{.Name}}</h2>{{"" -}} + <p class="filename">{{.File}}</p>{{"\n" -}} + <pre onClick="pprof_toggle_asm(event)">{{"\n" -}} + {{printf " Total: %10s %10s (flat, cum) %s" .Flat .Cumulative .Percent -}} + {{range .Lines -}}{{"\n" -}} + {{/* source line */ -}} + <span class=line>{{printf " %6d" .Line}}</span>{{" " -}} + <span class={{.HTMLClass}}> + {{- printf " %10s %10s %8s %s " .Flat .Cumulative "" .SrcLine -}} + </span>{{"" -}} + + {{if .Instructions -}} + {{/* instructions for this source line */ -}} + <span class=asm>{{"" -}} + {{range .Instructions -}} + {{/* separate when we hit a new basic block */ -}} + {{if .NewBlock -}}{{printf " %8s %28s\n" "" "⋮"}}{{end -}} + + {{/* inlined calls leading to this instruction */ -}} + {{range .InlinedCalls -}} + {{printf " %8s %10s %10s %8s " "" "" "" "" -}} + <span class=inlinesrc>{{.SrcLine}}</span>{{" " -}} + <span class=unimportant>{{.FileBase}}:{{.Line}}</span>{{"\n" -}} + {{end -}} + + {{if not .Synthetic -}} + {{/* disassembled instruction */ -}} + {{printf " %8s %10s %10s %8x: %s " "" .Flat .Cumulative .Address .Disasm -}} + <span class=unimportant>{{.FileLine}}</span>{{"\n" -}} + {{end -}} + {{end -}} + </span>{{"" -}} + {{end -}} + {{/* end of line */ -}} + {{end}}{{"\n" -}} + </pre>{{"\n" -}} + {{/* end of function */ -}} + {{end -}} + {{/* end of file */ -}} + {{end -}} + + {{if not .Standalone}}{{"\n " -}} + </div>{{"\n" -}} + {{template "script" .}}{{"\n" -}} + <script>viewer(new URL(window.location.href), null);</script>{{"" -}} + {{end}} </body> </html> diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/stacks.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/stacks.html index 1ddb7a3a1c..c2f8cf26b1 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/stacks.html +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/stacks.html @@ -26,6 +26,7 @@ {{template "script" .}} {{template "stacks_js"}} <script> + pprofUnitDefs = {{.UnitDefs}}; stackViewer({{.Stacks}}, {{.Nodes}}); </script> </body> diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/stacks.js b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/stacks.js index c8059fe6bf..df0f0649b9 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/stacks.js +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/stacks.js @@ -13,23 +13,6 @@ function stackViewer(stacks, nodes) { const FONT_SIZE = 12; const MIN_FONT_SIZE = 8; - // Mapping from unit to a list of display scales/labels. - // List should be ordered by increasing unit size. - const UNITS = new Map([ - ['B', [ - ['B', 1], - ['kB', Math.pow(2, 10)], - ['MB', Math.pow(2, 20)], - ['GB', Math.pow(2, 30)], - ['TB', Math.pow(2, 40)], - ['PB', Math.pow(2, 50)]]], - ['s', [ - ['ns', 1e-9], - ['µs', 1e-6], - ['ms', 1e-3], - ['s', 1], - ['hrs', 60*60]]]]); - // Fields let pivots = []; // Indices of currently selected data.Sources entries. let matches = new Set(); // Indices of sources that match search @@ -570,22 +553,7 @@ function stackViewer(stacks, nodes) { // unitText returns a formatted string to display for value. function unitText(value) { - const sign = (value < 0) ? "-" : ""; - let v = Math.abs(value) * stacks.Scale; - // Rescale to appropriate display unit. - let unit = stacks.Unit; - const list = UNITS.get(unit); - if (list) { - // Find first entry in list that is not too small. - for (const [name, scale] of list) { - if (v <= 100*scale) { - v /= scale; - unit = name; - break; - } - } - } - return sign + Number(v.toFixed(2)) + unit; + return pprofUnitText(value*stacks.Scale, stacks.Unit); } function find(name) { @@ -606,3 +574,29 @@ function stackViewer(stacks, nodes) { return hsl; } } + +// pprofUnitText returns a formatted string to display for value in the specified unit. +function pprofUnitText(value, unit) { + const sign = (value < 0) ? "-" : ""; + let v = Math.abs(value); + // Rescale to appropriate display unit. + let list = null; + for (const def of pprofUnitDefs) { + if (def.DefaultUnit.CanonicalName == unit) { + list = def.Units; + v *= def.DefaultUnit.Factor; + break; + } + } + if (list) { + // Stop just before entry that is too large. + for (let i = 0; i < list.length; i++) { + if (i == list.length-1 || list[i+1].Factor > v) { + v /= list[i].Factor; + unit = list[i].CanonicalName; + break; + } + } + } + return sign + Number(v.toFixed(2)) + unit; +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/settings.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/settings.go index b784618aca..5011a06666 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/settings.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/settings.go @@ -102,7 +102,7 @@ func configMenu(fname string, u url.URL) []configMenuEntry { UserConfig: (i != 0), } } - // Mark the last matching config as currennt + // Mark the last matching config as current if lastMatch >= 0 { result[lastMatch].Current = true } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/stacks.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/stacks.go index 6a61613344..355b8f2e2a 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/stacks.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/stacks.go @@ -19,6 +19,7 @@ import ( "html/template" "net/http" + "github.com/google/pprof/internal/measurement" "github.com/google/pprof/internal/report" ) @@ -52,7 +53,8 @@ func (ui *webInterface) stackView(w http.ResponseWriter, req *http.Request) { _, legend := report.TextItems(rpt) ui.render(w, req, "stacks", rpt, errList, legend, webArgs{ - Stacks: template.JS(b), - Nodes: nodes, + Stacks: template.JS(b), + Nodes: nodes, + UnitDefs: measurement.UnitTypes, }) } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go index 984936a9d6..0b8630bcf1 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go @@ -19,8 +19,27 @@ import ( "fmt" "html/template" "os" + "sync" + + "github.com/google/pprof/internal/report" +) + +var ( + htmlTemplates *template.Template // Lazily loaded templates + htmlTemplateInit sync.Once ) +// getHTMLTemplates returns the set of HTML templates used by pprof, +// initializing them if necessary. +func getHTMLTemplates() *template.Template { + htmlTemplateInit.Do(func() { + htmlTemplates = template.New("templategroup") + addTemplates(htmlTemplates) + report.AddSourceTemplates(htmlTemplates) + }) + return htmlTemplates +} + //go:embed html var embeddedFiles embed.FS @@ -54,6 +73,7 @@ func addTemplates(templates *template.Template) { def("css", loadCSS("html/common.css")) def("header", loadFile("html/header.html")) def("graph", loadFile("html/graph.html")) + def("graph_css", loadCSS("html/graph.css")) def("script", loadJS("html/common.js")) def("top", loadFile("html/top.html")) def("sourcelisting", loadFile("html/source.html")) diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go index 476e1d2cdf..2a2d7fb1d2 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go @@ -18,6 +18,7 @@ import ( "bytes" "fmt" "html/template" + "io" "net" "net/http" gourl "net/url" @@ -28,6 +29,7 @@ import ( "time" "github.com/google/pprof/internal/graph" + "github.com/google/pprof/internal/measurement" "github.com/google/pprof/internal/plugin" "github.com/google/pprof/internal/report" "github.com/google/pprof/profile" @@ -39,7 +41,6 @@ type webInterface struct { copier profileCopier options *plugin.Options help map[string]string - templates *template.Template settingsFile string } @@ -48,15 +49,11 @@ func makeWebInterface(p *profile.Profile, copier profileCopier, opt *plugin.Opti if err != nil { return nil, err } - templates := template.New("templategroup") - addTemplates(templates) - report.AddSourceTemplates(templates) return &webInterface{ prof: p, copier: copier, options: opt, help: make(map[string]string), - templates: templates, settingsFile: settingsFile, }, nil } @@ -82,14 +79,17 @@ type webArgs struct { Total int64 SampleTypes []string Legend []string + Standalone bool // True for command-line generation of HTML Help map[string]string Nodes []string HTMLBody template.HTML TextBody string Top []report.TextItem + Listing report.WebListData FlameGraph template.JS Stacks template.JS Configs []configMenuEntry + UnitDefs []measurement.UnitType } func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, disableBrowser bool) error { @@ -283,21 +283,25 @@ func (ui *webInterface) makeReport(w http.ResponseWriter, req *http.Request, return rpt, catcher.errors } -// render generates html using the named template based on the contents of data. -func (ui *webInterface) render(w http.ResponseWriter, req *http.Request, tmpl string, - rpt *report.Report, errList, legend []string, data webArgs) { +// renderHTML generates html using the named template based on the contents of data. +func renderHTML(dst io.Writer, tmpl string, rpt *report.Report, errList, legend []string, data webArgs) error { file := getFromLegend(legend, "File: ", "unknown") profile := getFromLegend(legend, "Type: ", "unknown") data.Title = file + " " + profile data.Errors = errList data.Total = rpt.Total() - data.SampleTypes = sampleTypes(ui.prof) data.Legend = legend + return getHTMLTemplates().ExecuteTemplate(dst, tmpl, data) +} + +// render responds with html generated by passing data to the named template. +func (ui *webInterface) render(w http.ResponseWriter, req *http.Request, tmpl string, + rpt *report.Report, errList, legend []string, data webArgs) { + data.SampleTypes = sampleTypes(ui.prof) data.Help = ui.help data.Configs = configMenu(ui.settingsFile, *req.URL) - html := &bytes.Buffer{} - if err := ui.templates.ExecuteTemplate(html, tmpl, data); err != nil { + if err := renderHTML(html, tmpl, rpt, errList, legend, data); err != nil { http.Error(w, "internal template error", http.StatusInternalServerError) ui.options.UI.PrintErr(err) return @@ -410,8 +414,8 @@ func (ui *webInterface) source(w http.ResponseWriter, req *http.Request) { } // Generate source listing. - var body bytes.Buffer - if err := report.PrintWebList(&body, rpt, ui.options.Obj, maxEntries); err != nil { + listing, err := report.MakeWebList(rpt, ui.options.Obj, maxEntries) + if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) ui.options.UI.PrintErr(err) return @@ -419,7 +423,7 @@ func (ui *webInterface) source(w http.ResponseWriter, req *http.Request) { legend := report.ProfileLabels(rpt) ui.render(w, req, "sourcelisting", rpt, errList, legend, webArgs{ - HTMLBody: template.HTML(body.String()), + Listing: listing, }) } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go b/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go index 718481b078..10436a2256 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go @@ -118,12 +118,7 @@ func parseNotes(reader io.Reader, alignment int, order binary.ByteOrder) ([]elfN // // If no build-ID was found but the binary was read without error, it returns // (nil, nil). -func GetBuildID(binary io.ReaderAt) ([]byte, error) { - f, err := elf.NewFile(binary) - if err != nil { - return nil, err - } - +func GetBuildID(f *elf.File) ([]byte, error) { findBuildID := func(notes []elfNote) ([]byte, error) { var buildID []byte for _, note := range notes { diff --git a/src/cmd/vendor/github.com/google/pprof/internal/graph/graph.go b/src/cmd/vendor/github.com/google/pprof/internal/graph/graph.go index 5ad10a2ae0..8abbd83f76 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/graph/graph.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/graph/graph.go @@ -444,7 +444,7 @@ func newTree(prof *profile.Profile, o *Options) (g *Graph) { } } - nodes := make(Nodes, len(prof.Location)) + nodes := make(Nodes, 0, len(prof.Location)) for _, nm := range parentNodeMap { nodes = append(nodes, nm.nodes()...) } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go b/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go index d9644f9326..e5b7dbc6c4 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go @@ -113,7 +113,7 @@ func compatibleValueTypes(v1, v2 *profile.ValueType) bool { if v1.Unit == v2.Unit { return true } - for _, ut := range unitTypes { + for _, ut := range UnitTypes { if ut.sniffUnit(v1.Unit) != nil && ut.sniffUnit(v2.Unit) != nil { return true } @@ -130,7 +130,7 @@ func Scale(value int64, fromUnit, toUnit string) (float64, string) { v, u := Scale(-value, fromUnit, toUnit) return -v, u } - for _, ut := range unitTypes { + for _, ut := range UnitTypes { if v, u, ok := ut.convertUnit(value, fromUnit, toUnit); ok { return v, u } @@ -177,26 +177,26 @@ func Percentage(value, total int64) string { } } -// unit includes a list of aliases representing a specific unit and a factor +// Unit includes a list of aliases representing a specific unit and a factor // which one can multiple a value in the specified unit by to get the value // in terms of the base unit. -type unit struct { - canonicalName string +type Unit struct { + CanonicalName string aliases []string - factor float64 + Factor float64 } -// unitType includes a list of units that are within the same category (i.e. +// UnitType includes a list of units that are within the same category (i.e. // memory or time units) and a default unit to use for this type of unit. -type unitType struct { - defaultUnit unit - units []unit +type UnitType struct { + DefaultUnit Unit + Units []Unit } // findByAlias returns the unit associated with the specified alias. It returns // nil if the unit with such alias is not found. -func (ut unitType) findByAlias(alias string) *unit { - for _, u := range ut.units { +func (ut UnitType) findByAlias(alias string) *Unit { + for _, u := range ut.Units { for _, a := range u.aliases { if alias == a { return &u @@ -208,7 +208,7 @@ func (ut unitType) findByAlias(alias string) *unit { // sniffUnit simpifies the input alias and returns the unit associated with the // specified alias. It returns nil if the unit with such alias is not found. -func (ut unitType) sniffUnit(unit string) *unit { +func (ut UnitType) sniffUnit(unit string) *Unit { unit = strings.ToLower(unit) if len(unit) > 2 { unit = strings.TrimSuffix(unit, "s") @@ -219,13 +219,13 @@ func (ut unitType) sniffUnit(unit string) *unit { // autoScale takes in the value with units of the base unit and returns // that value scaled to a reasonable unit if a reasonable unit is // found. -func (ut unitType) autoScale(value float64) (float64, string, bool) { +func (ut UnitType) autoScale(value float64) (float64, string, bool) { var f float64 var unit string - for _, u := range ut.units { - if u.factor >= f && (value/u.factor) >= 1.0 { - f = u.factor - unit = u.canonicalName + for _, u := range ut.Units { + if u.Factor >= f && (value/u.Factor) >= 1.0 { + f = u.Factor + unit = u.CanonicalName } } if f == 0 { @@ -239,27 +239,28 @@ func (ut unitType) autoScale(value float64) (float64, string, bool) { // included in the unitType, then a false boolean will be returned. If the // toUnit is not in the unitType, the value will be returned in terms of the // default unitType. -func (ut unitType) convertUnit(value int64, fromUnitStr, toUnitStr string) (float64, string, bool) { +func (ut UnitType) convertUnit(value int64, fromUnitStr, toUnitStr string) (float64, string, bool) { fromUnit := ut.sniffUnit(fromUnitStr) if fromUnit == nil { return 0, "", false } - v := float64(value) * fromUnit.factor + v := float64(value) * fromUnit.Factor if toUnitStr == "minimum" || toUnitStr == "auto" { if v, u, ok := ut.autoScale(v); ok { return v, u, true } - return v / ut.defaultUnit.factor, ut.defaultUnit.canonicalName, true + return v / ut.DefaultUnit.Factor, ut.DefaultUnit.CanonicalName, true } toUnit := ut.sniffUnit(toUnitStr) if toUnit == nil { - return v / ut.defaultUnit.factor, ut.defaultUnit.canonicalName, true + return v / ut.DefaultUnit.Factor, ut.DefaultUnit.CanonicalName, true } - return v / toUnit.factor, toUnit.canonicalName, true + return v / toUnit.Factor, toUnit.CanonicalName, true } -var unitTypes = []unitType{{ - units: []unit{ +// UnitTypes holds the definition of units known to pprof. +var UnitTypes = []UnitType{{ + Units: []Unit{ {"B", []string{"b", "byte"}, 1}, {"kB", []string{"kb", "kbyte", "kilobyte"}, float64(1 << 10)}, {"MB", []string{"mb", "mbyte", "megabyte"}, float64(1 << 20)}, @@ -267,18 +268,18 @@ var unitTypes = []unitType{{ {"TB", []string{"tb", "tbyte", "terabyte"}, float64(1 << 40)}, {"PB", []string{"pb", "pbyte", "petabyte"}, float64(1 << 50)}, }, - defaultUnit: unit{"B", []string{"b", "byte"}, 1}, + DefaultUnit: Unit{"B", []string{"b", "byte"}, 1}, }, { - units: []unit{ + Units: []Unit{ {"ns", []string{"ns", "nanosecond"}, float64(time.Nanosecond)}, {"us", []string{"μs", "us", "microsecond"}, float64(time.Microsecond)}, {"ms", []string{"ms", "millisecond"}, float64(time.Millisecond)}, {"s", []string{"s", "sec", "second"}, float64(time.Second)}, {"hrs", []string{"hour", "hr"}, float64(time.Hour)}, }, - defaultUnit: unit{"s", []string{}, float64(time.Second)}, + DefaultUnit: Unit{"s", []string{}, float64(time.Second)}, }, { - units: []unit{ + Units: []Unit{ {"n*GCU", []string{"nanogcu"}, 1e-9}, {"u*GCU", []string{"microgcu"}, 1e-6}, {"m*GCU", []string{"milligcu"}, 1e-3}, @@ -289,5 +290,5 @@ var unitTypes = []unitType{{ {"T*GCU", []string{"teragcu"}, 1e12}, {"P*GCU", []string{"petagcu"}, 1e15}, }, - defaultUnit: unit{"GCU", []string{}, 1.0}, + DefaultUnit: Unit{"GCU", []string{}, 1.0}, }} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/plugin/plugin.go b/src/cmd/vendor/github.com/google/pprof/internal/plugin/plugin.go index c934551036..f2ef987185 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/plugin/plugin.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/plugin/plugin.go @@ -175,7 +175,7 @@ type Sym struct { // A UI manages user interactions. type UI interface { - // Read returns a line of text (a command) read from the user. + // ReadLine returns a line of text (a command) read from the user. // prompt is printed before reading the command. ReadLine(prompt string) (string, error) diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/report.go b/src/cmd/vendor/github.com/google/pprof/internal/report/report.go index 96b80039e6..d72ebe914f 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/report/report.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/report/report.go @@ -111,12 +111,11 @@ func Generate(w io.Writer, rpt *Report, obj plugin.ObjTool) error { return printAssembly(w, rpt, obj) case List: return printSource(w, rpt) - case WebList: - return printWebSource(w, rpt, obj) case Callgrind: return printCallgrind(w, rpt) } - return fmt.Errorf("unexpected output format") + // Note: WebList handling is in driver package. + return fmt.Errorf("unexpected output format %v", o.OutputFormat) } // newTrimmedGraph creates a graph for this report, trimmed according @@ -1327,6 +1326,9 @@ type Report struct { // Total returns the total number of samples in a report. func (rpt *Report) Total() int64 { return rpt.total } +// OutputFormat returns the output format for the report. +func (rpt *Report) OutputFormat() int { return rpt.options.OutputFormat } + func abs64(i int64) int64 { if i < 0 { return -i diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/source.go b/src/cmd/vendor/github.com/google/pprof/internal/report/source.go index d8b4395265..d2148607ea 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/report/source.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/report/source.go @@ -122,17 +122,6 @@ func printSource(w io.Writer, rpt *Report) error { return nil } -// printWebSource prints an annotated source listing, include all -// functions with samples that match the regexp rpt.options.symbol. -func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error { - printHeader(w, rpt) - if err := PrintWebList(w, rpt, obj, -1); err != nil { - return err - } - printPageClosing(w) - return nil -} - // sourcePrinter holds state needed for generating source+asm HTML listing. type sourcePrinter struct { reader *sourceReader @@ -198,24 +187,73 @@ type addressRange struct { score int64 // Used to order ranges for processing } -// PrintWebList prints annotated source listing of rpt to w. +// WebListData holds the data needed to generate HTML source code listing. +type WebListData struct { + Total string + Files []WebListFile +} + +// WebListFile holds the per-file information for HTML source code listing. +type WebListFile struct { + Funcs []WebListFunc +} + +// WebListFunc holds the per-function information for HTML source code listing. +type WebListFunc struct { + Name string + File string + Flat string + Cumulative string + Percent string + Lines []WebListLine +} + +// WebListLine holds the per-source-line information for HTML source code listing. +type WebListLine struct { + SrcLine string + HTMLClass string + Line int + Flat string + Cumulative string + Instructions []WebListInstruction +} + +// WebListInstruction holds the per-instruction information for HTML source code listing. +type WebListInstruction struct { + NewBlock bool // Insert marker that indicates separation from previous block + Flat string + Cumulative string + Synthetic bool + Address uint64 + Disasm string + FileLine string + InlinedCalls []WebListCall +} + +// WebListCall holds the per-inlined-call information for HTML source code listing. +type WebListCall struct { + SrcLine string + FileBase string + Line int +} + +// MakeWebList returns an annotated source listing of rpt. // rpt.prof should contain inlined call info. -func PrintWebList(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFiles int) error { +func MakeWebList(rpt *Report, obj plugin.ObjTool, maxFiles int) (WebListData, error) { sourcePath := rpt.options.SourcePath if sourcePath == "" { wd, err := os.Getwd() if err != nil { - return fmt.Errorf("could not stat current dir: %v", err) + return WebListData{}, fmt.Errorf("could not stat current dir: %v", err) } sourcePath = wd } sp := newSourcePrinter(rpt, obj, sourcePath) if len(sp.interest) == 0 { - return fmt.Errorf("no matches found for regexp: %s", rpt.options.Symbol) + return WebListData{}, fmt.Errorf("no matches found for regexp: %s", rpt.options.Symbol) } - sp.print(w, maxFiles, rpt) - sp.close() - return nil + defer sp.close() + return sp.generate(maxFiles, rpt), nil } func newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourcePrinter { @@ -566,7 +604,7 @@ func (sp *sourcePrinter) initSamples(flat, cum map[uint64]int64) { } } -func (sp *sourcePrinter) print(w io.Writer, maxFiles int, rpt *Report) { +func (sp *sourcePrinter) generate(maxFiles int, rpt *Report) WebListData { // Finalize per-file counts. for _, file := range sp.files { seen := map[uint64]bool{} @@ -598,19 +636,31 @@ func (sp *sourcePrinter) print(w io.Writer, maxFiles int, rpt *Report) { maxFiles = len(files) } sort.Slice(files, order) + result := WebListData{ + Total: rpt.formatValue(rpt.total), + } for i, f := range files { if i < maxFiles { - sp.printFile(w, f, rpt) + result.Files = append(result.Files, sp.generateFile(f, rpt)) } } + return result } -func (sp *sourcePrinter) printFile(w io.Writer, f *sourceFile, rpt *Report) { +func (sp *sourcePrinter) generateFile(f *sourceFile, rpt *Report) WebListFile { + var result WebListFile for _, fn := range sp.functions(f) { if fn.cum == 0 { continue } - printFunctionHeader(w, fn.name, f.fname, fn.flat, fn.cum, rpt) + + listfn := WebListFunc{ + Name: fn.name, + File: f.fname, + Flat: rpt.formatValue(fn.flat), + Cumulative: rpt.formatValue(fn.cum), + Percent: measurement.Percentage(fn.cum, rpt.total), + } var asm []assemblyInstruction for l := fn.begin; l < fn.end; l++ { lineContents, ok := sp.reader.line(f.fname, l) @@ -654,10 +704,12 @@ func (sp *sourcePrinter) printFile(w io.Writer, f *sourceFile, rpt *Report) { }) } - printFunctionSourceLine(w, l, flatSum, cumSum, lineContents, asm, sp.reader, rpt) + listfn.Lines = append(listfn.Lines, makeWebListLine(l, flatSum, cumSum, lineContents, asm, sp.reader, rpt)) } - printFunctionClosing(w) + + result.Funcs = append(result.Funcs, listfn) } + return result } // functions splits apart the lines to show in a file into a list of per-function ranges. @@ -752,89 +804,58 @@ func (sp *sourcePrinter) objectFile(m *profile.Mapping) plugin.ObjFile { return object } -// printHeader prints the page header for a weblist report. -func printHeader(w io.Writer, rpt *Report) { - fmt.Fprintln(w, ` -<!DOCTYPE html> -<html> -<head> -<meta charset="UTF-8"> -<title>Pprof listing</title>`) - fmt.Fprintln(w, weblistPageCSS) - fmt.Fprintln(w, weblistPageScript) - fmt.Fprint(w, "</head>\n<body>\n\n") - - var labels []string - for _, l := range ProfileLabels(rpt) { - labels = append(labels, template.HTMLEscapeString(l)) +// makeWebListLine returns the contents of a single line in a web listing. This includes +// the source line and the corresponding assembly. +func makeWebListLine(lineNo int, flat, cum int64, lineContents string, + assembly []assemblyInstruction, reader *sourceReader, rpt *Report) WebListLine { + line := WebListLine{ + SrcLine: lineContents, + Line: lineNo, + Flat: valueOrDot(flat, rpt), + Cumulative: valueOrDot(cum, rpt), } - fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`, - strings.Join(labels, "<br>\n"), - rpt.formatValue(rpt.total), - ) -} - -// printFunctionHeader prints a function header for a weblist report. -func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) { - fmt.Fprintf(w, `<h2>%s</h2><p class="filename">%s</p> -<pre onClick="pprof_toggle_asm(event)"> - Total: %10s %10s (flat, cum) %s -`, - template.HTMLEscapeString(name), template.HTMLEscapeString(path), - rpt.formatValue(flatSum), rpt.formatValue(cumSum), - measurement.Percentage(cumSum, rpt.total)) -} - -// printFunctionSourceLine prints a source line and the corresponding assembly. -func printFunctionSourceLine(w io.Writer, lineNo int, flat, cum int64, lineContents string, - assembly []assemblyInstruction, reader *sourceReader, rpt *Report) { if len(assembly) == 0 { - fmt.Fprintf(w, - "<span class=line> %6d</span> <span class=nop> %10s %10s %8s %s </span>\n", - lineNo, - valueOrDot(flat, rpt), valueOrDot(cum, rpt), - "", template.HTMLEscapeString(lineContents)) - return + line.HTMLClass = "nop" + return line } nestedInfo := false - cl := "deadsrc" + line.HTMLClass = "deadsrc" for _, an := range assembly { if len(an.inlineCalls) > 0 || an.instruction != synthAsm { nestedInfo = true - cl = "livesrc" + line.HTMLClass = "livesrc" } } - fmt.Fprintf(w, - "<span class=line> %6d</span> <span class=%s> %10s %10s %8s %s </span>", - lineNo, cl, - valueOrDot(flat, rpt), valueOrDot(cum, rpt), - "", template.HTMLEscapeString(lineContents)) if nestedInfo { srcIndent := indentation(lineContents) - printNested(w, srcIndent, assembly, reader, rpt) + line.Instructions = makeWebListInstructions(srcIndent, assembly, reader, rpt) } - fmt.Fprintln(w) + return line } -func printNested(w io.Writer, srcIndent int, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) { - fmt.Fprint(w, "<span class=asm>") +func makeWebListInstructions(srcIndent int, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) []WebListInstruction { + var result []WebListInstruction var curCalls []callID for i, an := range assembly { - if an.startsBlock && i != 0 { - // Insert a separator between discontiguous blocks. - fmt.Fprintf(w, " %8s %28s\n", "", "⋮") - } - var fileline string if an.file != "" { fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(filepath.Base(an.file)), an.line) } - flat, cum := an.flat, an.cum + text := strings.Repeat(" ", srcIndent+4+4*len(an.inlineCalls)) + an.instruction + inst := WebListInstruction{ + NewBlock: (an.startsBlock && i != 0), + Flat: valueOrDot(an.flat, rpt), + Cumulative: valueOrDot(an.cum, rpt), + Synthetic: (an.instruction == synthAsm), + Address: an.address, + Disasm: rightPad(text, 80), + FileLine: fileline, + } - // Print inlined call context. + // Add inlined call context. for j, c := range an.inlineCalls { if j < len(curCalls) && curCalls[j] == c { // Skip if same as previous instruction. @@ -845,36 +866,18 @@ func printNested(w io.Writer, srcIndent int, assembly []assemblyInstruction, rea if !ok { fline = "" } - text := strings.Repeat(" ", srcIndent+4+4*j) + strings.TrimSpace(fline) - fmt.Fprintf(w, " %8s %10s %10s %8s <span class=inlinesrc>%s</span> <span class=unimportant>%s:%d</span>\n", - "", "", "", "", - template.HTMLEscapeString(rightPad(text, 80)), - template.HTMLEscapeString(filepath.Base(c.file)), c.line) + srcCode := strings.Repeat(" ", srcIndent+4+4*j) + strings.TrimSpace(fline) + inst.InlinedCalls = append(inst.InlinedCalls, WebListCall{ + SrcLine: rightPad(srcCode, 80), + FileBase: filepath.Base(c.file), + Line: c.line, + }) } curCalls = an.inlineCalls - if an.instruction == synthAsm { - continue - } - text := strings.Repeat(" ", srcIndent+4+4*len(curCalls)) + an.instruction - fmt.Fprintf(w, " %8s %10s %10s %8x: %s <span class=unimportant>%s</span>\n", - "", valueOrDot(flat, rpt), valueOrDot(cum, rpt), an.address, - template.HTMLEscapeString(rightPad(text, 80)), - // fileline should not be escaped since it was formed by appending - // line number (just digits) to an escaped file name. Escaping here - // would cause double-escaping of file name. - fileline) - } - fmt.Fprint(w, "</span>") -} - -// printFunctionClosing prints the end of a function in a weblist report. -func printFunctionClosing(w io.Writer) { - fmt.Fprintln(w, "</pre>") -} -// printPageClosing prints the end of the page in a weblist report. -func printPageClosing(w io.Writer) { - fmt.Fprintln(w, weblistPageClosing) + result = append(result, inst) + } + return result } // getSourceFromFile collects the sources of a function from a source diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/source_html.go b/src/cmd/vendor/github.com/google/pprof/internal/report/source_html.go index 851693f1d0..614a5ee293 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/report/source_html.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/report/source_html.go @@ -69,7 +69,3 @@ function pprof_toggle_asm(e) { } } </script>` - -const weblistPageClosing = ` -</body> -</html>` diff --git a/src/cmd/vendor/github.com/google/pprof/internal/symbolizer/symbolizer.go b/src/cmd/vendor/github.com/google/pprof/internal/symbolizer/symbolizer.go index 5ca71ab8be..70b4047269 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/symbolizer/symbolizer.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/symbolizer/symbolizer.go @@ -133,22 +133,80 @@ func doLocalSymbolize(prof *profile.Profile, fast, force bool, obj plugin.ObjToo } } - mt, err := newMapping(prof, obj, ui, force) - if err != nil { - return err + functions := map[profile.Function]*profile.Function{} + addFunction := func(f *profile.Function) *profile.Function { + if fp := functions[*f]; fp != nil { + return fp + } + functions[*f] = f + f.ID = uint64(len(prof.Function)) + 1 + prof.Function = append(prof.Function, f) + return f + } + + missingBinaries := false + mappingLocs := map[*profile.Mapping][]*profile.Location{} + for _, l := range prof.Location { + mappingLocs[l.Mapping] = append(mappingLocs[l.Mapping], l) } - defer mt.close() + for midx, m := range prof.Mapping { + locs := mappingLocs[m] + if len(locs) == 0 { + // The mapping is dangling and has no locations pointing to it. + continue + } + // Do not attempt to re-symbolize a mapping that has already been symbolized. + if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) { + continue + } + if m.File == "" { + if midx == 0 { + ui.PrintErr("Main binary filename not available.") + continue + } + missingBinaries = true + continue + } + if m.Unsymbolizable() { + // Skip well-known system mappings + continue + } + if m.BuildID == "" { + if u, err := url.Parse(m.File); err == nil && u.IsAbs() && strings.Contains(strings.ToLower(u.Scheme), "http") { + // Skip mappings pointing to a source URL + continue + } + } - functions := make(map[profile.Function]*profile.Function) - for _, l := range mt.prof.Location { - m := l.Mapping - segment := mt.segments[m] - if segment == nil { - // Nothing to do. + name := filepath.Base(m.File) + if m.BuildID != "" { + name += fmt.Sprintf(" (build ID %s)", m.BuildID) + } + f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol) + if err != nil { + ui.PrintErr("Local symbolization failed for ", name, ": ", err) + missingBinaries = true continue } + if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID { + ui.PrintErr("Local symbolization failed for ", name, ": build ID mismatch") + f.Close() + continue + } + symbolizeOneMapping(m, locs, f, addFunction) + f.Close() + } - stack, err := segment.SourceLine(l.Address) + if missingBinaries { + ui.PrintErr("Some binary filenames not available. Symbolization may be incomplete.\n" + + "Try setting PPROF_BINARY_PATH to the search path for local binaries.") + } + return nil +} + +func symbolizeOneMapping(m *profile.Mapping, locs []*profile.Location, obj plugin.ObjFile, addFunction func(*profile.Function) *profile.Function) { + for _, l := range locs { + stack, err := obj.SourceLine(l.Address) if err != nil || len(stack) == 0 { // No answers from addr2line. continue @@ -166,18 +224,11 @@ func doLocalSymbolize(prof *profile.Profile, fast, force bool, obj plugin.ObjToo if frame.Line != 0 { m.HasLineNumbers = true } - f := &profile.Function{ + f := addFunction(&profile.Function{ Name: frame.Func, SystemName: frame.Func, Filename: frame.File, - } - if fp := functions[*f]; fp != nil { - f = fp - } else { - functions[*f] = f - f.ID = uint64(len(mt.prof.Function)) + 1 - mt.prof.Function = append(mt.prof.Function, f) - } + }) l.Line[i] = profile.Line{ Function: f, Line: int64(frame.Line), @@ -189,8 +240,6 @@ func doLocalSymbolize(prof *profile.Profile, fast, force bool, obj plugin.ObjToo m.HasInlineFrames = true } } - - return nil } // Demangle updates the function names in a profile with demangled C++ @@ -294,87 +343,3 @@ func removeMatching(name string, start, end byte) string { } return name } - -// newMapping creates a mappingTable for a profile. -func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) { - mt := &mappingTable{ - prof: prof, - segments: make(map[*profile.Mapping]plugin.ObjFile), - } - - // Identify used mappings - mappings := make(map[*profile.Mapping]bool) - for _, l := range prof.Location { - mappings[l.Mapping] = true - } - - missingBinaries := false - for midx, m := range prof.Mapping { - if !mappings[m] { - continue - } - - // Do not attempt to re-symbolize a mapping that has already been symbolized. - if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) { - continue - } - - if m.File == "" { - if midx == 0 { - ui.PrintErr("Main binary filename not available.") - continue - } - missingBinaries = true - continue - } - - // Skip well-known system mappings - if m.Unsymbolizable() { - continue - } - - // Skip mappings pointing to a source URL - if m.BuildID == "" { - if u, err := url.Parse(m.File); err == nil && u.IsAbs() && strings.Contains(strings.ToLower(u.Scheme), "http") { - continue - } - } - - name := filepath.Base(m.File) - if m.BuildID != "" { - name += fmt.Sprintf(" (build ID %s)", m.BuildID) - } - f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol) - if err != nil { - ui.PrintErr("Local symbolization failed for ", name, ": ", err) - missingBinaries = true - continue - } - if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID { - ui.PrintErr("Local symbolization failed for ", name, ": build ID mismatch") - f.Close() - continue - } - - mt.segments[m] = f - } - if missingBinaries { - ui.PrintErr("Some binary filenames not available. Symbolization may be incomplete.\n" + - "Try setting PPROF_BINARY_PATH to the search path for local binaries.") - } - return mt, nil -} - -// mappingTable contains the mechanisms for symbolization of a -// profile. -type mappingTable struct { - prof *profile.Profile - segments map[*profile.Mapping]plugin.ObjFile -} - -// close releases any external processes being used for the mapping. -func (mt *mappingTable) close() { - for _, segment := range mt.segments { - segment.Close() - } -} diff --git a/src/cmd/vendor/github.com/google/pprof/profile/profile.go b/src/cmd/vendor/github.com/google/pprof/profile/profile.go index 62df80a556..5551eb0bfa 100644 --- a/src/cmd/vendor/github.com/google/pprof/profile/profile.go +++ b/src/cmd/vendor/github.com/google/pprof/profile/profile.go @@ -847,7 +847,7 @@ func (p *Profile) HasFileLines() bool { // "[vdso]", [vsyscall]" and some others, see the code. func (m *Mapping) Unsymbolizable() bool { name := filepath.Base(m.File) - return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/") + return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/") || m.File == "//anon" } // Copy makes a fully independent copy of a profile. |
