github.com/4ad/go@v0.0.0-20161219182952-69a12818b605/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  	"cmd/internal/pprof/profile"
    12  	"fmt"
    13  	"internal/trace"
    14  	"io/ioutil"
    15  	"net/http"
    16  	"os"
    17  	"os/exec"
    18  )
    19  
    20  func init() {
    21  	http.HandleFunc("/io", httpIO)
    22  	http.HandleFunc("/block", httpBlock)
    23  	http.HandleFunc("/syscall", httpSyscall)
    24  	http.HandleFunc("/sched", httpSched)
    25  }
    26  
    27  // Record represents one entry in pprof-like profiles.
    28  type Record struct {
    29  	stk  []*trace.Frame
    30  	n    uint64
    31  	time int64
    32  }
    33  
    34  // httpIO serves IO pprof-like profile (time spent in IO wait).
    35  func httpIO(w http.ResponseWriter, r *http.Request) {
    36  	events, err := parseEvents()
    37  	if err != nil {
    38  		http.Error(w, err.Error(), http.StatusInternalServerError)
    39  		return
    40  	}
    41  	prof := make(map[uint64]Record)
    42  	for _, ev := range events {
    43  		if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
    44  			continue
    45  		}
    46  		rec := prof[ev.StkID]
    47  		rec.stk = ev.Stk
    48  		rec.n++
    49  		rec.time += ev.Link.Ts - ev.Ts
    50  		prof[ev.StkID] = rec
    51  	}
    52  	serveSVGProfile(w, r, prof)
    53  }
    54  
    55  // httpBlock serves blocking pprof-like profile (time spent blocked on synchronization primitives).
    56  func httpBlock(w http.ResponseWriter, r *http.Request) {
    57  	events, err := parseEvents()
    58  	if err != nil {
    59  		http.Error(w, err.Error(), http.StatusInternalServerError)
    60  		return
    61  	}
    62  	prof := make(map[uint64]Record)
    63  	for _, ev := range events {
    64  		switch ev.Type {
    65  		case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect,
    66  			trace.EvGoBlockSync, trace.EvGoBlockCond:
    67  		default:
    68  			continue
    69  		}
    70  		if ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
    71  			continue
    72  		}
    73  		rec := prof[ev.StkID]
    74  		rec.stk = ev.Stk
    75  		rec.n++
    76  		rec.time += ev.Link.Ts - ev.Ts
    77  		prof[ev.StkID] = rec
    78  	}
    79  	serveSVGProfile(w, r, prof)
    80  }
    81  
    82  // httpSyscall serves syscall pprof-like profile (time spent blocked in syscalls).
    83  func httpSyscall(w http.ResponseWriter, r *http.Request) {
    84  	events, err := parseEvents()
    85  	if err != nil {
    86  		http.Error(w, err.Error(), http.StatusInternalServerError)
    87  		return
    88  	}
    89  	prof := make(map[uint64]Record)
    90  	for _, ev := range events {
    91  		if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
    92  			continue
    93  		}
    94  		rec := prof[ev.StkID]
    95  		rec.stk = ev.Stk
    96  		rec.n++
    97  		rec.time += ev.Link.Ts - ev.Ts
    98  		prof[ev.StkID] = rec
    99  	}
   100  	serveSVGProfile(w, r, prof)
   101  }
   102  
   103  // httpSched serves scheduler latency pprof-like profile
   104  // (time between a goroutine become runnable and actually scheduled for execution).
   105  func httpSched(w http.ResponseWriter, r *http.Request) {
   106  	events, err := parseEvents()
   107  	if err != nil {
   108  		http.Error(w, err.Error(), http.StatusInternalServerError)
   109  		return
   110  	}
   111  	prof := make(map[uint64]Record)
   112  	for _, ev := range events {
   113  		if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) ||
   114  			ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
   115  			continue
   116  		}
   117  		rec := prof[ev.StkID]
   118  		rec.stk = ev.Stk
   119  		rec.n++
   120  		rec.time += ev.Link.Ts - ev.Ts
   121  		prof[ev.StkID] = rec
   122  	}
   123  	serveSVGProfile(w, r, prof)
   124  }
   125  
   126  // generateSVGProfile generates pprof-like profile stored in prof and writes in to w.
   127  func serveSVGProfile(w http.ResponseWriter, r *http.Request, prof map[uint64]Record) {
   128  	if len(prof) == 0 {
   129  		http.Error(w, "The profile is empty", http.StatusNotFound)
   130  		return
   131  	}
   132  	blockf, err := ioutil.TempFile("", "block")
   133  	if err != nil {
   134  		http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError)
   135  		return
   136  	}
   137  	defer func() {
   138  		blockf.Close()
   139  		os.Remove(blockf.Name())
   140  	}()
   141  	blockb := bufio.NewWriter(blockf)
   142  	if err := buildProfile(prof).Write(blockb); err != nil {
   143  		http.Error(w, fmt.Sprintf("failed to write profile: %v", err), http.StatusInternalServerError)
   144  		return
   145  	}
   146  	if err := blockb.Flush(); err != nil {
   147  		http.Error(w, fmt.Sprintf("failed to flush temp file: %v", err), http.StatusInternalServerError)
   148  		return
   149  	}
   150  	if err := blockf.Close(); err != nil {
   151  		http.Error(w, fmt.Sprintf("failed to close temp file: %v", err), http.StatusInternalServerError)
   152  		return
   153  	}
   154  	svgFilename := blockf.Name() + ".svg"
   155  	if output, err := exec.Command("go", "tool", "pprof", "-svg", "-output", svgFilename, blockf.Name()).CombinedOutput(); err != nil {
   156  		http.Error(w, fmt.Sprintf("failed to execute go tool pprof: %v\n%s", err, output), http.StatusInternalServerError)
   157  		return
   158  	}
   159  	defer os.Remove(svgFilename)
   160  	w.Header().Set("Content-Type", "image/svg+xml")
   161  	http.ServeFile(w, r, svgFilename)
   162  }
   163  
   164  func buildProfile(prof map[uint64]Record) *profile.Profile {
   165  	p := &profile.Profile{
   166  		PeriodType: &profile.ValueType{Type: "trace", Unit: "count"},
   167  		Period:     1,
   168  		SampleType: []*profile.ValueType{
   169  			{Type: "contentions", Unit: "count"},
   170  			{Type: "delay", Unit: "nanoseconds"},
   171  		},
   172  	}
   173  	locs := make(map[uint64]*profile.Location)
   174  	funcs := make(map[string]*profile.Function)
   175  	for _, rec := range prof {
   176  		var sloc []*profile.Location
   177  		for _, frame := range rec.stk {
   178  			loc := locs[frame.PC]
   179  			if loc == nil {
   180  				fn := funcs[frame.File+frame.Fn]
   181  				if fn == nil {
   182  					fn = &profile.Function{
   183  						ID:         uint64(len(p.Function) + 1),
   184  						Name:       frame.Fn,
   185  						SystemName: frame.Fn,
   186  						Filename:   frame.File,
   187  					}
   188  					p.Function = append(p.Function, fn)
   189  					funcs[frame.File+frame.Fn] = fn
   190  				}
   191  				loc = &profile.Location{
   192  					ID:      uint64(len(p.Location) + 1),
   193  					Address: frame.PC,
   194  					Line: []profile.Line{
   195  						profile.Line{
   196  							Function: fn,
   197  							Line:     int64(frame.Line),
   198  						},
   199  					},
   200  				}
   201  				p.Location = append(p.Location, loc)
   202  				locs[frame.PC] = loc
   203  			}
   204  			sloc = append(sloc, loc)
   205  		}
   206  		p.Sample = append(p.Sample, &profile.Sample{
   207  			Value:    []int64{int64(rec.n), rec.time},
   208  			Location: sloc,
   209  		})
   210  	}
   211  	return p
   212  }