// locateFile opens a local file for symbolization on the search path // at $PPROF_BINARY_PATH. Looks inside these directories for files // named $BUILDID/$BASENAME and $BASENAME (if build id is available). func locateFile(obj plugin.ObjTool, file, buildID string, start uint64) (plugin.ObjFile, error) { // 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") } // Collect names to search: {buildid/basename, basename} var fileNames []string if baseName := filepath.Base(file); buildID != "" { fileNames = []string{filepath.Join(buildID, baseName), baseName} } else { fileNames = []string{baseName} } for _, path := range filepath.SplitList(searchPath) { for nameIndex, name := range fileNames { file := filepath.Join(path, name) if f, err := obj.Open(file, start); err == nil { fileBuildID := f.BuildID() if buildID == "" || buildID == fileBuildID { return f, nil } f.Close() if nameIndex == 0 { // If this is the first name, the path includes the build id. Report inconsistency. return nil, fmt.Errorf("found file %s with inconsistent build id %s", file, fileBuildID) } } } } // Try original file name f, err := obj.Open(file, start) if err == nil && buildID != "" { if fileBuildID := f.BuildID(); fileBuildID != "" && fileBuildID != buildID { // Mismatched build IDs, ignore f.Close() return nil, fmt.Errorf("mismatched build ids %s != %s", fileBuildID, buildID) } } return f, err }
// PProf acquires a profile, and symbolizes it using a profile // manager. Then it generates a report formatted according to the // options selected through the flags package. func PProf(flagset plugin.FlagSet, fetch plugin.Fetcher, sym plugin.Symbolizer, obj plugin.ObjTool, ui plugin.UI, overrides commands.Commands) error { // Remove any temporary files created during pprof processing. defer tempfile.Cleanup() f, err := getFlags(flagset, overrides, ui) if err != nil { return err } obj.SetConfig(*f.flagTools) sources := f.profileSource if len(sources) > 1 { source := sources[0] // If the first argument is a supported object file, treat as executable. if file, err := obj.Open(source, 0); err == nil { file.Close() f.profileExecName = source sources = sources[1:] } else if *f.flagBuildID == "" && isBuildID(source) { f.flagBuildID = &source sources = sources[1:] } } // errMu protects concurrent accesses to errset and err. errset is set if an // error is encountered by one of the goroutines grabbing a profile. errMu, errset := sync.Mutex{}, false // Fetch profiles. wg := sync.WaitGroup{} profs := make([]*profile.Profile, len(sources)) for i, source := range sources { wg.Add(1) go func(i int, src string) { defer wg.Done() p, grabErr := grabProfile(src, f.profileExecName, *f.flagBuildID, fetch, sym, obj, ui, f) if grabErr != nil { errMu.Lock() defer errMu.Unlock() errset, err = true, grabErr return } profs[i] = p }(i, source) } wg.Wait() if errset { return err } // Merge profiles. prof := profs[0] for _, p := range profs[1:] { if err = prof.Merge(p, 1); err != nil { return err } } if *f.flagBase != "" { // Fetch base profile and subtract from current profile. base, err := grabProfile(*f.flagBase, f.profileExecName, *f.flagBuildID, fetch, sym, obj, ui, f) if err != nil { return err } if err = prof.Merge(base, -1); err != nil { return err } } if err := processFlags(prof, ui, f); err != nil { return err } if !*f.flagRuntime { prof.RemoveUninteresting() } if *f.flagInteractive { return interactive(prof, obj, ui, f) } return generate(false, prof, obj, ui, f) }