github.com/bir3/gocompiler@v0.9.2202/src/internal/trace/traceviewer/pprof.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Serving of pprof-like profiles. 6 7 package traceviewer 8 9 import ( 10 "bufio" 11 "fmt" 12 "github.com/bir3/gocompiler/src/internal/profile" 13 "github.com/bir3/gocompiler/src/internal/trace" 14 "net/http" 15 "os" 16 "os/exec" 17 "path/filepath" 18 "runtime" 19 "time" 20 ) 21 22 type ProfileFunc func(r *http.Request) ([]ProfileRecord, error) 23 24 // SVGProfileHandlerFunc serves pprof-like profile generated by prof as svg. 25 func SVGProfileHandlerFunc(f ProfileFunc) http.HandlerFunc { 26 return func(w http.ResponseWriter, r *http.Request) { 27 if r.FormValue("raw") != "" { 28 w.Header().Set("Content-Type", "application/octet-stream") 29 30 failf := func(s string, args ...any) { 31 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 32 w.Header().Set("X-Go-Pprof", "1") 33 http.Error(w, fmt.Sprintf(s, args...), http.StatusInternalServerError) 34 } 35 records, err := f(r) 36 if err != nil { 37 failf("failed to get records: %v", err) 38 return 39 } 40 if err := BuildProfile(records).Write(w); err != nil { 41 failf("failed to write profile: %v", err) 42 return 43 } 44 return 45 } 46 47 blockf, err := os.CreateTemp("", "block") 48 if err != nil { 49 http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError) 50 return 51 } 52 defer func() { 53 blockf.Close() 54 os.Remove(blockf.Name()) 55 }() 56 records, err := f(r) 57 if err != nil { 58 http.Error(w, fmt.Sprintf("failed to generate profile: %v", err), http.StatusInternalServerError) 59 } 60 blockb := bufio.NewWriter(blockf) 61 if err := BuildProfile(records).Write(blockb); err != nil { 62 http.Error(w, fmt.Sprintf("failed to write profile: %v", err), http.StatusInternalServerError) 63 return 64 } 65 if err := blockb.Flush(); err != nil { 66 http.Error(w, fmt.Sprintf("failed to flush temp file: %v", err), http.StatusInternalServerError) 67 return 68 } 69 if err := blockf.Close(); err != nil { 70 http.Error(w, fmt.Sprintf("failed to close temp file: %v", err), http.StatusInternalServerError) 71 return 72 } 73 svgFilename := blockf.Name() + ".svg" 74 if output, err := exec.Command(goCmd(), "tool", "pprof", "-svg", "-output", svgFilename, blockf.Name()).CombinedOutput(); err != nil { 75 http.Error(w, fmt.Sprintf("failed to execute go tool pprof: %v\n%s", err, output), http.StatusInternalServerError) 76 return 77 } 78 defer os.Remove(svgFilename) 79 w.Header().Set("Content-Type", "image/svg+xml") 80 http.ServeFile(w, r, svgFilename) 81 } 82 } 83 84 type ProfileRecord struct { 85 Stack []*trace.Frame 86 Count uint64 87 Time time.Duration 88 } 89 90 func BuildProfile(prof []ProfileRecord) *profile.Profile { 91 p := &profile.Profile{ 92 PeriodType: &profile.ValueType{Type: "trace", Unit: "count"}, 93 Period: 1, 94 SampleType: []*profile.ValueType{ 95 {Type: "contentions", Unit: "count"}, 96 {Type: "delay", Unit: "nanoseconds"}, 97 }, 98 } 99 locs := make(map[uint64]*profile.Location) 100 funcs := make(map[string]*profile.Function) 101 for _, rec := range prof { 102 var sloc []*profile.Location 103 for _, frame := range rec.Stack { 104 loc := locs[frame.PC] 105 if loc == nil { 106 fn := funcs[frame.File+frame.Fn] 107 if fn == nil { 108 fn = &profile.Function{ 109 ID: uint64(len(p.Function) + 1), 110 Name: frame.Fn, 111 SystemName: frame.Fn, 112 Filename: frame.File, 113 } 114 p.Function = append(p.Function, fn) 115 funcs[frame.File+frame.Fn] = fn 116 } 117 loc = &profile.Location{ 118 ID: uint64(len(p.Location) + 1), 119 Address: frame.PC, 120 Line: []profile.Line{ 121 { 122 Function: fn, 123 Line: int64(frame.Line), 124 }, 125 }, 126 } 127 p.Location = append(p.Location, loc) 128 locs[frame.PC] = loc 129 } 130 sloc = append(sloc, loc) 131 } 132 p.Sample = append(p.Sample, &profile.Sample{ 133 Value: []int64{int64(rec.Count), int64(rec.Time)}, 134 Location: sloc, 135 }) 136 } 137 return p 138 } 139 140 func goCmd() string { 141 var exeSuffix string 142 if runtime.GOOS == "windows" { 143 exeSuffix = ".exe" 144 } 145 path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix) 146 if _, err := os.Stat(path); err == nil { 147 return path 148 } 149 return "go" 150 }