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