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