-
Notifications
You must be signed in to change notification settings - Fork 17.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
runtime: add execution tracer v2 behind GOEXPERIMENT=exectracer2
This change mostly implements the design described in #60773 and includes a new scalable parser for the new trace format, available in internal/trace/v2. I'll leave this commit message short because this is clearly an enormous CL with a lot of detail. This change does not hook up the new tracer into cmd/trace yet. A follow-up CL will handle that. For #60773. Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest,gotip-linux-amd64-longtest-race Change-Id: I5d2aca2cc07580ed3c76a9813ac48ec96b157de0 Reviewed-on: https://go-review.googlesource.com/c/go/+/494187 Reviewed-by: Michael Pratt <mpratt@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
- Loading branch information
Showing
100 changed files
with
11,790 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,256 @@ | ||
// Copyright 2023 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. | ||
|
||
// This file contains data types that all implementations of the trace format | ||
// parser need to provide to the rest of the package. | ||
|
||
package trace | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"internal/trace/v2/event" | ||
"internal/trace/v2/event/go122" | ||
"internal/trace/v2/version" | ||
) | ||
|
||
// maxArgs is the maximum number of arguments for "plain" events, | ||
// i.e. anything that could reasonably be represented as a Base. | ||
const maxArgs = 5 | ||
|
||
// baseEvent is the basic unprocessed event. This serves as a common | ||
// fundamental data structure across. | ||
type baseEvent struct { | ||
typ event.Type | ||
time Time | ||
args [maxArgs - 1]uint64 | ||
} | ||
|
||
// extra returns a slice representing extra available space in args | ||
// that the parser can use to pass data up into Event. | ||
func (e *baseEvent) extra(v version.Version) []uint64 { | ||
switch v { | ||
case version.Go122: | ||
return e.args[len(go122.Specs()[e.typ].Args)-1:] | ||
} | ||
panic(fmt.Sprintf("unsupported version: go 1.%d", v)) | ||
} | ||
|
||
// evTable contains the per-generation data necessary to | ||
// interpret an individual event. | ||
type evTable struct { | ||
freq frequency | ||
strings dataTable[stringID, string] | ||
stacks dataTable[stackID, stack] | ||
|
||
// extraStrings are strings that get generated during | ||
// parsing but haven't come directly from the trace, so | ||
// they don't appear in strings. | ||
extraStrings []string | ||
extraStringIDs map[string]extraStringID | ||
nextExtra extraStringID | ||
} | ||
|
||
// addExtraString adds an extra string to the evTable and returns | ||
// a unique ID for the string in the table. | ||
func (t *evTable) addExtraString(s string) extraStringID { | ||
if s == "" { | ||
return 0 | ||
} | ||
if t.extraStringIDs == nil { | ||
t.extraStringIDs = make(map[string]extraStringID) | ||
} | ||
if id, ok := t.extraStringIDs[s]; ok { | ||
return id | ||
} | ||
t.nextExtra++ | ||
id := t.nextExtra | ||
t.extraStrings = append(t.extraStrings, s) | ||
t.extraStringIDs[s] = id | ||
return id | ||
} | ||
|
||
// getExtraString returns the extra string for the provided ID. | ||
// The ID must have been produced by addExtraString for this evTable. | ||
func (t *evTable) getExtraString(id extraStringID) string { | ||
if id == 0 { | ||
return "" | ||
} | ||
return t.extraStrings[id-1] | ||
} | ||
|
||
// dataTable is a mapping from EIs to Es. | ||
type dataTable[EI ~uint64, E any] struct { | ||
present []uint8 | ||
dense []E | ||
sparse map[EI]E | ||
} | ||
|
||
// insert tries to add a mapping from id to s. | ||
// | ||
// Returns an error if a mapping for id already exists, regardless | ||
// of whether or not s is the same in content. This should be used | ||
// for validation during parsing. | ||
func (d *dataTable[EI, E]) insert(id EI, data E) error { | ||
if d.sparse == nil { | ||
d.sparse = make(map[EI]E) | ||
} | ||
if existing, ok := d.get(id); ok { | ||
return fmt.Errorf("multiple %Ts with the same ID: id=%d, new=%v, existing=%v", data, id, data, existing) | ||
} | ||
d.sparse[id] = data | ||
return nil | ||
} | ||
|
||
// compactify attempts to compact sparse into dense. | ||
// | ||
// This is intended to be called only once after insertions are done. | ||
func (d *dataTable[EI, E]) compactify() { | ||
if d.sparse == nil || len(d.dense) != 0 { | ||
// Already compactified. | ||
return | ||
} | ||
// Find the range of IDs. | ||
maxID := EI(0) | ||
minID := ^EI(0) | ||
for id := range d.sparse { | ||
if id > maxID { | ||
maxID = id | ||
} | ||
if id < minID { | ||
minID = id | ||
} | ||
} | ||
// We're willing to waste at most 2x memory. | ||
if int(maxID-minID) > 2*len(d.sparse) { | ||
return | ||
} | ||
if int(minID) > len(d.sparse) { | ||
return | ||
} | ||
size := int(maxID) + 1 | ||
d.present = make([]uint8, (size+7)/8) | ||
d.dense = make([]E, size) | ||
for id, data := range d.sparse { | ||
d.dense[id] = data | ||
d.present[id/8] |= uint8(1) << (id % 8) | ||
} | ||
d.sparse = nil | ||
} | ||
|
||
// get returns the E for id or false if it doesn't | ||
// exist. This should be used for validation during parsing. | ||
func (d *dataTable[EI, E]) get(id EI) (E, bool) { | ||
if id == 0 { | ||
return *new(E), true | ||
} | ||
if int(id) < len(d.dense) { | ||
if d.present[id/8]&(uint8(1)<<(id%8)) != 0 { | ||
return d.dense[id], true | ||
} | ||
} else if d.sparse != nil { | ||
if data, ok := d.sparse[id]; ok { | ||
return data, true | ||
} | ||
} | ||
return *new(E), false | ||
} | ||
|
||
// forEach iterates over all ID/value pairs in the data table. | ||
func (d *dataTable[EI, E]) forEach(yield func(EI, E) bool) bool { | ||
for id, value := range d.dense { | ||
if d.present[id/8]&(uint8(1)<<(id%8)) == 0 { | ||
continue | ||
} | ||
if !yield(EI(id), value) { | ||
return false | ||
} | ||
} | ||
if d.sparse == nil { | ||
return true | ||
} | ||
for id, value := range d.sparse { | ||
if !yield(id, value) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
// mustGet returns the E for id or panics if it fails. | ||
// | ||
// This should only be used if id has already been validated. | ||
func (d *dataTable[EI, E]) mustGet(id EI) E { | ||
data, ok := d.get(id) | ||
if !ok { | ||
panic(fmt.Sprintf("expected id %d in %T table", id, data)) | ||
} | ||
return data | ||
} | ||
|
||
// frequency is nanoseconds per timestamp unit. | ||
type frequency float64 | ||
|
||
// mul multiplies an unprocessed to produce a time in nanoseconds. | ||
func (f frequency) mul(t timestamp) Time { | ||
return Time(float64(t) * float64(f)) | ||
} | ||
|
||
// stringID is an index into the string table for a generation. | ||
type stringID uint64 | ||
|
||
// extraStringID is an index into the extra string table for a generation. | ||
type extraStringID uint64 | ||
|
||
// stackID is an index into the stack table for a generation. | ||
type stackID uint64 | ||
|
||
// cpuSample represents a CPU profiling sample captured by the trace. | ||
type cpuSample struct { | ||
schedCtx | ||
time Time | ||
stack stackID | ||
} | ||
|
||
// asEvent produces a complete Event from a cpuSample. It needs | ||
// the evTable from the generation that created it. | ||
// | ||
// We don't just store it as an Event in generation to minimize | ||
// the amount of pointer data floating around. | ||
func (s cpuSample) asEvent(table *evTable) Event { | ||
// TODO(mknyszek): This is go122-specific, but shouldn't be. | ||
// Generalize this in the future. | ||
e := Event{ | ||
table: table, | ||
ctx: s.schedCtx, | ||
base: baseEvent{ | ||
typ: go122.EvCPUSample, | ||
time: s.time, | ||
}, | ||
} | ||
e.base.args[0] = uint64(s.stack) | ||
return e | ||
} | ||
|
||
// stack represents a goroutine stack sample. | ||
type stack struct { | ||
frames []frame | ||
} | ||
|
||
func (s stack) String() string { | ||
var sb strings.Builder | ||
for _, frame := range s.frames { | ||
fmt.Fprintf(&sb, "\t%#v\n", frame) | ||
} | ||
return sb.String() | ||
} | ||
|
||
// frame represents a single stack frame. | ||
type frame struct { | ||
pc uint64 | ||
funcID stringID | ||
fileID stringID | ||
line uint64 | ||
} |
Oops, something went wrong.