github.com/c9s/go@v0.0.0-20180120015821-984e81f64e0c/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  	"strconv"
    21  
    22  	"github.com/google/pprof/profile"
    23  )
    24  
    25  func goCmd() string {
    26  	var exeSuffix string
    27  	if runtime.GOOS == "windows" {
    28  		exeSuffix = ".exe"
    29  	}
    30  	path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
    31  	if _, err := os.Stat(path); err == nil {
    32  		return path
    33  	}
    34  	return "go"
    35  }
    36  
    37  func init() {
    38  	http.HandleFunc("/io", serveSVGProfile(pprofIO))
    39  	http.HandleFunc("/block", serveSVGProfile(pprofBlock))
    40  	http.HandleFunc("/syscall", serveSVGProfile(pprofSyscall))
    41  	http.HandleFunc("/sched", serveSVGProfile(pprofSched))
    42  }
    43  
    44  // Record represents one entry in pprof-like profiles.
    45  type Record struct {
    46  	stk  []*trace.Frame
    47  	n    uint64
    48  	time int64
    49  }
    50  
    51  // pprofMatchingGoroutines parses the goroutine type id string (i.e. pc)
    52  // and returns the ids of goroutines of the matching type.
    53  // If the id string is empty, returns nil without an error.
    54  func pprofMatchingGoroutines(id string, events []*trace.Event) (map[uint64]bool, error) {
    55  	if id == "" {
    56  		return nil, nil
    57  	}
    58  	pc, err := strconv.ParseUint(id, 10, 64) // id is string
    59  	if err != nil {
    60  		return nil, fmt.Errorf("invalid goroutine type: %v", id)
    61  	}
    62  	analyzeGoroutines(events)
    63  	var res map[uint64]bool
    64  	for _, g := range gs {
    65  		if g.PC != pc {
    66  			continue
    67  		}
    68  		if res == nil {
    69  			res = make(map[uint64]bool)
    70  		}
    71  		res[g.ID] = true
    72  	}
    73  	if len(res) == 0 && id != "" {
    74  		return nil, fmt.Errorf("failed to find matching goroutines for id: %s", id)
    75  	}
    76  	return res, nil
    77  }
    78  
    79  // pprofIO generates IO pprof-like profile (time spent in IO wait,
    80  // currently only network blocking event).
    81  func pprofIO(w io.Writer, id string) error {
    82  	events, err := parseEvents()
    83  	if err != nil {
    84  		return err
    85  	}
    86  	goroutines, err := pprofMatchingGoroutines(id, events)
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	prof := make(map[uint64]Record)
    92  	for _, ev := range events {
    93  		if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
    94  			continue
    95  		}
    96  		if goroutines != nil && !goroutines[ev.G] {
    97  			continue
    98  		}
    99  		rec := prof[ev.StkID]
   100  		rec.stk = ev.Stk
   101  		rec.n++
   102  		rec.time += ev.Link.Ts - ev.Ts
   103  		prof[ev.StkID] = rec
   104  	}
   105  	return buildProfile(prof).Write(w)
   106  }
   107  
   108  // pprofBlock generates blocking pprof-like profile (time spent blocked on synchronization primitives).
   109  func pprofBlock(w io.Writer, id string) error {
   110  	events, err := parseEvents()
   111  	if err != nil {
   112  		return err
   113  	}
   114  	goroutines, err := pprofMatchingGoroutines(id, events)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	prof := make(map[uint64]Record)
   120  	for _, ev := range events {
   121  		switch ev.Type {
   122  		case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect,
   123  			trace.EvGoBlockSync, trace.EvGoBlockCond, trace.EvGoBlockGC:
   124  			// TODO(hyangah): figure out why EvGoBlockGC should be here.
   125  			// EvGoBlockGC indicates the goroutine blocks on GC assist, not
   126  			// on synchronization primitives.
   127  		default:
   128  			continue
   129  		}
   130  		if ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
   131  			continue
   132  		}
   133  		if goroutines != nil && !goroutines[ev.G] {
   134  			continue
   135  		}
   136  		rec := prof[ev.StkID]
   137  		rec.stk = ev.Stk
   138  		rec.n++
   139  		rec.time += ev.Link.Ts - ev.Ts
   140  		prof[ev.StkID] = rec
   141  	}
   142  	return buildProfile(prof).Write(w)
   143  }
   144  
   145  // pprofSyscall generates syscall pprof-like profile (time spent blocked in syscalls).
   146  func pprofSyscall(w io.Writer, id string) error {
   147  
   148  	events, err := parseEvents()
   149  	if err != nil {
   150  		return err
   151  	}
   152  	goroutines, err := pprofMatchingGoroutines(id, events)
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	prof := make(map[uint64]Record)
   158  	for _, ev := range events {
   159  		if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
   160  			continue
   161  		}
   162  		if goroutines != nil && !goroutines[ev.G] {
   163  			continue
   164  		}
   165  		rec := prof[ev.StkID]
   166  		rec.stk = ev.Stk
   167  		rec.n++
   168  		rec.time += ev.Link.Ts - ev.Ts
   169  		prof[ev.StkID] = rec
   170  	}
   171  	return buildProfile(prof).Write(w)
   172  }
   173  
   174  // pprofSched generates scheduler latency pprof-like profile
   175  // (time between a goroutine become runnable and actually scheduled for execution).
   176  func pprofSched(w io.Writer, id string) error {
   177  	events, err := parseEvents()
   178  	if err != nil {
   179  		return err
   180  	}
   181  	goroutines, err := pprofMatchingGoroutines(id, events)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	prof := make(map[uint64]Record)
   187  	for _, ev := range events {
   188  		if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) ||
   189  			ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
   190  			continue
   191  		}
   192  		if goroutines != nil && !goroutines[ev.G] {
   193  			continue
   194  		}
   195  		rec := prof[ev.StkID]
   196  		rec.stk = ev.Stk
   197  		rec.n++
   198  		rec.time += ev.Link.Ts - ev.Ts
   199  		prof[ev.StkID] = rec
   200  	}
   201  	return buildProfile(prof).Write(w)
   202  }
   203  
   204  // serveSVGProfile serves pprof-like profile generated by prof as svg.
   205  func serveSVGProfile(prof func(w io.Writer, id string) error) http.HandlerFunc {
   206  	return func(w http.ResponseWriter, r *http.Request) {
   207  
   208  		if r.FormValue("raw") != "" {
   209  			w.Header().Set("Content-Type", "application/octet-stream")
   210  			if err := prof(w, r.FormValue("id")); err != nil {
   211  				w.Header().Set("Content-Type", "text/plain; charset=utf-8")
   212  				w.Header().Set("X-Go-Pprof", "1")
   213  				http.Error(w, fmt.Sprintf("failed to get profile: %v", err), http.StatusInternalServerError)
   214  				return
   215  			}
   216  			return
   217  		}
   218  
   219  		blockf, err := ioutil.TempFile("", "block")
   220  		if err != nil {
   221  			http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError)
   222  			return
   223  		}
   224  		defer func() {
   225  			blockf.Close()
   226  			os.Remove(blockf.Name())
   227  		}()
   228  		blockb := bufio.NewWriter(blockf)
   229  		if err := prof(blockb, r.FormValue("id")); err != nil {
   230  			http.Error(w, fmt.Sprintf("failed to generate profile: %v", err), http.StatusInternalServerError)
   231  			return
   232  		}
   233  		if err := blockb.Flush(); err != nil {
   234  			http.Error(w, fmt.Sprintf("failed to flush temp file: %v", err), http.StatusInternalServerError)
   235  			return
   236  		}
   237  		if err := blockf.Close(); err != nil {
   238  			http.Error(w, fmt.Sprintf("failed to close temp file: %v", err), http.StatusInternalServerError)
   239  			return
   240  		}
   241  		svgFilename := blockf.Name() + ".svg"
   242  		if output, err := exec.Command(goCmd(), "tool", "pprof", "-svg", "-output", svgFilename, blockf.Name()).CombinedOutput(); err != nil {
   243  			http.Error(w, fmt.Sprintf("failed to execute go tool pprof: %v\n%s", err, output), http.StatusInternalServerError)
   244  			return
   245  		}
   246  		defer os.Remove(svgFilename)
   247  		w.Header().Set("Content-Type", "image/svg+xml")
   248  		http.ServeFile(w, r, svgFilename)
   249  	}
   250  }
   251  
   252  func buildProfile(prof map[uint64]Record) *profile.Profile {
   253  	p := &profile.Profile{
   254  		PeriodType: &profile.ValueType{Type: "trace", Unit: "count"},
   255  		Period:     1,
   256  		SampleType: []*profile.ValueType{
   257  			{Type: "contentions", Unit: "count"},
   258  			{Type: "delay", Unit: "nanoseconds"},
   259  		},
   260  	}
   261  	locs := make(map[uint64]*profile.Location)
   262  	funcs := make(map[string]*profile.Function)
   263  	for _, rec := range prof {
   264  		var sloc []*profile.Location
   265  		for _, frame := range rec.stk {
   266  			loc := locs[frame.PC]
   267  			if loc == nil {
   268  				fn := funcs[frame.File+frame.Fn]
   269  				if fn == nil {
   270  					fn = &profile.Function{
   271  						ID:         uint64(len(p.Function) + 1),
   272  						Name:       frame.Fn,
   273  						SystemName: frame.Fn,
   274  						Filename:   frame.File,
   275  					}
   276  					p.Function = append(p.Function, fn)
   277  					funcs[frame.File+frame.Fn] = fn
   278  				}
   279  				loc = &profile.Location{
   280  					ID:      uint64(len(p.Location) + 1),
   281  					Address: frame.PC,
   282  					Line: []profile.Line{
   283  						profile.Line{
   284  							Function: fn,
   285  							Line:     int64(frame.Line),
   286  						},
   287  					},
   288  				}
   289  				p.Location = append(p.Location, loc)
   290  				locs[frame.PC] = loc
   291  			}
   292  			sloc = append(sloc, loc)
   293  		}
   294  		p.Sample = append(p.Sample, &profile.Sample{
   295  			Value:    []int64{int64(rec.n), rec.time},
   296  			Location: sloc,
   297  		})
   298  	}
   299  	return p
   300  }