func compileTagFilter(filter string, ui plugin.UI) (f func(string, string, int64) bool, err error) { if filter == "" { return nil, nil } if numFilter := parseTagFilterRange(filter); numFilter != nil { ui.PrintErr("Interpreted '", filter, "' as range, not regexp") return func(key, val string, num int64) bool { if val != "" { return false } return numFilter(num, key) }, nil } fx, err := regexp.Compile(filter) if err != nil { return nil, err } return func(key, val string, num int64) bool { if val == "" { return false } return fx.MatchString(key + ":" + val) }, nil }
// symbolize attempts to symbolize profile p. // If the source is a local binary, it tries using symbolizer and obj. // If the source is a URL, it fetches symbol information using symbolz. func symbolize(mode, source string, p *profile.Profile, obj plugin.ObjTool, ui plugin.UI) error { remote, local := true, true for _, o := range strings.Split(strings.ToLower(mode), ":") { switch o { case "none", "no": return nil case "local": remote, local = false, true case "remote": remote, local = true, false default: ui.PrintErr("ignoring unrecognized symbolization option: " + mode) ui.PrintErr("expecting -symbolize=[local|remote|none][:force]") fallthrough case "", "force": // Ignore these options, -force is recognized by symbolizer.Symbolize } } var err error if local { // Symbolize using binutils. if err = symbolizer.Symbolize(mode, p, obj, ui); err == nil { return nil } } if remote { err = symbolz.Symbolize(source, fetch.PostURL, p) } return err }
// adjustURL updates the profile source URL based on heuristics. It // will append ?seconds=sec for CPU profiles if not already // specified. Returns the hostname if the profile is remote. func adjustURL(source string, sec int, ui plugin.UI) (adjusted, host string, duration time.Duration) { // If there is a local file with this name, just use it. if _, err := os.Stat(source); err == nil { return source, "", 0 } url, err := url.Parse(source) // Automatically add http:// to URLs of the form hostname:port/path. // url.Parse treats "hostname" as the Scheme. if err != nil || (url.Host == "" && url.Scheme != "" && url.Scheme != "file") { url, err = url.Parse("http://" + source) if err != nil { return source, "", 0 } } if scheme := strings.ToLower(url.Scheme); scheme == "" || scheme == "file" { url.Scheme = "" return url.String(), "", 0 } values := url.Query() if urlSeconds := values.Get("seconds"); urlSeconds != "" { if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil { if sec >= 0 { ui.PrintErr("Overriding -seconds for URL ", source) } sec = int(us) } } switch strings.ToLower(url.Path) { case "", "/": // Apply default /profilez. url.Path = cpuProfileHandler case "/protoz": // Rewrite to /profilez?type=proto url.Path = cpuProfileHandler values.Set("type", "proto") } if hasDuration(url.Path) { if sec > 0 { duration = time.Duration(sec) * time.Second values.Set("seconds", fmt.Sprintf("%d", sec)) } else { // Assume default duration: 30 seconds duration = 30 * time.Second } } url.RawQuery = values.Encode() return url.String(), url.Host, duration }
func generate(interactive bool, prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error { o, postProcess, err := parseOptions(f) if err != nil { return err } var w io.Writer if *f.flagOutput == "" { w = os.Stdout } else { ui.PrintErr("Generating report in ", *f.flagOutput) outputFile, err := os.Create(*f.flagOutput) if err != nil { return err } defer outputFile.Close() w = outputFile } if prof.Empty() { return fmt.Errorf("profile is empty") } value, stype, unit := sampleFormat(prof, f) o.SampleType = stype rpt := report.New(prof, *o, value, unit) // Do not apply filters if we're just generating a proto, so we // still have all the data. if o.OutputFormat != report.Proto { // Delay applying focus/ignore until after creating the report so // the report reflects the total number of samples. if err := preprocess(prof, ui, f); err != nil { return err } } if postProcess == nil { return report.Generate(w, rpt, obj) } var dot bytes.Buffer if err = report.Generate(&dot, rpt, obj); err != nil { return err } return postProcess(&dot, w, ui) }
// interactive displays a prompt and reads commands for profile // manipulation/visualization. func interactive(p *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error { updateAutoComplete(p) // Enter command processing loop. ui.Print("Entering interactive mode (type \"help\" for commands)") ui.SetAutoComplete(commands.NewCompleter(f.commands)) for { input, err := readCommand(p, ui, f) if err != nil { if err != io.EOF { return err } if input == "" { return nil } } // Process simple commands. switch input { case "": continue case ":": f.flagFocus = newString("") f.flagIgnore = newString("") f.flagTagFocus = newString("") f.flagTagIgnore = newString("") f.flagHide = newString("") continue } fields := splitCommand(input) // Process report generation commands. if _, ok := f.commands[fields[0]]; ok { if err := generateReport(p, fields, obj, ui, f); err != nil { if err == io.EOF { return nil } ui.PrintErr(err) } continue } switch cmd := fields[0]; cmd { case "help": commandHelp(fields, ui, f) continue case "exit", "quit": return nil } // Process option settings. if of, err := optFlags(p, input, f); err == nil { f = of } else { ui.PrintErr("Error: ", err.Error()) } } }
func (f *flags) usage(ui plugin.UI) { var commandMsg []string for name, cmd := range f.commands { if cmd.HasParam { name = name + "=p" } commandMsg = append(commandMsg, fmt.Sprintf(" -%-16s %s", name, cmd.Usage)) } sort.Strings(commandMsg) text := usageMsgHdr + strings.Join(commandMsg, "\n") + "\n" + usageMsg + "\n" if f.extraUsage != "" { text += f.extraUsage + "\n" } text += usageMsgVars ui.Print(text) }
// symbolize attempts to symbolize profile p. // If the source is a local binary, it tries using symbolizer and obj. // If the source is a URL, it fetches symbol information using symbolz. func symbolize(mode, source string, p *profile.Profile, obj plugin.ObjTool, ui plugin.UI) error { remote, local := true, true for _, o := range strings.Split(strings.ToLower(mode), ":") { switch o { case "none", "no": return nil case "local": remote, local = false, true case "remote": remote, local = true, false default: ui.PrintErr("ignoring unrecognized symbolization option: " + mode) ui.PrintErr("expecting -symbolize=[local|remote|none][:force]") fallthrough case "", "force": // -force is recognized by symbolizer.Symbolize. // If the source is remote, and the mapping file // does not exist, don't use local symbolization. if isRemote(source) { if len(p.Mapping) == 0 { local = false } else if _, err := os.Stat(p.Mapping[0].File); err != nil { local = false } } } } var err error if local { // Symbolize using binutils. if err = symbolizer.Symbolize(mode, p, obj, ui); err == nil { return nil } } if remote { err = symbolz.Symbolize(source, fetch.PostURL, p) } return err }
// 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 } for _, 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 } f, err := locateFile(obj, m.File, m.BuildID, m.Start) if err != nil { ui.PrintErr("Local symbolization failed for ", filepath.Base(m.File), ": ", err) // Move on to other mappings continue } if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID { // Build ID mismatch - ignore. f.Close() continue } mt.segments[m] = f } return mt, nil }
// grabProfile fetches and symbolizes a profile. func grabProfile(source, exec, buildid string, fetch plugin.Fetcher, sym plugin.Symbolizer, obj plugin.ObjTool, ui plugin.UI, f *flags) (*profile.Profile, error) { source, host, duration := adjustURL(source, *f.flagSeconds, ui) remote := host != "" if remote { ui.Print("Fetching profile from ", source) if duration != 0 { ui.Print("Please wait... (" + duration.String() + ")") } } now := time.Now() // Fetch profile from source. // Give 50% slack on the timeout. p, err := fetch(source, duration+duration/2, ui) if err != nil { return nil, err } // Update the time/duration if the profile source doesn't include it. // TODO(rsilvera): Remove this when we remove support for legacy profiles. if remote { if p.TimeNanos == 0 { p.TimeNanos = now.UnixNano() } if duration != 0 && p.DurationNanos == 0 { p.DurationNanos = int64(duration) } } // Replace executable/buildID with the options provided in the // command line. Assume the executable is the first Mapping entry. if exec != "" || buildid != "" { if len(p.Mapping) == 0 { // Create a fake mapping to hold the user option, and associate // all samples to it. m := &profile.Mapping{ ID: 1, } for _, l := range p.Location { l.Mapping = m } p.Mapping = []*profile.Mapping{m} } if exec != "" { p.Mapping[0].File = exec } if buildid != "" { p.Mapping[0].BuildID = buildid } } if err := sym(*f.flagSymbolize, source, p, obj, ui); err != nil { return nil, err } // Save a copy of any remote profiles, unless the user is explicitly // saving it. if remote && !f.isFormat("proto") { prefix := "pprof." if len(p.Mapping) > 0 && p.Mapping[0].File != "" { prefix = prefix + filepath.Base(p.Mapping[0].File) + "." } if !strings.ContainsRune(host, os.PathSeparator) { prefix = prefix + host + "." } for _, s := range p.SampleType { prefix = prefix + s.Type + "." } dir := os.Getenv("PPROF_TMPDIR") tempFile, err := tempfile.New(dir, prefix, ".pb.gz") if err == nil { if err = p.Write(tempFile); err == nil { ui.PrintErr("Saved profile in ", tempFile.Name()) } } if err != nil { ui.PrintErr("Could not save profile: ", err) } } if err := p.Demangle(obj.Demangle); err != nil { ui.PrintErr("Failed to demangle profile: ", err) } if err := p.CheckValid(); err != nil { return nil, fmt.Errorf("Grab %s: %v", source, err) } return p, nil }
func warnNoMatches(match bool, rx, option string, ui plugin.UI) { if !match && rx != "" && rx != "." { ui.PrintErr(option + " expression matched no samples: " + rx) } }
func commandHelp(_ []string, ui plugin.UI, f *flags) error { help := ` Commands: cmd [n] [--cum] [focus_regex]* [-ignore_regex]* Produce a text report with the top n entries. Include samples matching focus_regex, and exclude ignore_regex. Add --cum to sort using cumulative data. Available commands: ` var commands []string for name, cmd := range f.commands { commands = append(commands, fmt.Sprintf(" %-12s %s", name, cmd.Usage)) } sort.Strings(commands) help = help + strings.Join(commands, "\n") + ` peek func_regex Display callers and callees of functions matching func_regex. dot [n] [focus_regex]* [-ignore_regex]* [>file] Produce an annotated callgraph with the top n entries. Include samples matching focus_regex, and exclude ignore_regex. For other outputs, replace dot with: - Graphic formats: dot, svg, pdf, ps, gif, png (use > to name output file) - Graph viewer: gv, web, evince, eog callgrind [n] [focus_regex]* [-ignore_regex]* [>file] Produce a file in callgrind-compatible format. Include samples matching focus_regex, and exclude ignore_regex. weblist func_regex [-ignore_regex]* Show annotated source with interspersed assembly in a web browser. list func_regex [-ignore_regex]* Print source for routines matching func_regex, and exclude ignore_regex. disasm func_regex [-ignore_regex]* Disassemble routines matching func_regex, and exclude ignore_regex. tags tag_regex [-ignore_regex]* List tags with key:value matching tag_regex and exclude ignore_regex. quit/exit/^D Exit pprof. option=value The following options can be set individually: cum/flat: Sort entries based on cumulative or flat data call_tree: Build context-sensitive call trees nodecount: Max number of entries to display nodefraction: Min frequency ratio of nodes to display edgefraction: Min frequency ratio of edges to display focus/ignore: Regexp to include/exclude samples by name/file tagfocus/tagignore: Regexp or value range to filter samples by tag eg "1mb", "1mb:2mb", ":64kb" functions: Level of aggregation for sample data files: lines: addresses: unit: Measurement unit to use on reports Sample value selection by index: sample_index: Index of sample value to display mean: Average sample value over first value Sample value selection by name: alloc_space for heap profiles alloc_objects inuse_space inuse_objects total_delay for contention profiles mean_delay contentions : Clear focus/ignore/hide/tagfocus/tagignore` ui.Print(help) return nil }
// readCommand prompts for and reads the next command. func readCommand(p *profile.Profile, ui plugin.UI, f *flags) (string, error) { //ui.Print("Options:\n", f.String(p)) s, err := ui.ReadLine() return strings.TrimSpace(s), err }