|  | // Copyright 2014 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | // Serving of pprof-like profiles. | 
|  |  | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "bufio" | 
|  | "fmt" | 
|  | "internal/trace" | 
|  | "io" | 
|  | "io/ioutil" | 
|  | "net/http" | 
|  | "os" | 
|  | "os/exec" | 
|  | "path/filepath" | 
|  | "runtime" | 
|  |  | 
|  | "github.com/google/pprof/profile" | 
|  | ) | 
|  |  | 
|  | func goCmd() string { | 
|  | var exeSuffix string | 
|  | if runtime.GOOS == "windows" { | 
|  | exeSuffix = ".exe" | 
|  | } | 
|  | path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix) | 
|  | if _, err := os.Stat(path); err == nil { | 
|  | return path | 
|  | } | 
|  | return "go" | 
|  | } | 
|  |  | 
|  | func init() { | 
|  | http.HandleFunc("/io", serveSVGProfile(pprofIO)) | 
|  | http.HandleFunc("/block", serveSVGProfile(pprofBlock)) | 
|  | http.HandleFunc("/syscall", serveSVGProfile(pprofSyscall)) | 
|  | http.HandleFunc("/sched", serveSVGProfile(pprofSched)) | 
|  | } | 
|  |  | 
|  | // Record represents one entry in pprof-like profiles. | 
|  | type Record struct { | 
|  | stk  []*trace.Frame | 
|  | n    uint64 | 
|  | time int64 | 
|  | } | 
|  |  | 
|  | // pprofIO generates IO pprof-like profile (time spent in IO wait). | 
|  | func pprofIO(w io.Writer) error { | 
|  | events, err := parseEvents() | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | prof := make(map[uint64]Record) | 
|  | for _, ev := range events { | 
|  | if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { | 
|  | continue | 
|  | } | 
|  | rec := prof[ev.StkID] | 
|  | rec.stk = ev.Stk | 
|  | rec.n++ | 
|  | rec.time += ev.Link.Ts - ev.Ts | 
|  | prof[ev.StkID] = rec | 
|  | } | 
|  | return buildProfile(prof).Write(w) | 
|  | } | 
|  |  | 
|  | // pprofBlock generates blocking pprof-like profile (time spent blocked on synchronization primitives). | 
|  | func pprofBlock(w io.Writer) error { | 
|  | events, err := parseEvents() | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | prof := make(map[uint64]Record) | 
|  | for _, ev := range events { | 
|  | switch ev.Type { | 
|  | case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect, | 
|  | trace.EvGoBlockSync, trace.EvGoBlockCond, trace.EvGoBlockGC: | 
|  | default: | 
|  | continue | 
|  | } | 
|  | if ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { | 
|  | continue | 
|  | } | 
|  | rec := prof[ev.StkID] | 
|  | rec.stk = ev.Stk | 
|  | rec.n++ | 
|  | rec.time += ev.Link.Ts - ev.Ts | 
|  | prof[ev.StkID] = rec | 
|  | } | 
|  | return buildProfile(prof).Write(w) | 
|  | } | 
|  |  | 
|  | // pprofSyscall generates syscall pprof-like profile (time spent blocked in syscalls). | 
|  | func pprofSyscall(w io.Writer) error { | 
|  | events, err := parseEvents() | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | prof := make(map[uint64]Record) | 
|  | for _, ev := range events { | 
|  | if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { | 
|  | continue | 
|  | } | 
|  | rec := prof[ev.StkID] | 
|  | rec.stk = ev.Stk | 
|  | rec.n++ | 
|  | rec.time += ev.Link.Ts - ev.Ts | 
|  | prof[ev.StkID] = rec | 
|  | } | 
|  | return buildProfile(prof).Write(w) | 
|  | } | 
|  |  | 
|  | // pprofSched generates scheduler latency pprof-like profile | 
|  | // (time between a goroutine become runnable and actually scheduled for execution). | 
|  | func pprofSched(w io.Writer) error { | 
|  | events, err := parseEvents() | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | prof := make(map[uint64]Record) | 
|  | for _, ev := range events { | 
|  | if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) || | 
|  | ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { | 
|  | continue | 
|  | } | 
|  | rec := prof[ev.StkID] | 
|  | rec.stk = ev.Stk | 
|  | rec.n++ | 
|  | rec.time += ev.Link.Ts - ev.Ts | 
|  | prof[ev.StkID] = rec | 
|  | } | 
|  | return buildProfile(prof).Write(w) | 
|  | } | 
|  |  | 
|  | // serveSVGProfile serves pprof-like profile generated by prof as svg. | 
|  | func serveSVGProfile(prof func(w io.Writer) error) http.HandlerFunc { | 
|  | return func(w http.ResponseWriter, r *http.Request) { | 
|  | blockf, err := ioutil.TempFile("", "block") | 
|  | if err != nil { | 
|  | http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError) | 
|  | return | 
|  | } | 
|  | defer func() { | 
|  | blockf.Close() | 
|  | os.Remove(blockf.Name()) | 
|  | }() | 
|  | blockb := bufio.NewWriter(blockf) | 
|  | if err := prof(blockb); err != nil { | 
|  | http.Error(w, fmt.Sprintf("failed to generate profile: %v", err), http.StatusInternalServerError) | 
|  | return | 
|  | } | 
|  | if err := blockb.Flush(); err != nil { | 
|  | http.Error(w, fmt.Sprintf("failed to flush temp file: %v", err), http.StatusInternalServerError) | 
|  | return | 
|  | } | 
|  | if err := blockf.Close(); err != nil { | 
|  | http.Error(w, fmt.Sprintf("failed to close temp file: %v", err), http.StatusInternalServerError) | 
|  | return | 
|  | } | 
|  | svgFilename := blockf.Name() + ".svg" | 
|  | if output, err := exec.Command(goCmd(), "tool", "pprof", "-svg", "-output", svgFilename, blockf.Name()).CombinedOutput(); err != nil { | 
|  | http.Error(w, fmt.Sprintf("failed to execute go tool pprof: %v\n%s", err, output), http.StatusInternalServerError) | 
|  | return | 
|  | } | 
|  | defer os.Remove(svgFilename) | 
|  | w.Header().Set("Content-Type", "image/svg+xml") | 
|  | http.ServeFile(w, r, svgFilename) | 
|  | } | 
|  | } | 
|  |  | 
|  | func buildProfile(prof map[uint64]Record) *profile.Profile { | 
|  | p := &profile.Profile{ | 
|  | PeriodType: &profile.ValueType{Type: "trace", Unit: "count"}, | 
|  | Period:     1, | 
|  | SampleType: []*profile.ValueType{ | 
|  | {Type: "contentions", Unit: "count"}, | 
|  | {Type: "delay", Unit: "nanoseconds"}, | 
|  | }, | 
|  | } | 
|  | locs := make(map[uint64]*profile.Location) | 
|  | funcs := make(map[string]*profile.Function) | 
|  | for _, rec := range prof { | 
|  | var sloc []*profile.Location | 
|  | for _, frame := range rec.stk { | 
|  | loc := locs[frame.PC] | 
|  | if loc == nil { | 
|  | fn := funcs[frame.File+frame.Fn] | 
|  | if fn == nil { | 
|  | fn = &profile.Function{ | 
|  | ID:         uint64(len(p.Function) + 1), | 
|  | Name:       frame.Fn, | 
|  | SystemName: frame.Fn, | 
|  | Filename:   frame.File, | 
|  | } | 
|  | p.Function = append(p.Function, fn) | 
|  | funcs[frame.File+frame.Fn] = fn | 
|  | } | 
|  | loc = &profile.Location{ | 
|  | ID:      uint64(len(p.Location) + 1), | 
|  | Address: frame.PC, | 
|  | Line: []profile.Line{ | 
|  | profile.Line{ | 
|  | Function: fn, | 
|  | Line:     int64(frame.Line), | 
|  | }, | 
|  | }, | 
|  | } | 
|  | p.Location = append(p.Location, loc) | 
|  | locs[frame.PC] = loc | 
|  | } | 
|  | sloc = append(sloc, loc) | 
|  | } | 
|  | p.Sample = append(p.Sample, &profile.Sample{ | 
|  | Value:    []int64{int64(rec.n), rec.time}, | 
|  | Location: sloc, | 
|  | }) | 
|  | } | 
|  | return p | 
|  | } |