github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/scripts/pprof-view/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"flag"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"os"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/pyroscope-io/pyroscope/pkg/convert/pprof"
    15  	"github.com/pyroscope-io/pyroscope/pkg/storage"
    16  	"github.com/pyroscope-io/pyroscope/pkg/storage/tree"
    17  
    18  	"gopkg.in/yaml.v2"
    19  )
    20  
    21  func main() {
    22  	// the idea here is that you can run it via go main like so:
    23  	//   cat heap.pprof.gz | go run scripts/pprof-view/main.go
    24  	// and the script will print a json version of a given profile
    25  	if len(os.Args) == 1 {
    26  		if err := dumpJSON(os.Stdout); err != nil {
    27  			log.Fatalln(err)
    28  		}
    29  		return
    30  	}
    31  
    32  	// You can also parse pprof with a config:
    33  	//   go run scripts/pprof-view/main.go -path heap.pb.gz -type mem -config ./scripts/pprof-view/pprof-config.yaml
    34  	// If config is not specified, the default one is used (see tree.DefaultSampleTypeMapping).
    35  	var (
    36  		configPath  string
    37  		pprofPath   string
    38  		profileType string
    39  	)
    40  
    41  	flag.StringVar(&configPath, "config", "", "path to pprof parsing config")
    42  	flag.StringVar(&pprofPath, "path", "", "path tp pprof data (gzip or plain)")
    43  	flag.StringVar(&profileType, "type", "cpu", "profile type from the config (cpu, mem, goroutines, etc)")
    44  	flag.Parse()
    45  
    46  	if err := printProfiles(os.Stdout, pprofPath, configPath, profileType); err != nil {
    47  		log.Fatalln(err)
    48  	}
    49  }
    50  
    51  func dumpJSON(w io.Writer) error {
    52  	var p tree.Profile
    53  	if err := pprof.Decode(os.Stdin, &p); err != nil {
    54  		return err
    55  	}
    56  	b, err := json.MarshalIndent(&p, "", "  ")
    57  	if err != nil {
    58  		return err
    59  	}
    60  	_, err = fmt.Fprintln(w, string(b))
    61  	return err
    62  }
    63  
    64  type ingester struct{ actual []*storage.PutInput }
    65  
    66  func (m *ingester) Put(_ context.Context, p *storage.PutInput) error {
    67  	m.actual = append(m.actual, p)
    68  	return nil
    69  }
    70  
    71  func printProfiles(w io.Writer, pprofPath, configPath, profileType string) error {
    72  	c := tree.DefaultSampleTypeMapping
    73  	if configPath != "" {
    74  		sc, err := readPprofConfig(configPath)
    75  		if err != nil {
    76  			return fmt.Errorf("reading pprof parsing config: %w", err)
    77  		}
    78  		var ok bool
    79  		if c, ok = sc[profileType]; !ok {
    80  			return fmt.Errorf("profile type not found in the config")
    81  		}
    82  	}
    83  
    84  	p, err := readPprof(pprofPath)
    85  	if err != nil {
    86  		return fmt.Errorf("reading pprof file: %w", err)
    87  	}
    88  
    89  	x := new(ingester)
    90  	pw := pprof.NewParser(pprof.ParserConfig{
    91  		Putter:      x,
    92  		SampleTypes: c,
    93  		SpyName:     "spy-name",
    94  		Labels:      nil,
    95  	})
    96  
    97  	if err = pw.Convert(context.TODO(), time.Time{}, time.Time{}, p, false); err != nil {
    98  		return fmt.Errorf("parsing pprof: %w", err)
    99  	}
   100  
   101  	_, _ = fmt.Fprintln(w, "Found profiles:", len(x.actual))
   102  	for i, profile := range x.actual {
   103  		_, _ = fmt.Fprintln(w, strings.Repeat("-", 80))
   104  		_, _ = fmt.Fprintf(w, "Profile %d: <app_name>%s\n", i+1, profile.Key.Normalized())
   105  		_, _ = fmt.Fprintln(w, "\tAggregation:", profile.AggregationType)
   106  		_, _ = fmt.Fprintln(w, "\tUnits:", profile.Units)
   107  		_, _ = fmt.Fprintln(w, "\tTotal:", profile.Val.Samples())
   108  		_, _ = fmt.Fprintln(w, "\tSample rate:", profile.SampleRate)
   109  		_, _ = fmt.Fprintf(w, "\n%s\n", profile.Val)
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  func readPprofConfig(path string) (map[string]map[string]*tree.SampleTypeConfig, error) {
   116  	f, err := os.Open(path)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	defer func() {
   121  		_ = f.Close()
   122  	}()
   123  	var c map[string]map[string]*tree.SampleTypeConfig
   124  	if err = yaml.NewDecoder(f).Decode(&c); err != nil {
   125  		return nil, err
   126  	}
   127  	return c, nil
   128  }
   129  
   130  func readPprof(path string) (*tree.Profile, error) {
   131  	f, err := os.Open(path)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	defer func() {
   136  		_ = f.Close()
   137  	}()
   138  	var p tree.Profile
   139  	if err = pprof.Decode(f, &p); err != nil {
   140  		return nil, err
   141  	}
   142  	return &p, nil
   143  }