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 }