// greetings prints a brief welcome and some overall profile // information before accepting interactive commands. func greetings(p *profile.Profile, ui plugin.UI) { ropt, err := reportOptions(p, pprofVariables) if err == nil { ui.Print(strings.Join(report.ProfileLabels(report.New(p, ropt)), "\n")) } ui.Print("Entering interactive mode (type \"help\" for commands, \"o\" for options)") }
func sampleIndex(flag *bool, si string, sampleType, option string, ui plugin.UI) string { if *flag { if si == "" { return sampleType } ui.PrintErr("Multiple value selections, ignoring ", option) } return si }
func printCurrentOptions(p *profile.Profile, ui plugin.UI) { var args []string type groupInfo struct { set string values []string } groups := make(map[string]*groupInfo) for n, o := range pprofVariables { v := o.stringValue() comment := "" if g := o.group; g != "" { gi, ok := groups[g] if !ok { gi = &groupInfo{} groups[g] = gi } if o.boolValue() { gi.set = n } gi.values = append(gi.values, n) continue } switch { case n == "sample_index": st := sampleTypes(p) if v == "" { // Apply default (last sample index). v = st[len(st)-1] } // Add comments for all sample types in profile. comment = "[" + strings.Join(st, " | ") + "]" case n == "source_path": continue case n == "nodecount" && v == "-1": comment = "default" case v == "": // Add quotes for empty values. v = `""` } if comment != "" { comment = commentStart + " " + comment } args = append(args, fmt.Sprintf(" %-25s = %-20s %s", n, v, comment)) } for g, vars := range groups { sort.Strings(vars.values) comment := commentStart + " [" + strings.Join(vars.values, " | ") + "]" args = append(args, fmt.Sprintf(" %-25s = %-20s %s", g, vars.set, comment)) } sort.Strings(args) ui.Print(strings.Join(args, "\n")) }
// setTmpDir prepares the directory to use to save profiles retrieved // remotely. It is selected from PPROF_TMPDIR, defaults to $HOME/pprof. func setTmpDir(ui plugin.UI) (string, error) { if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir != "" { return profileDir, nil } for _, tmpDir := range []string{os.Getenv("HOME") + "/pprof", os.TempDir()} { if err := os.MkdirAll(tmpDir, 0755); err != nil { ui.PrintErr("Could not use temp dir ", tmpDir, ": ", err.Error()) continue } return tmpDir, nil } return "", fmt.Errorf("failed to identify temp dir") }
// locateBinaries searches for binary files listed in the profile and, if found, // updates the profile accordingly. func locateBinaries(p *profile.Profile, s *source, obj plugin.ObjTool, ui plugin.UI) { // Construct search path to examine searchPath := os.Getenv("PPROF_BINARY_PATH") if searchPath == "" { // Use $HOME/pprof/binaries as default directory for local symbolization binaries searchPath = filepath.Join(os.Getenv("HOME"), "pprof", "binaries") } mapping: for i, m := range p.Mapping { var baseName string // Replace executable filename/buildID with the overrides from source. // Assumes the executable is the first Mapping entry. if i == 0 { if s.ExecName != "" { m.File = s.ExecName } if s.BuildID != "" { m.BuildID = s.BuildID } } if m.File != "" { baseName = filepath.Base(m.File) } for _, path := range filepath.SplitList(searchPath) { var fileNames []string if m.BuildID != "" { fileNames = []string{filepath.Join(path, m.BuildID, baseName)} if matches, err := filepath.Glob(filepath.Join(path, m.BuildID, "*")); err == nil { fileNames = append(fileNames, matches...) } } if baseName != "" { fileNames = append(fileNames, filepath.Join(path, baseName)) } for _, name := range fileNames { if f, err := obj.Open(name, m.Start, m.Limit, m.Offset); err == nil { defer f.Close() fileBuildID := f.BuildID() if m.BuildID != "" && m.BuildID != fileBuildID { ui.PrintErr("Ignoring local file " + name + ": build-id mismatch (" + m.BuildID + " != " + fileBuildID + ")") } else { m.File = name continue mapping } } } } } }
// commandHelp displays help and usage information for all Commands // and Variables or a specific Command or Variable. func commandHelp(args string, ui plugin.UI) { if args == "" { help := usage(false) help = help + ` : Clear focus/ignore/hide/tagfocus/tagignore type "help <cmd|option>" for more information ` ui.Print(help) return } if c := pprofCommands[args]; c != nil { ui.Print(c.help(args)) return } if v := pprofVariables[args]; v != nil { ui.Print(v.help + "\n") return } ui.PrintErr("Unknown command: " + args) }
// convertPerfData converts the file at path which should be in perf.data format // using the perf_to_profile tool and returns the file containing the // profile.proto formatted data. func convertPerfData(perfPath string, ui plugin.UI) (*os.File, error) { ui.Print(fmt.Sprintf( "Converting %s to a profile.proto... (May take a few minutes)", perfPath)) profile, err := newTempFile(os.TempDir(), "pprof_", ".pb.gz") if err != nil { return nil, err } deferDeleteTempFile(profile.Name()) cmd := exec.Command("perf_to_profile", perfPath, profile.Name()) if err := cmd.Run(); err != nil { profile.Close() return nil, fmt.Errorf("failed to convert perf.data file. Try github.com/google/perf_data_converter: %v", err) } return profile, nil }
// 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 } // Skip well-known system mappings name := filepath.Base(m.File) if name == "" || name == "[vdso]" || strings.HasPrefix(name, "linux-vdso") { continue } f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset) if err != nil { ui.PrintErr("Local symbolization failed for ", name, ": ", err) 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 } return mt, nil }
// fetch fetches a profile from source, within the timeout specified, // producing messages through the ui. It returns the profile and the // url of the actual source of the profile for remote profiles. func fetch(source string, duration, timeout time.Duration, ui plugin.UI) (p *profile.Profile, src string, err error) { var f io.ReadCloser if sourceURL, timeout := adjustURL(source, duration, timeout); sourceURL != "" { ui.Print("Fetching profile over HTTP from " + sourceURL) if duration > 0 { ui.Print(fmt.Sprintf("Please wait... (%v)", duration)) } f, err = fetchURL(sourceURL, timeout) src = sourceURL } else { f, err = os.Open(source) } if err == nil { defer f.Close() p, err = profile.Parse(f) } return }
func compileTagFilter(name, value string, ui plugin.UI, err error) (func(*profile.Sample) bool, error) { if value == "" || err != nil { return nil, err } if numFilter := parseTagFilterRange(value); numFilter != nil { ui.PrintErr(name, ":Interpreted '", value, "' as range, not regexp") return func(s *profile.Sample) bool { for key, vals := range s.NumLabel { for _, val := range vals { if numFilter(val, key) { return true } } } return false }, nil } var rfx []*regexp.Regexp for _, tagf := range strings.Split(value, ",") { fx, err := regexp.Compile(tagf) if err != nil { return nil, fmt.Errorf("parsing %s regexp: %v", name, err) } rfx = append(rfx, fx) } return func(s *profile.Sample) bool { matchedrx: for _, rx := range rfx { for key, vals := range s.Label { for _, val := range vals { if rx.MatchString(key + ":" + val) { continue matchedrx } } } return false } return true }, nil }
// concurrentGrab fetches multiple profiles concurrently func concurrentGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, plugin.MappingSources, bool, int, error) { wg := sync.WaitGroup{} wg.Add(len(sources)) for i := range sources { go func(s *profileSource) { defer wg.Done() s.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, s.scale, fetch, obj, ui) }(&sources[i]) } wg.Wait() var save bool profiles := make([]*profile.Profile, 0, len(sources)) msrcs := make([]plugin.MappingSources, 0, len(sources)) for i := range sources { s := &sources[i] if err := s.err; err != nil { ui.PrintErr(s.addr + ": " + err.Error()) continue } save = save || s.remote profiles = append(profiles, s.p) msrcs = append(msrcs, s.msrc) *s = profileSource{} } if len(profiles) == 0 { return nil, nil, false, 0, nil } p, msrc, err := combineProfiles(profiles, msrcs) if err != nil { return nil, nil, false, 0, err } return p, msrc, save, len(profiles), nil }
func warnNoMatches(match bool, option string, ui plugin.UI) { if !match { ui.PrintErr(option + " expression matched no samples") } }
// 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.\n" + "Try passing the path to the main binary before the profile.") continue } missingBinaries = true continue } // Skip well-known system mappings name := filepath.Base(m.File) if name == "[vdso]" || strings.HasPrefix(name, "linux-vdso") { continue } // Skip mappings pointing to a source URL if m.BuildID == "" { if u, err := url.Parse(m.File); err == nil && u.IsAbs() { continue } } f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset) if err != nil { ui.PrintErr("Local symbolization failed for ", name, ": ", err) 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.") } return mt, nil }
// concurrentGrab fetches multiple profiles concurrently func concurrentGrab(s *source, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, plugin.MappingSources, bool, error) { wg := sync.WaitGroup{} numprofs := len(s.Sources) + len(s.Base) profs := make([]*profile.Profile, numprofs) msrcs := make([]plugin.MappingSources, numprofs) remote := make([]bool, numprofs) errs := make([]error, numprofs) for i, source := range s.Sources { wg.Add(1) go func(i int, src string) { defer wg.Done() profs[i], msrcs[i], remote[i], errs[i] = grabProfile(s, src, 1, fetch, obj, ui) }(i, source) } for i, source := range s.Base { wg.Add(1) go func(i int, src string) { defer wg.Done() profs[i], msrcs[i], remote[i], errs[i] = grabProfile(s, src, -1, fetch, obj, ui) }(i+len(s.Sources), source) } wg.Wait() var save bool var numFailed = 0 for i, src := range s.Sources { if errs[i] != nil { ui.PrintErr(src + ": " + errs[i].Error()) numFailed++ } save = save || remote[i] } for i, src := range s.Base { b := i + len(s.Sources) if errs[b] != nil { ui.PrintErr(src + ": " + errs[b].Error()) numFailed++ } save = save || remote[b] } if numFailed == numprofs { return nil, nil, false, fmt.Errorf("failed to fetch any profiles") } if numFailed > 0 { ui.PrintErr(fmt.Sprintf("fetched %d profiles out of %d", numprofs-numFailed, numprofs)) } scaled := make([]*profile.Profile, 0, numprofs) for _, p := range profs { if p != nil { scaled = append(scaled, p) } } // Merge profiles. if err := measurement.ScaleProfiles(scaled); err != nil { return nil, nil, false, err } p, err := profile.Merge(scaled) if err != nil { return nil, nil, false, err } // Combine mapping sources. msrc := make(plugin.MappingSources) for _, ms := range msrcs { for m, s := range ms { msrc[m] = append(msrc[m], s...) } } return p, msrc, save, nil }