| | |
| | |
| | |
| |
|
| | |
| |
|
| | package main |
| |
|
| | import ( |
| | "cmp" |
| | "fmt" |
| | "internal/trace" |
| | "internal/trace/traceviewer" |
| | "net/http" |
| | "slices" |
| | "strings" |
| | "time" |
| | ) |
| |
|
| | func pprofByGoroutine(compute computePprofFunc, t *parsedTrace) traceviewer.ProfileFunc { |
| | return func(r *http.Request) ([]traceviewer.ProfileRecord, error) { |
| | name := r.FormValue("name") |
| | gToIntervals, err := pprofMatchingGoroutines(name, t) |
| | if err != nil { |
| | return nil, err |
| | } |
| | return compute(gToIntervals, t.events) |
| | } |
| | } |
| |
|
| | func pprofByRegion(compute computePprofFunc, t *parsedTrace) traceviewer.ProfileFunc { |
| | return func(r *http.Request) ([]traceviewer.ProfileRecord, error) { |
| | filter, err := newRegionFilter(r) |
| | if err != nil { |
| | return nil, err |
| | } |
| | gToIntervals, err := pprofMatchingRegions(filter, t) |
| | if err != nil { |
| | return nil, err |
| | } |
| | return compute(gToIntervals, t.events) |
| | } |
| | } |
| |
|
| | |
| | |
| | func pprofMatchingGoroutines(name string, t *parsedTrace) (map[trace.GoID][]interval, error) { |
| | res := make(map[trace.GoID][]interval) |
| | for _, g := range t.summary.Goroutines { |
| | if name != "" && g.Name != name { |
| | continue |
| | } |
| | endTime := g.EndTime |
| | if g.EndTime == 0 { |
| | endTime = t.endTime() |
| | } |
| | res[g.ID] = []interval{{start: g.StartTime, end: endTime}} |
| | } |
| | if len(res) == 0 { |
| | return nil, fmt.Errorf("failed to find matching goroutines for name: %s", name) |
| | } |
| | return res, nil |
| | } |
| |
|
| | |
| | |
| | func pprofMatchingRegions(filter *regionFilter, t *parsedTrace) (map[trace.GoID][]interval, error) { |
| | if filter == nil { |
| | return nil, nil |
| | } |
| |
|
| | gToIntervals := make(map[trace.GoID][]interval) |
| | for _, g := range t.summary.Goroutines { |
| | for _, r := range g.Regions { |
| | if !filter.match(t, r) { |
| | continue |
| | } |
| | gToIntervals[g.ID] = append(gToIntervals[g.ID], regionInterval(t, r)) |
| | } |
| | } |
| |
|
| | for g, intervals := range gToIntervals { |
| | |
| | |
| | |
| | |
| | slices.SortFunc(intervals, func(a, b interval) int { |
| | if c := cmp.Compare(a.start, b.start); c != 0 { |
| | return c |
| | } |
| | return cmp.Compare(a.end, b.end) |
| | }) |
| | var lastTimestamp trace.Time |
| | var n int |
| | |
| | for _, i := range intervals { |
| | if lastTimestamp <= i.start { |
| | intervals[n] = i |
| | lastTimestamp = i.end |
| | n++ |
| | } |
| | |
| | } |
| | gToIntervals[g] = intervals[:n] |
| | } |
| | return gToIntervals, nil |
| | } |
| |
|
| | type computePprofFunc func(gToIntervals map[trace.GoID][]interval, events []trace.Event) ([]traceviewer.ProfileRecord, error) |
| |
|
| | |
| | |
| | func computePprofIO() computePprofFunc { |
| | return makeComputePprofFunc(trace.GoWaiting, func(reason string) bool { |
| | return reason == "network" |
| | }) |
| | } |
| |
|
| | |
| | |
| | func computePprofBlock() computePprofFunc { |
| | return makeComputePprofFunc(trace.GoWaiting, func(reason string) bool { |
| | return strings.Contains(reason, "chan") || strings.Contains(reason, "sync") || strings.Contains(reason, "select") |
| | }) |
| | } |
| |
|
| | |
| | |
| | func computePprofSyscall() computePprofFunc { |
| | return makeComputePprofFunc(trace.GoSyscall, func(_ string) bool { |
| | return true |
| | }) |
| | } |
| |
|
| | |
| | |
| | func computePprofSched() computePprofFunc { |
| | return makeComputePprofFunc(trace.GoRunnable, func(_ string) bool { |
| | return true |
| | }) |
| | } |
| |
|
| | |
| | |
| | func makeComputePprofFunc(state trace.GoState, trackReason func(string) bool) computePprofFunc { |
| | return func(gToIntervals map[trace.GoID][]interval, events []trace.Event) ([]traceviewer.ProfileRecord, error) { |
| | stacks := newStackMap() |
| | tracking := make(map[trace.GoID]*trace.Event) |
| | for i := range events { |
| | ev := &events[i] |
| |
|
| | |
| | if ev.Kind() != trace.EventStateTransition { |
| | continue |
| | } |
| |
|
| | |
| | st := ev.StateTransition() |
| | if st.Resource.Kind != trace.ResourceGoroutine { |
| | continue |
| | } |
| | id := st.Resource.Goroutine() |
| | _, new := st.Goroutine() |
| |
|
| | |
| | startEv := tracking[id] |
| | if startEv == nil { |
| | |
| | |
| | |
| | if new == state && trackReason(st.Reason) { |
| | tracking[id] = ev |
| | } |
| | continue |
| | } |
| | |
| | if new == state { |
| | |
| | |
| | continue |
| | } |
| | |
| | |
| | delete(tracking, id) |
| |
|
| | overlapping := pprofOverlappingDuration(gToIntervals, id, interval{startEv.Time(), ev.Time()}) |
| | if overlapping > 0 { |
| | rec := stacks.getOrAdd(startEv.Stack()) |
| | rec.Count++ |
| | rec.Time += overlapping |
| | } |
| | } |
| | return stacks.profile(), nil |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | func pprofOverlappingDuration(gToIntervals map[trace.GoID][]interval, id trace.GoID, sample interval) time.Duration { |
| | if gToIntervals == nil { |
| | return sample.duration() |
| | } |
| | intervals := gToIntervals[id] |
| | if len(intervals) == 0 { |
| | return 0 |
| | } |
| |
|
| | var overlapping time.Duration |
| | for _, i := range intervals { |
| | if o := i.overlap(sample); o > 0 { |
| | overlapping += o |
| | } |
| | } |
| | return overlapping |
| | } |
| |
|
| | |
| | type interval struct { |
| | start, end trace.Time |
| | } |
| |
|
| | func (i interval) duration() time.Duration { |
| | return i.end.Sub(i.start) |
| | } |
| |
|
| | func (i1 interval) overlap(i2 interval) time.Duration { |
| | |
| | if i1.end < i2.start || i2.end < i1.start { |
| | return 0 |
| | } |
| | if i1.start < i2.start { |
| | i1.start = i2.start |
| | } |
| | if i1.end > i2.end { |
| | i1.end = i2.end |
| | } |
| | return i1.duration() |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | const pprofMaxStack = 128 |
| |
|
| | |
| | type stackMap struct { |
| | |
| | |
| | |
| | |
| | |
| | stacks map[trace.Stack]*traceviewer.ProfileRecord |
| |
|
| | |
| | |
| | pcs map[[pprofMaxStack]uint64]trace.Stack |
| | } |
| |
|
| | func newStackMap() *stackMap { |
| | return &stackMap{ |
| | stacks: make(map[trace.Stack]*traceviewer.ProfileRecord), |
| | pcs: make(map[[pprofMaxStack]uint64]trace.Stack), |
| | } |
| | } |
| |
|
| | func (m *stackMap) getOrAdd(stack trace.Stack) *traceviewer.ProfileRecord { |
| | |
| | if rec, ok := m.stacks[stack]; ok { |
| | return rec |
| | } |
| | |
| |
|
| | |
| | var pcs [pprofMaxStack]uint64 |
| | pcsForStack(stack, &pcs) |
| |
|
| | |
| | var rec *traceviewer.ProfileRecord |
| | if existing, ok := m.pcs[pcs]; ok { |
| | |
| | rec = m.stacks[existing] |
| | delete(m.stacks, existing) |
| | } else { |
| | |
| | rec = new(traceviewer.ProfileRecord) |
| | } |
| | |
| | |
| | |
| | |
| | |
| | m.pcs[pcs] = stack |
| | m.stacks[stack] = rec |
| | return rec |
| | } |
| |
|
| | func (m *stackMap) profile() []traceviewer.ProfileRecord { |
| | prof := make([]traceviewer.ProfileRecord, 0, len(m.stacks)) |
| | for stack, record := range m.stacks { |
| | rec := *record |
| | var i int |
| | for frame := range stack.Frames() { |
| | rec.Stack = append(rec.Stack, frame) |
| | |
| | |
| | if i >= pprofMaxStack { |
| | break |
| | } |
| | i++ |
| | } |
| | prof = append(prof, rec) |
| | } |
| | return prof |
| | } |
| |
|
| | |
| | func pcsForStack(stack trace.Stack, pcs *[pprofMaxStack]uint64) { |
| | for i, frame := range slices.Collect(stack.Frames()) { |
| | pcs[i] = frame.PC |
| | if i >= len(pcs) { |
| | break |
| | } |
| | } |
| | } |
| |
|