Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
pprof.go329 linesDownload Raw Back to trace
1// Copyright 2014 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.4 5// Serving of pprof-like profiles.6 7package main8 9import (10	"cmp"11	"fmt"12	"internal/trace"13	"internal/trace/traceviewer"14	"net/http"15	"slices"16	"strings"17	"time"18)19 20func pprofByGoroutine(compute computePprofFunc, t *parsedTrace) traceviewer.ProfileFunc {21	return func(r *http.Request) ([]traceviewer.ProfileRecord, error) {22		name := r.FormValue("name")23		gToIntervals, err := pprofMatchingGoroutines(name, t)24		if err != nil {25			return nil, err26		}27		return compute(gToIntervals, t.events)28	}29}30 31func pprofByRegion(compute computePprofFunc, t *parsedTrace) traceviewer.ProfileFunc {32	return func(r *http.Request) ([]traceviewer.ProfileRecord, error) {33		filter, err := newRegionFilter(r)34		if err != nil {35			return nil, err36		}37		gToIntervals, err := pprofMatchingRegions(filter, t)38		if err != nil {39			return nil, err40		}41		return compute(gToIntervals, t.events)42	}43}44 45// pprofMatchingGoroutines returns the ids of goroutines of the matching name and its interval.46// If the id string is empty, returns nil without an error.47func pprofMatchingGoroutines(name string, t *parsedTrace) (map[trace.GoID][]interval, error) {48	res := make(map[trace.GoID][]interval)49	for _, g := range t.summary.Goroutines {50		if name != "" && g.Name != name {51			continue52		}53		endTime := g.EndTime54		if g.EndTime == 0 {55			endTime = t.endTime() // Use the trace end time, since the goroutine is still live then.56		}57		res[g.ID] = []interval{{start: g.StartTime, end: endTime}}58	}59	if len(res) == 0 {60		return nil, fmt.Errorf("failed to find matching goroutines for name: %s", name)61	}62	return res, nil63}64 65// pprofMatchingRegions returns the time intervals of matching regions66// grouped by the goroutine id. If the filter is nil, returns nil without an error.67func pprofMatchingRegions(filter *regionFilter, t *parsedTrace) (map[trace.GoID][]interval, error) {68	if filter == nil {69		return nil, nil70	}71 72	gToIntervals := make(map[trace.GoID][]interval)73	for _, g := range t.summary.Goroutines {74		for _, r := range g.Regions {75			if !filter.match(t, r) {76				continue77			}78			gToIntervals[g.ID] = append(gToIntervals[g.ID], regionInterval(t, r))79		}80	}81 82	for g, intervals := range gToIntervals {83		// In order to remove nested regions and84		// consider only the outermost regions,85		// first, we sort based on the start time86		// and then scan through to select only the outermost regions.87		slices.SortFunc(intervals, func(a, b interval) int {88			if c := cmp.Compare(a.start, b.start); c != 0 {89				return c90			}91			return cmp.Compare(a.end, b.end)92		})93		var lastTimestamp trace.Time94		var n int95		// Select only the outermost regions.96		for _, i := range intervals {97			if lastTimestamp <= i.start {98				intervals[n] = i // new non-overlapping region starts.99				lastTimestamp = i.end100				n++101			}102			// Otherwise, skip because this region overlaps with a previous region.103		}104		gToIntervals[g] = intervals[:n]105	}106	return gToIntervals, nil107}108 109type computePprofFunc func(gToIntervals map[trace.GoID][]interval, events []trace.Event) ([]traceviewer.ProfileRecord, error)110 111// computePprofIO returns a computePprofFunc that generates IO pprof-like profile (time spent in112// IO wait, currently only network blocking event).113func computePprofIO() computePprofFunc {114	return makeComputePprofFunc(trace.GoWaiting, func(reason string) bool {115		return reason == "network"116	})117}118 119// computePprofBlock returns a computePprofFunc that generates blocking pprof-like profile120// (time spent blocked on synchronization primitives).121func computePprofBlock() computePprofFunc {122	return makeComputePprofFunc(trace.GoWaiting, func(reason string) bool {123		return strings.Contains(reason, "chan") || strings.Contains(reason, "sync") || strings.Contains(reason, "select")124	})125}126 127// computePprofSyscall returns a computePprofFunc that generates a syscall pprof-like128// profile (time spent in syscalls).129func computePprofSyscall() computePprofFunc {130	return makeComputePprofFunc(trace.GoSyscall, func(_ string) bool {131		return true132	})133}134 135// computePprofSched returns a computePprofFunc that generates a scheduler latency pprof-like profile136// (time between a goroutine become runnable and actually scheduled for execution).137func computePprofSched() computePprofFunc {138	return makeComputePprofFunc(trace.GoRunnable, func(_ string) bool {139		return true140	})141}142 143// makeComputePprofFunc returns a computePprofFunc that generates a profile of time goroutines spend144// in a particular state for the specified reasons.145func makeComputePprofFunc(state trace.GoState, trackReason func(string) bool) computePprofFunc {146	return func(gToIntervals map[trace.GoID][]interval, events []trace.Event) ([]traceviewer.ProfileRecord, error) {147		stacks := newStackMap()148		tracking := make(map[trace.GoID]*trace.Event)149		for i := range events {150			ev := &events[i]151 152			// Filter out any non-state-transitions and events without stacks.153			if ev.Kind() != trace.EventStateTransition {154				continue155			}156 157			// The state transition has to apply to a goroutine.158			st := ev.StateTransition()159			if st.Resource.Kind != trace.ResourceGoroutine {160				continue161			}162			id := st.Resource.Goroutine()163			_, new := st.Goroutine()164 165			// Check if we're tracking this goroutine.166			startEv := tracking[id]167			if startEv == nil {168				// We're not. Start tracking if the new state169				// matches what we want and the transition is170				// for one of the reasons we care about.171				if new == state && trackReason(st.Reason) {172					tracking[id] = ev173				}174				continue175			}176			// We're tracking this goroutine.177			if new == state {178				// We're tracking this goroutine, but it's just transitioning179				// to the same state (this is a no-ip180				continue181			}182			// The goroutine has transitioned out of the state we care about,183			// so remove it from tracking and record the stack.184			delete(tracking, id)185 186			overlapping := pprofOverlappingDuration(gToIntervals, id, interval{startEv.Time(), ev.Time()})187			if overlapping > 0 {188				rec := stacks.getOrAdd(startEv.Stack())189				rec.Count++190				rec.Time += overlapping191			}192		}193		return stacks.profile(), nil194	}195}196 197// pprofOverlappingDuration returns the overlapping duration between198// the time intervals in gToIntervals and the specified event.199// If gToIntervals is nil, this simply returns the event's duration.200func pprofOverlappingDuration(gToIntervals map[trace.GoID][]interval, id trace.GoID, sample interval) time.Duration {201	if gToIntervals == nil { // No filtering.202		return sample.duration()203	}204	intervals := gToIntervals[id]205	if len(intervals) == 0 {206		return 0207	}208 209	var overlapping time.Duration210	for _, i := range intervals {211		if o := i.overlap(sample); o > 0 {212			overlapping += o213		}214	}215	return overlapping216}217 218// interval represents a time interval in the trace.219type interval struct {220	start, end trace.Time221}222 223func (i interval) duration() time.Duration {224	return i.end.Sub(i.start)225}226 227func (i1 interval) overlap(i2 interval) time.Duration {228	// Assume start1 <= end1 and start2 <= end2229	if i1.end < i2.start || i2.end < i1.start {230		return 0231	}232	if i1.start < i2.start { // choose the later one233		i1.start = i2.start234	}235	if i1.end > i2.end { // choose the earlier one236		i1.end = i2.end237	}238	return i1.duration()239}240 241// pprofMaxStack is the extent of the deduplication we're willing to do.242//243// Because slices aren't comparable and we want to leverage maps for deduplication,244// we have to choose a fixed constant upper bound on the amount of frames we want245// to support. In practice this is fine because there's a maximum depth to these246// stacks anyway.247const pprofMaxStack = 128248 249// stackMap is a map of trace.Stack to some value V.250type stackMap struct {251	// stacks contains the full list of stacks in the set, however252	// it is insufficient for deduplication because trace.Stack253	// equality is only optimistic. If two trace.Stacks are equal,254	// then they are guaranteed to be equal in content. If they are255	// not equal, then they might still be equal in content.256	stacks map[trace.Stack]*traceviewer.ProfileRecord257 258	// pcs is the source-of-truth for deduplication. It is a map of259	// the actual PCs in the stack to a trace.Stack.260	pcs map[[pprofMaxStack]uint64]trace.Stack261}262 263func newStackMap() *stackMap {264	return &stackMap{265		stacks: make(map[trace.Stack]*traceviewer.ProfileRecord),266		pcs:    make(map[[pprofMaxStack]uint64]trace.Stack),267	}268}269 270func (m *stackMap) getOrAdd(stack trace.Stack) *traceviewer.ProfileRecord {271	// Fast path: check to see if this exact stack is already in the map.272	if rec, ok := m.stacks[stack]; ok {273		return rec274	}275	// Slow path: the stack may still be in the map.276 277	// Grab the stack's PCs as the source-of-truth.278	var pcs [pprofMaxStack]uint64279	pcsForStack(stack, &pcs)280 281	// Check the source-of-truth.282	var rec *traceviewer.ProfileRecord283	if existing, ok := m.pcs[pcs]; ok {284		// In the map.285		rec = m.stacks[existing]286		delete(m.stacks, existing)287	} else {288		// Not in the map.289		rec = new(traceviewer.ProfileRecord)290	}291	// Insert regardless of whether we have a match in m.pcs.292	// Even if we have a match, we want to keep the newest version293	// of that stack, since we're much more likely tos see it again294	// as we iterate through the trace linearly. Simultaneously, we295	// are likely to never see the old stack again.296	m.pcs[pcs] = stack297	m.stacks[stack] = rec298	return rec299}300 301func (m *stackMap) profile() []traceviewer.ProfileRecord {302	prof := make([]traceviewer.ProfileRecord, 0, len(m.stacks))303	for stack, record := range m.stacks {304		rec := *record305		var i int306		for frame := range stack.Frames() {307			rec.Stack = append(rec.Stack, frame)308			// Cut this off at pprofMaxStack because that's as far309			// as our deduplication goes.310			if i >= pprofMaxStack {311				break312			}313			i++314		}315		prof = append(prof, rec)316	}317	return prof318}319 320// pcsForStack extracts the first pprofMaxStack PCs from stack into pcs.321func pcsForStack(stack trace.Stack, pcs *[pprofMaxStack]uint64) {322	for i, frame := range slices.Collect(stack.Frames()) {323		pcs[i] = frame.PC324		if i >= len(pcs) {325			break326		}327	}328}329 
codekingpro/portable-devtools · Team Ai