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. 18package driver 19 20import ( 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. 37func 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. 64func 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. 114func 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 166func 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 178func 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. 235func 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 244func dropEmptyStrings(in []string) (out []string) { 245 for _, s := range in { 246 if s != "" { 247 out = append(out, s) 248 } 249 } 250 return 251} 252 253func 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 282func 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. 351func 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 362type 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. 366func 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 382func 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. 390type profileCopier []byte 391 392func 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. 400func (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