github.com/c9s/go@v0.0.0-20180120015821-984e81f64e0c/src/cmd/trace/pprof.go (about) 1 // Copyright 2014 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 main 8 9 import ( 10 "bufio" 11 "fmt" 12 "internal/trace" 13 "io" 14 "io/ioutil" 15 "net/http" 16 "os" 17 "os/exec" 18 "path/filepath" 19 "runtime" 20 "strconv" 21 22 "github.com/google/pprof/profile" 23 ) 24 25 func goCmd() string { 26 var exeSuffix string 27 if runtime.GOOS == "windows" { 28 exeSuffix = ".exe" 29 } 30 path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix) 31 if _, err := os.Stat(path); err == nil { 32 return path 33 } 34 return "go" 35 } 36 37 func init() { 38 http.HandleFunc("/io", serveSVGProfile(pprofIO)) 39 http.HandleFunc("/block", serveSVGProfile(pprofBlock)) 40 http.HandleFunc("/syscall", serveSVGProfile(pprofSyscall)) 41 http.HandleFunc("/sched", serveSVGProfile(pprofSched)) 42 } 43 44 // Record represents one entry in pprof-like profiles. 45 type Record struct { 46 stk []*trace.Frame 47 n uint64 48 time int64 49 } 50 51 // pprofMatchingGoroutines parses the goroutine type id string (i.e. pc) 52 // and returns the ids of goroutines of the matching type. 53 // If the id string is empty, returns nil without an error. 54 func pprofMatchingGoroutines(id string, events []*trace.Event) (map[uint64]bool, error) { 55 if id == "" { 56 return nil, nil 57 } 58 pc, err := strconv.ParseUint(id, 10, 64) // id is string 59 if err != nil { 60 return nil, fmt.Errorf("invalid goroutine type: %v", id) 61 } 62 analyzeGoroutines(events) 63 var res map[uint64]bool 64 for _, g := range gs { 65 if g.PC != pc { 66 continue 67 } 68 if res == nil { 69 res = make(map[uint64]bool) 70 } 71 res[g.ID] = true 72 } 73 if len(res) == 0 && id != "" { 74 return nil, fmt.Errorf("failed to find matching goroutines for id: %s", id) 75 } 76 return res, nil 77 } 78 79 // pprofIO generates IO pprof-like profile (time spent in IO wait, 80 // currently only network blocking event). 81 func pprofIO(w io.Writer, id string) error { 82 events, err := parseEvents() 83 if err != nil { 84 return err 85 } 86 goroutines, err := pprofMatchingGoroutines(id, events) 87 if err != nil { 88 return err 89 } 90 91 prof := make(map[uint64]Record) 92 for _, ev := range events { 93 if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { 94 continue 95 } 96 if goroutines != nil && !goroutines[ev.G] { 97 continue 98 } 99 rec := prof[ev.StkID] 100 rec.stk = ev.Stk 101 rec.n++ 102 rec.time += ev.Link.Ts - ev.Ts 103 prof[ev.StkID] = rec 104 } 105 return buildProfile(prof).Write(w) 106 } 107 108 // pprofBlock generates blocking pprof-like profile (time spent blocked on synchronization primitives). 109 func pprofBlock(w io.Writer, id string) error { 110 events, err := parseEvents() 111 if err != nil { 112 return err 113 } 114 goroutines, err := pprofMatchingGoroutines(id, events) 115 if err != nil { 116 return err 117 } 118 119 prof := make(map[uint64]Record) 120 for _, ev := range events { 121 switch ev.Type { 122 case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect, 123 trace.EvGoBlockSync, trace.EvGoBlockCond, trace.EvGoBlockGC: 124 // TODO(hyangah): figure out why EvGoBlockGC should be here. 125 // EvGoBlockGC indicates the goroutine blocks on GC assist, not 126 // on synchronization primitives. 127 default: 128 continue 129 } 130 if ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { 131 continue 132 } 133 if goroutines != nil && !goroutines[ev.G] { 134 continue 135 } 136 rec := prof[ev.StkID] 137 rec.stk = ev.Stk 138 rec.n++ 139 rec.time += ev.Link.Ts - ev.Ts 140 prof[ev.StkID] = rec 141 } 142 return buildProfile(prof).Write(w) 143 } 144 145 // pprofSyscall generates syscall pprof-like profile (time spent blocked in syscalls). 146 func pprofSyscall(w io.Writer, id string) error { 147 148 events, err := parseEvents() 149 if err != nil { 150 return err 151 } 152 goroutines, err := pprofMatchingGoroutines(id, events) 153 if err != nil { 154 return err 155 } 156 157 prof := make(map[uint64]Record) 158 for _, ev := range events { 159 if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { 160 continue 161 } 162 if goroutines != nil && !goroutines[ev.G] { 163 continue 164 } 165 rec := prof[ev.StkID] 166 rec.stk = ev.Stk 167 rec.n++ 168 rec.time += ev.Link.Ts - ev.Ts 169 prof[ev.StkID] = rec 170 } 171 return buildProfile(prof).Write(w) 172 } 173 174 // pprofSched generates scheduler latency pprof-like profile 175 // (time between a goroutine become runnable and actually scheduled for execution). 176 func pprofSched(w io.Writer, id string) error { 177 events, err := parseEvents() 178 if err != nil { 179 return err 180 } 181 goroutines, err := pprofMatchingGoroutines(id, events) 182 if err != nil { 183 return err 184 } 185 186 prof := make(map[uint64]Record) 187 for _, ev := range events { 188 if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) || 189 ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { 190 continue 191 } 192 if goroutines != nil && !goroutines[ev.G] { 193 continue 194 } 195 rec := prof[ev.StkID] 196 rec.stk = ev.Stk 197 rec.n++ 198 rec.time += ev.Link.Ts - ev.Ts 199 prof[ev.StkID] = rec 200 } 201 return buildProfile(prof).Write(w) 202 } 203 204 // serveSVGProfile serves pprof-like profile generated by prof as svg. 205 func serveSVGProfile(prof func(w io.Writer, id string) error) http.HandlerFunc { 206 return func(w http.ResponseWriter, r *http.Request) { 207 208 if r.FormValue("raw") != "" { 209 w.Header().Set("Content-Type", "application/octet-stream") 210 if err := prof(w, r.FormValue("id")); err != nil { 211 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 212 w.Header().Set("X-Go-Pprof", "1") 213 http.Error(w, fmt.Sprintf("failed to get profile: %v", err), http.StatusInternalServerError) 214 return 215 } 216 return 217 } 218 219 blockf, err := ioutil.TempFile("", "block") 220 if err != nil { 221 http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError) 222 return 223 } 224 defer func() { 225 blockf.Close() 226 os.Remove(blockf.Name()) 227 }() 228 blockb := bufio.NewWriter(blockf) 229 if err := prof(blockb, r.FormValue("id")); err != nil { 230 http.Error(w, fmt.Sprintf("failed to generate profile: %v", err), http.StatusInternalServerError) 231 return 232 } 233 if err := blockb.Flush(); err != nil { 234 http.Error(w, fmt.Sprintf("failed to flush temp file: %v", err), http.StatusInternalServerError) 235 return 236 } 237 if err := blockf.Close(); err != nil { 238 http.Error(w, fmt.Sprintf("failed to close temp file: %v", err), http.StatusInternalServerError) 239 return 240 } 241 svgFilename := blockf.Name() + ".svg" 242 if output, err := exec.Command(goCmd(), "tool", "pprof", "-svg", "-output", svgFilename, blockf.Name()).CombinedOutput(); err != nil { 243 http.Error(w, fmt.Sprintf("failed to execute go tool pprof: %v\n%s", err, output), http.StatusInternalServerError) 244 return 245 } 246 defer os.Remove(svgFilename) 247 w.Header().Set("Content-Type", "image/svg+xml") 248 http.ServeFile(w, r, svgFilename) 249 } 250 } 251 252 func buildProfile(prof map[uint64]Record) *profile.Profile { 253 p := &profile.Profile{ 254 PeriodType: &profile.ValueType{Type: "trace", Unit: "count"}, 255 Period: 1, 256 SampleType: []*profile.ValueType{ 257 {Type: "contentions", Unit: "count"}, 258 {Type: "delay", Unit: "nanoseconds"}, 259 }, 260 } 261 locs := make(map[uint64]*profile.Location) 262 funcs := make(map[string]*profile.Function) 263 for _, rec := range prof { 264 var sloc []*profile.Location 265 for _, frame := range rec.stk { 266 loc := locs[frame.PC] 267 if loc == nil { 268 fn := funcs[frame.File+frame.Fn] 269 if fn == nil { 270 fn = &profile.Function{ 271 ID: uint64(len(p.Function) + 1), 272 Name: frame.Fn, 273 SystemName: frame.Fn, 274 Filename: frame.File, 275 } 276 p.Function = append(p.Function, fn) 277 funcs[frame.File+frame.Fn] = fn 278 } 279 loc = &profile.Location{ 280 ID: uint64(len(p.Location) + 1), 281 Address: frame.PC, 282 Line: []profile.Line{ 283 profile.Line{ 284 Function: fn, 285 Line: int64(frame.Line), 286 }, 287 }, 288 } 289 p.Location = append(p.Location, loc) 290 locs[frame.PC] = loc 291 } 292 sloc = append(sloc, loc) 293 } 294 p.Sample = append(p.Sample, &profile.Sample{ 295 Value: []int64{int64(rec.n), rec.time}, 296 Location: sloc, 297 }) 298 } 299 return p 300 }