| // 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/ioutil" | |
| "net/http" | |
| "os" | |
| "os/exec" | |
| ) | |
| func init() { | |
| http.HandleFunc("/io", httpIO) | |
| http.HandleFunc("/block", httpBlock) | |
| http.HandleFunc("/syscall", httpSyscall) | |
| http.HandleFunc("/sched", httpSched) | |
| } | |
| // Record represents one entry in pprof-like profiles. | |
| type Record struct { | |
| stk []*trace.Frame | |
| n uint64 | |
| time int64 | |
| } | |
| // httpIO serves IO pprof-like profile (time spent in IO wait). | |
| func httpIO(w http.ResponseWriter, r *http.Request) { | |
| events, err := parseEvents() | |
| if err != nil { | |
| http.Error(w, err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| 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 | |
| } | |
| serveSVGProfile(w, r, prof) | |
| } | |
| // httpBlock serves blocking pprof-like profile (time spent blocked on synchronization primitives). | |
| func httpBlock(w http.ResponseWriter, r *http.Request) { | |
| events, err := parseEvents() | |
| if err != nil { | |
| http.Error(w, err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| prof := make(map[uint64]Record) | |
| for _, ev := range events { | |
| switch ev.Type { | |
| case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect, | |
| trace.EvGoBlockSync, trace.EvGoBlockCond: | |
| 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 | |
| } | |
| serveSVGProfile(w, r, prof) | |
| } | |
| // httpSyscall serves syscall pprof-like profile (time spent blocked in syscalls). | |
| func httpSyscall(w http.ResponseWriter, r *http.Request) { | |
| events, err := parseEvents() | |
| if err != nil { | |
| http.Error(w, err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| 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 | |
| } | |
| serveSVGProfile(w, r, prof) | |
| } | |
| // httpSched serves scheduler latency pprof-like profile | |
| // (time between a goroutine become runnable and actually scheduled for execution). | |
| func httpSched(w http.ResponseWriter, r *http.Request) { | |
| events, err := parseEvents() | |
| if err != nil { | |
| http.Error(w, err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| 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 | |
| } | |
| serveSVGProfile(w, r, prof) | |
| } | |
| // generateSVGProfile generates pprof-like profile stored in prof and writes in to w. | |
| func serveSVGProfile(w http.ResponseWriter, r *http.Request, prof map[uint64]Record) { | |
| if len(prof) == 0 { | |
| http.Error(w, "The profile is empty", http.StatusNotFound) | |
| return | |
| } | |
| blockf, err := ioutil.TempFile("", "block") | |
| if err != nil { | |
| http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError) | |
| return | |
| } | |
| defer os.Remove(blockf.Name()) | |
| blockb := bufio.NewWriter(blockf) | |
| fmt.Fprintf(blockb, "--- contention:\ncycles/second=1000000000\n") | |
| for _, rec := range prof { | |
| fmt.Fprintf(blockb, "%v %v @", rec.time, rec.n) | |
| for _, f := range rec.stk { | |
| fmt.Fprintf(blockb, " 0x%x", f.PC) | |
| } | |
| fmt.Fprintf(blockb, "\n") | |
| } | |
| err = blockb.Flush() | |
| if err != nil { | |
| http.Error(w, fmt.Sprintf("failed to flush temp file: %v", err), http.StatusInternalServerError) | |
| return | |
| } | |
| err = blockf.Close() | |
| if err != nil { | |
| http.Error(w, fmt.Sprintf("failed to close temp file: %v", err), http.StatusInternalServerError) | |
| return | |
| } | |
| svgFilename := blockf.Name() + ".svg" | |
| _, err = exec.Command("go", "tool", "pprof", "-svg", "-output", svgFilename, programBinary, blockf.Name()).CombinedOutput() | |
| if err != nil { | |
| http.Error(w, fmt.Sprintf("failed to execute go tool pprof: %v", err), http.StatusInternalServerError) | |
| return | |
| } | |
| defer os.Remove(svgFilename) | |
| w.Header().Set("Content-Type", "image/svg+xml") | |
| http.ServeFile(w, r, svgFilename) | |
| } |