| // 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) { |
| 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) |
| } |