blob: 3722b37ab8da871ef14c62cd8afa70000e3f85ff [file] [log] [blame]
// 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 (
"fmt"
"internal/trace"
"internal/trace/traceviewer"
"net/http"
"sort"
"strconv"
"time"
)
func init() {
http.HandleFunc("/io", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofIO)))
http.HandleFunc("/block", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofBlock)))
http.HandleFunc("/syscall", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofSyscall)))
http.HandleFunc("/sched", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofSched)))
http.HandleFunc("/regionio", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofIO)))
http.HandleFunc("/regionblock", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofBlock)))
http.HandleFunc("/regionsyscall", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofSyscall)))
http.HandleFunc("/regionsched", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofSched)))
}
// interval represents a time interval in the trace.
type interval struct {
begin, end int64 // nanoseconds.
}
func pprofByGoroutine(compute computePprofFunc) traceviewer.ProfileFunc {
return func(r *http.Request) ([]traceviewer.ProfileRecord, error) {
id := r.FormValue("id")
events, err := parseEvents()
if err != nil {
return nil, err
}
gToIntervals, err := pprofMatchingGoroutines(id, events)
if err != nil {
return nil, err
}
return compute(gToIntervals, events)
}
}
func pprofByRegion(compute computePprofFunc) traceviewer.ProfileFunc {
return func(r *http.Request) ([]traceviewer.ProfileRecord, error) {
filter, err := newRegionFilter(r)
if err != nil {
return nil, err
}
gToIntervals, err := pprofMatchingRegions(filter)
if err != nil {
return nil, err
}
events, _ := parseEvents()
return compute(gToIntervals, events)
}
}
// pprofMatchingGoroutines parses the goroutine type id string (i.e. pc)
// and returns the ids of goroutines of the matching type and its interval.
// If the id string is empty, returns nil without an error.
func pprofMatchingGoroutines(id string, events []*trace.Event) (map[uint64][]interval, error) {
if id == "" {
return nil, nil
}
pc, err := strconv.ParseUint(id, 10, 64) // id is string
if err != nil {
return nil, fmt.Errorf("invalid goroutine type: %v", id)
}
analyzeGoroutines(events)
var res map[uint64][]interval
for _, g := range gs {
if g.PC != pc {
continue
}
if res == nil {
res = make(map[uint64][]interval)
}
endTime := g.EndTime
if g.EndTime == 0 {
endTime = lastTimestamp() // the trace doesn't include the goroutine end event. Use the trace end time.
}
res[g.ID] = []interval{{begin: g.StartTime, end: endTime}}
}
if len(res) == 0 && id != "" {
return nil, fmt.Errorf("failed to find matching goroutines for id: %s", id)
}
return res, nil
}
// pprofMatchingRegions returns the time intervals of matching regions
// grouped by the goroutine id. If the filter is nil, returns nil without an error.
func pprofMatchingRegions(filter *regionFilter) (map[uint64][]interval, error) {
res, err := analyzeAnnotations()
if err != nil {
return nil, err
}
if filter == nil {
return nil, nil
}
gToIntervals := make(map[uint64][]interval)
for id, regions := range res.regions {
for _, s := range regions {
if filter.match(id, s) {
gToIntervals[s.G] = append(gToIntervals[s.G], interval{begin: s.firstTimestamp(), end: s.lastTimestamp()})
}
}
}
for g, intervals := range gToIntervals {
// in order to remove nested regions and
// consider only the outermost regions,
// first, we sort based on the start time
// and then scan through to select only the outermost regions.
sort.Slice(intervals, func(i, j int) bool {
x := intervals[i].begin
y := intervals[j].begin
if x == y {
return intervals[i].end < intervals[j].end
}
return x < y
})
var lastTimestamp int64
var n int
// select only the outermost regions.
for _, i := range intervals {
if lastTimestamp <= i.begin {
intervals[n] = i // new non-overlapping region starts.
lastTimestamp = i.end
n++
} // otherwise, skip because this region overlaps with a previous region.
}
gToIntervals[g] = intervals[:n]
}
return gToIntervals, nil
}
type computePprofFunc func(gToIntervals map[uint64][]interval, events []*trace.Event) ([]traceviewer.ProfileRecord, error)
// computePprofIO generates IO pprof-like profile (time spent in IO wait, currently only network blocking event).
func computePprofIO(gToIntervals map[uint64][]interval, events []*trace.Event) ([]traceviewer.ProfileRecord, error) {
prof := make(map[uint64]traceviewer.ProfileRecord)
for _, ev := range events {
if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
continue
}
overlapping := pprofOverlappingDuration(gToIntervals, ev)
if overlapping > 0 {
rec := prof[ev.StkID]
rec.Stack = ev.Stk
rec.Count++
rec.Time += overlapping
prof[ev.StkID] = rec
}
}
return recordsOf(prof), nil
}
// computePprofBlock generates blocking pprof-like profile (time spent blocked on synchronization primitives).
func computePprofBlock(gToIntervals map[uint64][]interval, events []*trace.Event) ([]traceviewer.ProfileRecord, error) {
prof := make(map[uint64]traceviewer.ProfileRecord)
for _, ev := range events {
switch ev.Type {
case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect,
trace.EvGoBlockSync, trace.EvGoBlockCond, trace.EvGoBlockGC:
// TODO(hyangah): figure out why EvGoBlockGC should be here.
// EvGoBlockGC indicates the goroutine blocks on GC assist, not
// on synchronization primitives.
default:
continue
}
if ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
continue
}
overlapping := pprofOverlappingDuration(gToIntervals, ev)
if overlapping > 0 {
rec := prof[ev.StkID]
rec.Stack = ev.Stk
rec.Count++
rec.Time += overlapping
prof[ev.StkID] = rec
}
}
return recordsOf(prof), nil
}
// computePprofSyscall generates syscall pprof-like profile (time spent blocked in syscalls).
func computePprofSyscall(gToIntervals map[uint64][]interval, events []*trace.Event) ([]traceviewer.ProfileRecord, error) {
prof := make(map[uint64]traceviewer.ProfileRecord)
for _, ev := range events {
if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
continue
}
overlapping := pprofOverlappingDuration(gToIntervals, ev)
if overlapping > 0 {
rec := prof[ev.StkID]
rec.Stack = ev.Stk
rec.Count++
rec.Time += overlapping
prof[ev.StkID] = rec
}
}
return recordsOf(prof), nil
}
// computePprofSched generates scheduler latency pprof-like profile
// (time between a goroutine become runnable and actually scheduled for execution).
func computePprofSched(gToIntervals map[uint64][]interval, events []*trace.Event) ([]traceviewer.ProfileRecord, error) {
prof := make(map[uint64]traceviewer.ProfileRecord)
for _, ev := range events {
if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) ||
ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 {
continue
}
overlapping := pprofOverlappingDuration(gToIntervals, ev)
if overlapping > 0 {
rec := prof[ev.StkID]
rec.Stack = ev.Stk
rec.Count++
rec.Time += overlapping
prof[ev.StkID] = rec
}
}
return recordsOf(prof), nil
}
// pprofOverlappingDuration returns the overlapping duration between
// the time intervals in gToIntervals and the specified event.
// If gToIntervals is nil, this simply returns the event's duration.
func pprofOverlappingDuration(gToIntervals map[uint64][]interval, ev *trace.Event) time.Duration {
if gToIntervals == nil { // No filtering.
return time.Duration(ev.Link.Ts-ev.Ts) * time.Nanosecond
}
intervals := gToIntervals[ev.G]
if len(intervals) == 0 {
return 0
}
var overlapping time.Duration
for _, i := range intervals {
if o := overlappingDuration(i.begin, i.end, ev.Ts, ev.Link.Ts); o > 0 {
overlapping += o
}
}
return overlapping
}
func recordsOf(records map[uint64]traceviewer.ProfileRecord) []traceviewer.ProfileRecord {
result := make([]traceviewer.ProfileRecord, 0, len(records))
for _, record := range records {
result = append(result, record)
}
return result
}