Beispiel #1
0
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
}
Beispiel #2
0
// 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
}
Beispiel #3
0
// 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())
		}
	}
}
Beispiel #4
0
// 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 = "/profilez"
	case "/protoz":
		// Rewrite to /profilez?type=proto
		url.Path = "/profilez"
		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
}
Beispiel #5
0
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)
}
Beispiel #6
0
// 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
}
Beispiel #7
0
// 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
}
Beispiel #8
0
// 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
}
Beispiel #9
0
func warnNoMatches(match bool, rx, option string, ui plugin.UI) {
	if !match && rx != "" && rx != "." {
		ui.PrintErr(option + " expression matched no samples: " + rx)
	}
}