aboutsummaryrefslogtreecommitdiff
path: root/src/internal
diff options
context:
space:
mode:
authorCarlos Amedee <carlos@golang.org>2024-05-07 15:37:10 -0400
committerCarlos Amedee <carlos@golang.org>2024-05-17 18:31:04 +0000
commit664088b898330a2c73121c5a6f2d4dbec5b180d7 (patch)
tree7cde53dae9ac350ffce508ffd581bdc958829764 /src/internal
parente95f6af0082556bbcf01e376ae8c5f1f14ded75e (diff)
downloadgo-664088b898330a2c73121c5a6f2d4dbec5b180d7.tar.gz
go-664088b898330a2c73121c5a6f2d4dbec5b180d7.zip
internal/trace: remove remnanats of v1 tracer
This change removes unused parts of the v1 tracer in preperation of the move of the v2 tracer into the trace package. Updates #67367 Change-Id: I3e53a8afdef72dc90c2d5b514380d1077d284bc7 Reviewed-on: https://go-review.googlesource.com/c/go/+/584537 Reviewed-by: Michael Knyszek <mknyszek@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/internal')
-rw-r--r--src/internal/trace/gc.go158
-rw-r--r--src/internal/trace/gc_test.go37
-rw-r--r--src/internal/trace/goroutines.go358
-rwxr-xr-xsrc/internal/trace/mkcanned.bash20
-rw-r--r--src/internal/trace/order.go285
-rw-r--r--src/internal/trace/parser.go1102
-rw-r--r--src/internal/trace/parser_test.go123
-rw-r--r--src/internal/trace/summary.go8
-rw-r--r--src/internal/trace/testdata/http_1_10_goodbin2201 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/http_1_11_goodbin2779 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/http_1_19_goodbin28172 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/http_1_21_goodbin6744 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/http_1_5_goodbin42218 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/http_1_7_goodbin1971 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/http_1_9_goodbin2187 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_1_10_goodbin370999 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_1_11_goodbin370129 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_1_19_goodbin322338 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_1_21_goodbin353725 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_1_5_goodbin7446 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_1_5_unorderedbin8194 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_1_7_goodbin396526 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_1_9_goodbin365129 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_start_stop_1_10_goodbin6338 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_start_stop_1_11_goodbin4882 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_start_stop_1_19_goodbin7448 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_start_stop_1_21_goodbin5002 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_start_stop_1_5_goodbin6997 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_start_stop_1_7_goodbin2055 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/stress_start_stop_1_9_goodbin6271 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/user_task_region_1_11_goodbin2000 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/user_task_region_1_19_goodbin1922 -> 0 bytes
-rw-r--r--src/internal/trace/testdata/user_task_region_1_21_goodbin2404 -> 0 bytes
-rw-r--r--src/internal/trace/writer.go49
34 files changed, 8 insertions, 2132 deletions
diff --git a/src/internal/trace/gc.go b/src/internal/trace/gc.go
index ca91969cfb..60c45f46aa 100644
--- a/src/internal/trace/gc.go
+++ b/src/internal/trace/gc.go
@@ -45,164 +45,6 @@ const (
UtilPerProc
)
-// MutatorUtilization returns a set of mutator utilization functions
-// for the given trace. Each function will always end with 0
-// utilization. The bounds of each function are implicit in the first
-// and last event; outside of these bounds each function is undefined.
-//
-// If the UtilPerProc flag is not given, this always returns a single
-// utilization function. Otherwise, it returns one function per P.
-func MutatorUtilization(events []*Event, flags UtilFlags) [][]MutatorUtil {
- if len(events) == 0 {
- return nil
- }
-
- type perP struct {
- // gc > 0 indicates that GC is active on this P.
- gc int
- // series the logical series number for this P. This
- // is necessary because Ps may be removed and then
- // re-added, and then the new P needs a new series.
- series int
- }
- ps := []perP{}
- stw := 0
-
- out := [][]MutatorUtil{}
- assists := map[uint64]bool{}
- block := map[uint64]*Event{}
- bgMark := map[uint64]bool{}
-
- for _, ev := range events {
- switch ev.Type {
- case EvGomaxprocs:
- gomaxprocs := int(ev.Args[0])
- if len(ps) > gomaxprocs {
- if flags&UtilPerProc != 0 {
- // End each P's series.
- for _, p := range ps[gomaxprocs:] {
- out[p.series] = addUtil(out[p.series], MutatorUtil{ev.Ts, 0})
- }
- }
- ps = ps[:gomaxprocs]
- }
- for len(ps) < gomaxprocs {
- // Start new P's series.
- series := 0
- if flags&UtilPerProc != 0 || len(out) == 0 {
- series = len(out)
- out = append(out, []MutatorUtil{{ev.Ts, 1}})
- }
- ps = append(ps, perP{series: series})
- }
- case EvSTWStart:
- if flags&UtilSTW != 0 {
- stw++
- }
- case EvSTWDone:
- if flags&UtilSTW != 0 {
- stw--
- }
- case EvGCMarkAssistStart:
- if flags&UtilAssist != 0 {
- ps[ev.P].gc++
- assists[ev.G] = true
- }
- case EvGCMarkAssistDone:
- if flags&UtilAssist != 0 {
- ps[ev.P].gc--
- delete(assists, ev.G)
- }
- case EvGCSweepStart:
- if flags&UtilSweep != 0 {
- ps[ev.P].gc++
- }
- case EvGCSweepDone:
- if flags&UtilSweep != 0 {
- ps[ev.P].gc--
- }
- case EvGoStartLabel:
- if flags&UtilBackground != 0 && strings.HasPrefix(ev.SArgs[0], "GC ") && ev.SArgs[0] != "GC (idle)" {
- // Background mark worker.
- //
- // If we're in per-proc mode, we don't
- // count dedicated workers because
- // they kick all of the goroutines off
- // that P, so don't directly
- // contribute to goroutine latency.
- if !(flags&UtilPerProc != 0 && ev.SArgs[0] == "GC (dedicated)") {
- bgMark[ev.G] = true
- ps[ev.P].gc++
- }
- }
- fallthrough
- case EvGoStart:
- if assists[ev.G] {
- // Unblocked during assist.
- ps[ev.P].gc++
- }
- block[ev.G] = ev.Link
- default:
- if ev != block[ev.G] {
- continue
- }
-
- if assists[ev.G] {
- // Blocked during assist.
- ps[ev.P].gc--
- }
- if bgMark[ev.G] {
- // Background mark worker done.
- ps[ev.P].gc--
- delete(bgMark, ev.G)
- }
- delete(block, ev.G)
- }
-
- if flags&UtilPerProc == 0 {
- // Compute the current average utilization.
- if len(ps) == 0 {
- continue
- }
- gcPs := 0
- if stw > 0 {
- gcPs = len(ps)
- } else {
- for i := range ps {
- if ps[i].gc > 0 {
- gcPs++
- }
- }
- }
- mu := MutatorUtil{ev.Ts, 1 - float64(gcPs)/float64(len(ps))}
-
- // Record the utilization change. (Since
- // len(ps) == len(out), we know len(out) > 0.)
- out[0] = addUtil(out[0], mu)
- } else {
- // Check for per-P utilization changes.
- for i := range ps {
- p := &ps[i]
- util := 1.0
- if stw > 0 || p.gc > 0 {
- util = 0.0
- }
- out[p.series] = addUtil(out[p.series], MutatorUtil{ev.Ts, util})
- }
- }
- }
-
- // Add final 0 utilization event to any remaining series. This
- // is important to mark the end of the trace. The exact value
- // shouldn't matter since no window should extend beyond this,
- // but using 0 is symmetric with the start of the trace.
- mu := MutatorUtil{events[len(events)-1].Ts, 0}
- for i := range ps {
- out[ps[i].series] = addUtil(out[ps[i].series], mu)
- }
- return out
-}
-
// MutatorUtilizationV2 returns a set of mutator utilization functions
// for the given v2 trace, passed as an io.Reader. Each function will
// always end with 0 utilization. The bounds of each function are implicit
diff --git a/src/internal/trace/gc_test.go b/src/internal/trace/gc_test.go
index 8c9f77f57a..a74d59d8b7 100644
--- a/src/internal/trace/gc_test.go
+++ b/src/internal/trace/gc_test.go
@@ -5,12 +5,10 @@
package trace
import (
- "bytes"
tracev2 "internal/trace/v2"
"internal/trace/v2/testtrace"
"io"
"math"
- "os"
"testing"
"time"
)
@@ -118,17 +116,6 @@ func TestMMUTrace(t *testing.T) {
}
}
}
- t.Run("V1", func(t *testing.T) {
- data, err := os.ReadFile("testdata/stress_1_10_good")
- if err != nil {
- t.Fatalf("failed to read input file: %v", err)
- }
- events, err := Parse(bytes.NewReader(data), "")
- if err != nil {
- t.Fatalf("failed to parse trace: %s", err)
- }
- check(t, MutatorUtilization(events.Events, UtilSTW|UtilBackground|UtilAssist))
- })
t.Run("V2", func(t *testing.T) {
testPath := "v2/testdata/tests/go122-gc-stress.test"
r, _, err := testtrace.ParseFile(testPath)
@@ -155,30 +142,6 @@ func TestMMUTrace(t *testing.T) {
})
}
-func BenchmarkMMU(b *testing.B) {
- data, err := os.ReadFile("testdata/stress_1_10_good")
- if err != nil {
- b.Fatalf("failed to read input file: %v", err)
- }
- events, err := Parse(bytes.NewReader(data), "")
- if err != nil {
- b.Fatalf("failed to parse trace: %s", err)
- }
- mu := MutatorUtilization(events.Events, UtilSTW|UtilBackground|UtilAssist|UtilSweep)
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- mmuCurve := NewMMUCurve(mu)
- xMin, xMax := time.Microsecond, time.Second
- logMin, logMax := math.Log(float64(xMin)), math.Log(float64(xMax))
- const samples = 100
- for i := 0; i < samples; i++ {
- window := time.Duration(math.Exp(float64(i)/(samples-1)*(logMax-logMin) + logMin))
- mmuCurve.MMU(window)
- }
- }
-}
-
func mmuSlow(util []MutatorUtil, window time.Duration) (mmu float64) {
if max := time.Duration(util[len(util)-1].Time - util[0].Time); window > max {
window = max
diff --git a/src/internal/trace/goroutines.go b/src/internal/trace/goroutines.go
deleted file mode 100644
index 4b4f13d2ae..0000000000
--- a/src/internal/trace/goroutines.go
+++ /dev/null
@@ -1,358 +0,0 @@
-// 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.
-
-package trace
-
-import (
- "sort"
- "strings"
-)
-
-// GDesc contains statistics and execution details of a single goroutine.
-type GDesc struct {
- ID uint64
- Name string
- PC uint64
- CreationTime int64
- StartTime int64
- EndTime int64
-
- // List of regions in the goroutine, sorted based on the start time.
- Regions []*UserRegionDesc
-
- // Statistics of execution time during the goroutine execution.
- GExecutionStat
-
- *gdesc // private part.
-}
-
-// UserRegionDesc represents a region and goroutine execution stats
-// while the region was active.
-type UserRegionDesc struct {
- TaskID uint64
- Name string
-
- // Region start event. Normally EvUserRegion start event or nil,
- // but can be EvGoCreate event if the region is a synthetic
- // region representing task inheritance from the parent goroutine.
- Start *Event
-
- // Region end event. Normally EvUserRegion end event or nil,
- // but can be EvGoStop or EvGoEnd event if the goroutine
- // terminated without explicitly ending the region.
- End *Event
-
- GExecutionStat
-}
-
-// GExecutionStat contains statistics about a goroutine's execution
-// during a period of time.
-type GExecutionStat struct {
- ExecTime int64
- SchedWaitTime int64
- IOTime int64
- BlockTime int64
- SyscallTime int64
- GCTime int64
- SweepTime int64
- TotalTime int64
-}
-
-// sub returns the stats v-s.
-func (s GExecutionStat) sub(v GExecutionStat) (r GExecutionStat) {
- r = s
- r.ExecTime -= v.ExecTime
- r.SchedWaitTime -= v.SchedWaitTime
- r.IOTime -= v.IOTime
- r.BlockTime -= v.BlockTime
- r.SyscallTime -= v.SyscallTime
- r.GCTime -= v.GCTime
- r.SweepTime -= v.SweepTime
- r.TotalTime -= v.TotalTime
- return r
-}
-
-// snapshotStat returns the snapshot of the goroutine execution statistics.
-// This is called as we process the ordered trace event stream. lastTs and
-// activeGCStartTime are used to process pending statistics if this is called
-// before any goroutine end event.
-func (g *GDesc) snapshotStat(lastTs, activeGCStartTime int64) (ret GExecutionStat) {
- ret = g.GExecutionStat
-
- if g.gdesc == nil {
- return ret // finalized GDesc. No pending state.
- }
-
- if activeGCStartTime != 0 { // terminating while GC is active
- if g.CreationTime < activeGCStartTime {
- ret.GCTime += lastTs - activeGCStartTime
- } else {
- // The goroutine's lifetime completely overlaps
- // with a GC.
- ret.GCTime += lastTs - g.CreationTime
- }
- }
-
- if g.TotalTime == 0 {
- ret.TotalTime = lastTs - g.CreationTime
- }
-
- if g.lastStartTime != 0 {
- ret.ExecTime += lastTs - g.lastStartTime
- }
- if g.blockNetTime != 0 {
- ret.IOTime += lastTs - g.blockNetTime
- }
- if g.blockSyncTime != 0 {
- ret.BlockTime += lastTs - g.blockSyncTime
- }
- if g.blockSyscallTime != 0 {
- ret.SyscallTime += lastTs - g.blockSyscallTime
- }
- if g.blockSchedTime != 0 {
- ret.SchedWaitTime += lastTs - g.blockSchedTime
- }
- if g.blockSweepTime != 0 {
- ret.SweepTime += lastTs - g.blockSweepTime
- }
- return ret
-}
-
-// finalize is called when processing a goroutine end event or at
-// the end of trace processing. This finalizes the execution stat
-// and any active regions in the goroutine, in which case trigger is nil.
-func (g *GDesc) finalize(lastTs, activeGCStartTime int64, trigger *Event) {
- if trigger != nil {
- g.EndTime = trigger.Ts
- }
- finalStat := g.snapshotStat(lastTs, activeGCStartTime)
-
- g.GExecutionStat = finalStat
-
- // System goroutines are never part of regions, even though they
- // "inherit" a task due to creation (EvGoCreate) from within a region.
- // This may happen e.g. if the first GC is triggered within a region,
- // starting the GC worker goroutines.
- if !IsSystemGoroutine(g.Name) {
- for _, s := range g.activeRegions {
- s.End = trigger
- s.GExecutionStat = finalStat.sub(s.GExecutionStat)
- g.Regions = append(g.Regions, s)
- }
- }
- *(g.gdesc) = gdesc{}
-}
-
-// gdesc is a private part of GDesc that is required only during analysis.
-type gdesc struct {
- lastStartTime int64
- blockNetTime int64
- blockSyncTime int64
- blockSyscallTime int64
- blockSweepTime int64
- blockGCTime int64
- blockSchedTime int64
-
- activeRegions []*UserRegionDesc // stack of active regions
-}
-
-// GoroutineStats generates statistics for all goroutines in the trace.
-func GoroutineStats(events []*Event) map[uint64]*GDesc {
- gs := make(map[uint64]*GDesc)
- var lastTs int64
- var gcStartTime int64 // gcStartTime == 0 indicates gc is inactive.
- for _, ev := range events {
- lastTs = ev.Ts
- switch ev.Type {
- case EvGoCreate:
- g := &GDesc{ID: ev.Args[0], CreationTime: ev.Ts, gdesc: new(gdesc)}
- g.blockSchedTime = ev.Ts
- // When a goroutine is newly created, inherit the task
- // of the active region. For ease handling of this
- // case, we create a fake region description with the
- // task id. This isn't strictly necessary as this
- // goroutine may not be associated with the task, but
- // it can be convenient to see all children created
- // during a region.
- if creatorG := gs[ev.G]; creatorG != nil && len(creatorG.gdesc.activeRegions) > 0 {
- regions := creatorG.gdesc.activeRegions
- s := regions[len(regions)-1]
- if s.TaskID != 0 {
- g.gdesc.activeRegions = []*UserRegionDesc{
- {TaskID: s.TaskID, Start: ev},
- }
- }
- }
- gs[g.ID] = g
- case EvGoStart, EvGoStartLabel:
- g := gs[ev.G]
- if g.PC == 0 && len(ev.Stk) > 0 {
- g.PC = ev.Stk[0].PC
- g.Name = ev.Stk[0].Fn
- }
- g.lastStartTime = ev.Ts
- if g.StartTime == 0 {
- g.StartTime = ev.Ts
- }
- if g.blockSchedTime != 0 {
- g.SchedWaitTime += ev.Ts - g.blockSchedTime
- g.blockSchedTime = 0
- }
- case EvGoEnd, EvGoStop:
- g := gs[ev.G]
- g.finalize(ev.Ts, gcStartTime, ev)
- case EvGoBlockSend, EvGoBlockRecv, EvGoBlockSelect,
- EvGoBlockSync, EvGoBlockCond:
- g := gs[ev.G]
- g.ExecTime += ev.Ts - g.lastStartTime
- g.lastStartTime = 0
- g.blockSyncTime = ev.Ts
- case EvGoSched, EvGoPreempt:
- g := gs[ev.G]
- g.ExecTime += ev.Ts - g.lastStartTime
- g.lastStartTime = 0
- g.blockSchedTime = ev.Ts
- case EvGoSleep, EvGoBlock:
- g := gs[ev.G]
- g.ExecTime += ev.Ts - g.lastStartTime
- g.lastStartTime = 0
- case EvGoBlockNet:
- g := gs[ev.G]
- g.ExecTime += ev.Ts - g.lastStartTime
- g.lastStartTime = 0
- g.blockNetTime = ev.Ts
- case EvGoBlockGC:
- g := gs[ev.G]
- g.ExecTime += ev.Ts - g.lastStartTime
- g.lastStartTime = 0
- g.blockGCTime = ev.Ts
- case EvGoUnblock:
- g := gs[ev.Args[0]]
- if g.blockNetTime != 0 {
- g.IOTime += ev.Ts - g.blockNetTime
- g.blockNetTime = 0
- }
- if g.blockSyncTime != 0 {
- g.BlockTime += ev.Ts - g.blockSyncTime
- g.blockSyncTime = 0
- }
- g.blockSchedTime = ev.Ts
- case EvGoSysBlock:
- g := gs[ev.G]
- g.ExecTime += ev.Ts - g.lastStartTime
- g.lastStartTime = 0
- g.blockSyscallTime = ev.Ts
- case EvGoSysExit:
- g := gs[ev.G]
- if g.blockSyscallTime != 0 {
- g.SyscallTime += ev.Ts - g.blockSyscallTime
- g.blockSyscallTime = 0
- }
- g.blockSchedTime = ev.Ts
- case EvGCSweepStart:
- g := gs[ev.G]
- if g != nil {
- // Sweep can happen during GC on system goroutine.
- g.blockSweepTime = ev.Ts
- }
- case EvGCSweepDone:
- g := gs[ev.G]
- if g != nil && g.blockSweepTime != 0 {
- g.SweepTime += ev.Ts - g.blockSweepTime
- g.blockSweepTime = 0
- }
- case EvGCStart:
- gcStartTime = ev.Ts
- case EvGCDone:
- for _, g := range gs {
- if g.EndTime != 0 {
- continue
- }
- if gcStartTime < g.CreationTime {
- g.GCTime += ev.Ts - g.CreationTime
- } else {
- g.GCTime += ev.Ts - gcStartTime
- }
- }
- gcStartTime = 0 // indicates gc is inactive.
- case EvUserRegion:
- g := gs[ev.G]
- switch mode := ev.Args[1]; mode {
- case 0: // region start
- g.activeRegions = append(g.activeRegions, &UserRegionDesc{
- Name: ev.SArgs[0],
- TaskID: ev.Args[0],
- Start: ev,
- GExecutionStat: g.snapshotStat(lastTs, gcStartTime),
- })
- case 1: // region end
- var sd *UserRegionDesc
- if regionStk := g.activeRegions; len(regionStk) > 0 {
- n := len(regionStk)
- sd = regionStk[n-1]
- regionStk = regionStk[:n-1] // pop
- g.activeRegions = regionStk
- } else {
- sd = &UserRegionDesc{
- Name: ev.SArgs[0],
- TaskID: ev.Args[0],
- }
- }
- sd.GExecutionStat = g.snapshotStat(lastTs, gcStartTime).sub(sd.GExecutionStat)
- sd.End = ev
- g.Regions = append(g.Regions, sd)
- }
- }
- }
-
- for _, g := range gs {
- g.finalize(lastTs, gcStartTime, nil)
-
- // sort based on region start time
- sort.Slice(g.Regions, func(i, j int) bool {
- x := g.Regions[i].Start
- y := g.Regions[j].Start
- if x == nil {
- return true
- }
- if y == nil {
- return false
- }
- return x.Ts < y.Ts
- })
-
- g.gdesc = nil
- }
-
- return gs
-}
-
-// RelatedGoroutines finds a set of goroutines related to goroutine goid.
-func RelatedGoroutines(events []*Event, goid uint64) map[uint64]bool {
- // BFS of depth 2 over "unblock" edges
- // (what goroutines unblock goroutine goid?).
- gmap := make(map[uint64]bool)
- gmap[goid] = true
- for i := 0; i < 2; i++ {
- gmap1 := make(map[uint64]bool)
- for g := range gmap {
- gmap1[g] = true
- }
- for _, ev := range events {
- if ev.Type == EvGoUnblock && gmap[ev.Args[0]] {
- gmap1[ev.G] = true
- }
- }
- gmap = gmap1
- }
- gmap[0] = true // for GC events
- return gmap
-}
-
-func IsSystemGoroutine(entryFn string) bool {
- // This mimics runtime.isSystemGoroutine as closely as
- // possible.
- // Also, locked g in extra M (with empty entryFn) is system goroutine.
- return entryFn == "" || entryFn != "runtime.main" && strings.HasPrefix(entryFn, "runtime.")
-}
diff --git a/src/internal/trace/mkcanned.bash b/src/internal/trace/mkcanned.bash
deleted file mode 100755
index 879cf1c500..0000000000
--- a/src/internal/trace/mkcanned.bash
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env bash
-# Copyright 2016 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.
-
-# mkcanned.bash creates canned traces for the trace test suite using
-# the current Go version.
-
-set -e
-
-if [ $# != 1 ]; then
- echo "usage: $0 <label>" >&2
- exit 1
-fi
-
-go test -run '^$' -bench ClientServerParallel4 -benchtime 10x -trace "testdata/http_$1_good" net/http
-go test -run 'TraceStress$|TraceStressStartStop$|TestUserTaskRegion$' runtime/trace -savetraces
-mv ../../runtime/trace/TestTraceStress.trace "testdata/stress_$1_good"
-mv ../../runtime/trace/TestTraceStressStartStop.trace "testdata/stress_start_stop_$1_good"
-mv ../../runtime/trace/TestUserTaskRegion.trace "testdata/user_task_region_$1_good"
diff --git a/src/internal/trace/order.go b/src/internal/trace/order.go
deleted file mode 100644
index 07a6e13ffe..0000000000
--- a/src/internal/trace/order.go
+++ /dev/null
@@ -1,285 +0,0 @@
-// Copyright 2016 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.
-
-package trace
-
-import (
- "fmt"
- "sort"
-)
-
-type eventBatch struct {
- events []*Event
- selected bool
-}
-
-type orderEvent struct {
- ev *Event
- batch int
- g uint64
- init gState
- next gState
-}
-
-type gStatus int
-
-type gState struct {
- seq uint64
- status gStatus
-}
-
-const (
- gDead gStatus = iota
- gRunnable
- gRunning
- gWaiting
-
- unordered = ^uint64(0)
- garbage = ^uint64(0) - 1
- noseq = ^uint64(0)
- seqinc = ^uint64(0) - 1
-)
-
-// order1007 merges a set of per-P event batches into a single, consistent stream.
-// The high level idea is as follows. Events within an individual batch are in
-// correct order, because they are emitted by a single P. So we need to produce
-// a correct interleaving of the batches. To do this we take first unmerged event
-// from each batch (frontier). Then choose subset that is "ready" to be merged,
-// that is, events for which all dependencies are already merged. Then we choose
-// event with the lowest timestamp from the subset, merge it and repeat.
-// This approach ensures that we form a consistent stream even if timestamps are
-// incorrect (condition observed on some machines).
-func order1007(m map[int][]*Event) (events []*Event, err error) {
- pending := 0
- // The ordering of CPU profile sample events in the data stream is based on
- // when each run of the signal handler was able to acquire the spinlock,
- // with original timestamps corresponding to when ReadTrace pulled the data
- // off of the profBuf queue. Re-sort them by the timestamp we captured
- // inside the signal handler.
- sort.Stable(eventList(m[ProfileP]))
- var batches []*eventBatch
- for _, v := range m {
- pending += len(v)
- batches = append(batches, &eventBatch{v, false})
- }
- gs := make(map[uint64]gState)
- var frontier []orderEvent
- for ; pending != 0; pending-- {
- for i, b := range batches {
- if b.selected || len(b.events) == 0 {
- continue
- }
- ev := b.events[0]
- g, init, next := stateTransition(ev)
- if !transitionReady(g, gs[g], init) {
- continue
- }
- frontier = append(frontier, orderEvent{ev, i, g, init, next})
- b.events = b.events[1:]
- b.selected = true
- // Get rid of "Local" events, they are intended merely for ordering.
- switch ev.Type {
- case EvGoStartLocal:
- ev.Type = EvGoStart
- case EvGoUnblockLocal:
- ev.Type = EvGoUnblock
- case EvGoSysExitLocal:
- ev.Type = EvGoSysExit
- }
- }
- if len(frontier) == 0 {
- return nil, fmt.Errorf("no consistent ordering of events possible")
- }
- sort.Sort(orderEventList(frontier))
- f := frontier[0]
- frontier[0] = frontier[len(frontier)-1]
- frontier = frontier[:len(frontier)-1]
- events = append(events, f.ev)
- transition(gs, f.g, f.init, f.next)
- if !batches[f.batch].selected {
- panic("frontier batch is not selected")
- }
- batches[f.batch].selected = false
- }
-
- // At this point we have a consistent stream of events.
- // Make sure time stamps respect the ordering.
- // The tests will skip (not fail) the test case if they see this error.
- if !sort.IsSorted(eventList(events)) {
- return nil, ErrTimeOrder
- }
-
- // The last part is giving correct timestamps to EvGoSysExit events.
- // The problem with EvGoSysExit is that actual syscall exit timestamp (ev.Args[2])
- // is potentially acquired long before event emission. So far we've used
- // timestamp of event emission (ev.Ts).
- // We could not set ev.Ts = ev.Args[2] earlier, because it would produce
- // seemingly broken timestamps (misplaced event).
- // We also can't simply update the timestamp and resort events, because
- // if timestamps are broken we will misplace the event and later report
- // logically broken trace (instead of reporting broken timestamps).
- lastSysBlock := make(map[uint64]int64)
- for _, ev := range events {
- switch ev.Type {
- case EvGoSysBlock, EvGoInSyscall:
- lastSysBlock[ev.G] = ev.Ts
- case EvGoSysExit:
- ts := int64(ev.Args[2])
- if ts == 0 {
- continue
- }
- block := lastSysBlock[ev.G]
- if block == 0 {
- return nil, fmt.Errorf("stray syscall exit")
- }
- if ts < block {
- return nil, ErrTimeOrder
- }
- ev.Ts = ts
- }
- }
- sort.Stable(eventList(events))
-
- return
-}
-
-// stateTransition returns goroutine state (sequence and status) when the event
-// becomes ready for merging (init) and the goroutine state after the event (next).
-func stateTransition(ev *Event) (g uint64, init, next gState) {
- switch ev.Type {
- case EvGoCreate:
- g = ev.Args[0]
- init = gState{0, gDead}
- next = gState{1, gRunnable}
- case EvGoWaiting, EvGoInSyscall:
- g = ev.G
- init = gState{1, gRunnable}
- next = gState{2, gWaiting}
- case EvGoStart, EvGoStartLabel:
- g = ev.G
- init = gState{ev.Args[1], gRunnable}
- next = gState{ev.Args[1] + 1, gRunning}
- case EvGoStartLocal:
- // noseq means that this event is ready for merging as soon as
- // frontier reaches it (EvGoStartLocal is emitted on the same P
- // as the corresponding EvGoCreate/EvGoUnblock, and thus the latter
- // is already merged).
- // seqinc is a stub for cases when event increments g sequence,
- // but since we don't know current seq we also don't know next seq.
- g = ev.G
- init = gState{noseq, gRunnable}
- next = gState{seqinc, gRunning}
- case EvGoBlock, EvGoBlockSend, EvGoBlockRecv, EvGoBlockSelect,
- EvGoBlockSync, EvGoBlockCond, EvGoBlockNet, EvGoSleep,
- EvGoSysBlock, EvGoBlockGC:
- g = ev.G
- init = gState{noseq, gRunning}
- next = gState{noseq, gWaiting}
- case EvGoSched, EvGoPreempt:
- g = ev.G
- init = gState{noseq, gRunning}
- next = gState{noseq, gRunnable}
- case EvGoUnblock, EvGoSysExit:
- g = ev.Args[0]
- init = gState{ev.Args[1], gWaiting}
- next = gState{ev.Args[1] + 1, gRunnable}
- case EvGoUnblockLocal, EvGoSysExitLocal:
- g = ev.Args[0]
- init = gState{noseq, gWaiting}
- next = gState{seqinc, gRunnable}
- case EvGCStart:
- g = garbage
- init = gState{ev.Args[0], gDead}
- next = gState{ev.Args[0] + 1, gDead}
- default:
- // no ordering requirements
- g = unordered
- }
- return
-}
-
-func transitionReady(g uint64, curr, init gState) bool {
- return g == unordered || (init.seq == noseq || init.seq == curr.seq) && init.status == curr.status
-}
-
-func transition(gs map[uint64]gState, g uint64, init, next gState) {
- if g == unordered {
- return
- }
- curr := gs[g]
- if !transitionReady(g, curr, init) {
- panic("event sequences are broken")
- }
- switch next.seq {
- case noseq:
- next.seq = curr.seq
- case seqinc:
- next.seq = curr.seq + 1
- }
- gs[g] = next
-}
-
-// order1005 merges a set of per-P event batches into a single, consistent stream.
-func order1005(m map[int][]*Event) (events []*Event, err error) {
- for _, batch := range m {
- events = append(events, batch...)
- }
- for _, ev := range events {
- if ev.Type == EvGoSysExit {
- // EvGoSysExit emission is delayed until the thread has a P.
- // Give it the real sequence number and time stamp.
- ev.seq = int64(ev.Args[1])
- if ev.Args[2] != 0 {
- ev.Ts = int64(ev.Args[2])
- }
- }
- }
- sort.Sort(eventSeqList(events))
- if !sort.IsSorted(eventList(events)) {
- return nil, ErrTimeOrder
- }
- return
-}
-
-type orderEventList []orderEvent
-
-func (l orderEventList) Len() int {
- return len(l)
-}
-
-func (l orderEventList) Less(i, j int) bool {
- return l[i].ev.Ts < l[j].ev.Ts
-}
-
-func (l orderEventList) Swap(i, j int) {
- l[i], l[j] = l[j], l[i]
-}
-
-type eventList []*Event
-
-func (l eventList) Len() int {
- return len(l)
-}
-
-func (l eventList) Less(i, j int) bool {
- return l[i].Ts < l[j].Ts
-}
-
-func (l eventList) Swap(i, j int) {
- l[i], l[j] = l[j], l[i]
-}
-
-type eventSeqList []*Event
-
-func (l eventSeqList) Len() int {
- return len(l)
-}
-
-func (l eventSeqList) Less(i, j int) bool {
- return l[i].seq < l[j].seq
-}
-
-func (l eventSeqList) Swap(i, j int) {
- l[i], l[j] = l[j], l[i]
-}
diff --git a/src/internal/trace/parser.go b/src/internal/trace/parser.go
index 3bbfbebab4..d6fff84d55 100644
--- a/src/internal/trace/parser.go
+++ b/src/internal/trace/parser.go
@@ -4,62 +4,6 @@
package trace
-import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "math/rand"
- "os"
- "os/exec"
- "path/filepath"
- "runtime"
- "strconv"
- "strings"
- _ "unsafe"
-)
-
-func goCmd() string {
- var exeSuffix string
- if runtime.GOOS == "windows" {
- exeSuffix = ".exe"
- }
- path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
- if _, err := os.Stat(path); err == nil {
- return path
- }
- return "go"
-}
-
-// Event describes one event in the trace.
-type Event struct {
- Off int // offset in input file (for debugging and error reporting)
- Type byte // one of Ev*
- seq int64 // sequence number
- Ts int64 // timestamp in nanoseconds
- P int // P on which the event happened (can be one of TimerP, NetpollP, SyscallP)
- G uint64 // G on which the event happened
- StkID uint64 // unique stack ID
- Stk []*Frame // stack trace (can be empty)
- Args [3]uint64 // event-type-specific arguments
- SArgs []string // event-type-specific string args
- // linked event (can be nil), depends on event type:
- // for GCStart: the GCStop
- // for GCSTWStart: the GCSTWDone
- // for GCSweepStart: the GCSweepDone
- // for GoCreate: first GoStart of the created goroutine
- // for GoStart/GoStartLabel: the associated GoEnd, GoBlock or other blocking event
- // for GoSched/GoPreempt: the next GoStart
- // for GoBlock and other blocking events: the unblock event
- // for GoUnblock: the associated GoStart
- // for blocking GoSysCall: the associated GoSysExit
- // for GoSysExit: the next GoStart
- // for GCMarkAssistStart: the associated GCMarkAssistDone
- // for UserTaskCreate: the UserTaskEnd
- // for UserRegion: if the start region, the corresponding UserRegion end event
- Link *Event
-}
-
// Frame is a frame in stack traces.
type Frame struct {
PC uint64
@@ -78,972 +22,6 @@ const (
ProfileP // depicts recording of CPU profile samples
)
-// ParseResult is the result of Parse.
-type ParseResult struct {
- // Events is the sorted list of Events in the trace.
- Events []*Event
- // Stacks is the stack traces keyed by stack IDs from the trace.
- Stacks map[uint64][]*Frame
-}
-
-// Parse parses, post-processes and verifies the trace.
-func Parse(r io.Reader, bin string) (ParseResult, error) {
- ver, res, err := parse(r, bin)
- if err != nil {
- return ParseResult{}, err
- }
- if ver < 1007 && bin == "" {
- return ParseResult{}, fmt.Errorf("for traces produced by go 1.6 or below, the binary argument must be provided")
- }
- return res, nil
-}
-
-// parse parses, post-processes and verifies the trace. It returns the
-// trace version and the list of events.
-func parse(r io.Reader, bin string) (int, ParseResult, error) {
- ver, rawEvents, strings, err := readTrace(r)
- if err != nil {
- return 0, ParseResult{}, err
- }
- events, stacks, err := parseEvents(ver, rawEvents, strings)
- if err != nil {
- return 0, ParseResult{}, err
- }
- events = removeFutile(events)
- err = postProcessTrace(ver, events)
- if err != nil {
- return 0, ParseResult{}, err
- }
- // Attach stack traces.
- for _, ev := range events {
- if ev.StkID != 0 {
- ev.Stk = stacks[ev.StkID]
- }
- }
- if ver < 1007 && bin != "" {
- if err := symbolize(events, bin); err != nil {
- return 0, ParseResult{}, err
- }
- }
- return ver, ParseResult{Events: events, Stacks: stacks}, nil
-}
-
-// rawEvent is a helper type used during parsing.
-type rawEvent struct {
- off int
- typ byte
- args []uint64
- sargs []string
-}
-
-func ReadVersion(r io.Reader) (ver int, off int, err error) {
- // Read and validate trace header.
- var buf [16]byte
- off, err = io.ReadFull(r, buf[:])
- if err != nil {
- err = fmt.Errorf("failed to read header: read %v, err %v", off, err)
- return
- }
- ver, err = parseHeader(buf[:])
- return
-}
-
-// readTrace does wire-format parsing and verification.
-// It does not care about specific event types and argument meaning.
-func readTrace(r io.Reader) (ver int, events []rawEvent, strings map[uint64]string, err error) {
- var off int
- ver, off, err = ReadVersion(r)
- if err != nil {
- return
- }
- switch ver {
- case 1005, 1007, 1008, 1009, 1010, 1011, 1019, 1021:
- // Note: When adding a new version, confirm that canned traces from the
- // old version are part of the test suite. Add them using mkcanned.bash.
- break
- default:
- err = fmt.Errorf("unsupported trace file version %v.%v (update Go toolchain) %v", ver/1000, ver%1000, ver)
- return
- }
-
- // Read events.
- var buf [16]byte
- strings = make(map[uint64]string)
- for {
- // Read event type and number of arguments (1 byte).
- off0 := off
- var n int
- n, err = r.Read(buf[:1])
- if err == io.EOF {
- err = nil
- break
- }
- if err != nil || n != 1 {
- err = fmt.Errorf("failed to read trace at offset 0x%x: n=%v err=%v", off0, n, err)
- return
- }
- off += n
- typ := buf[0] << 2 >> 2
- narg := buf[0]>>6 + 1
- inlineArgs := byte(4)
- if ver < 1007 {
- narg++
- inlineArgs++
- }
- if typ == EvNone || typ >= EvCount || EventDescriptions[typ].minVersion > ver {
- err = fmt.Errorf("unknown event type %v at offset 0x%x", typ, off0)
- return
- }
- if typ == EvString {
- // String dictionary entry [ID, length, string].
- var id uint64
- id, off, err = readVal(r, off)
- if err != nil {
- return
- }
- if id == 0 {
- err = fmt.Errorf("string at offset %d has invalid id 0", off)
- return
- }
- if strings[id] != "" {
- err = fmt.Errorf("string at offset %d has duplicate id %v", off, id)
- return
- }
- var ln uint64
- ln, off, err = readVal(r, off)
- if err != nil {
- return
- }
- if ln == 0 {
- err = fmt.Errorf("string at offset %d has invalid length 0", off)
- return
- }
- if ln > 1e6 {
- err = fmt.Errorf("string at offset %d has too large length %v", off, ln)
- return
- }
- buf := make([]byte, ln)
- var n int
- n, err = io.ReadFull(r, buf)
- if err != nil {
- err = fmt.Errorf("failed to read trace at offset %d: read %v, want %v, error %v", off, n, ln, err)
- return
- }
- off += n
- strings[id] = string(buf)
- continue
- }
- ev := rawEvent{typ: typ, off: off0}
- if narg < inlineArgs {
- for i := 0; i < int(narg); i++ {
- var v uint64
- v, off, err = readVal(r, off)
- if err != nil {
- err = fmt.Errorf("failed to read event %v argument at offset %v (%v)", typ, off, err)
- return
- }
- ev.args = append(ev.args, v)
- }
- } else {
- // More than inlineArgs args, the first value is length of the event in bytes.
- var v uint64
- v, off, err = readVal(r, off)
- if err != nil {
- err = fmt.Errorf("failed to read event %v argument at offset %v (%v)", typ, off, err)
- return
- }
- evLen := v
- off1 := off
- for evLen > uint64(off-off1) {
- v, off, err = readVal(r, off)
- if err != nil {
- err = fmt.Errorf("failed to read event %v argument at offset %v (%v)", typ, off, err)
- return
- }
- ev.args = append(ev.args, v)
- }
- if evLen != uint64(off-off1) {
- err = fmt.Errorf("event has wrong length at offset 0x%x: want %v, got %v", off0, evLen, off-off1)
- return
- }
- }
- switch ev.typ {
- case EvUserLog: // EvUserLog records are followed by a value string of length ev.args[len(ev.args)-1]
- var s string
- s, off, err = readStr(r, off)
- ev.sargs = append(ev.sargs, s)
- }
- events = append(events, ev)
- }
- return
-}
-
-func readStr(r io.Reader, off0 int) (s string, off int, err error) {
- var sz uint64
- sz, off, err = readVal(r, off0)
- if err != nil || sz == 0 {
- return "", off, err
- }
- if sz > 1e6 {
- return "", off, fmt.Errorf("string at offset %d is too large (len=%d)", off, sz)
- }
- buf := make([]byte, sz)
- n, err := io.ReadFull(r, buf)
- if err != nil || sz != uint64(n) {
- return "", off + n, fmt.Errorf("failed to read trace at offset %d: read %v, want %v, error %v", off, n, sz, err)
- }
- return string(buf), off + n, nil
-}
-
-// parseHeader parses trace header of the form "go 1.7 trace\x00\x00\x00\x00"
-// and returns parsed version as 1007.
-func parseHeader(buf []byte) (int, error) {
- if len(buf) != 16 {
- return 0, fmt.Errorf("bad header length")
- }
- if buf[0] != 'g' || buf[1] != 'o' || buf[2] != ' ' ||
- buf[3] < '1' || buf[3] > '9' ||
- buf[4] != '.' ||
- buf[5] < '1' || buf[5] > '9' {
- return 0, fmt.Errorf("not a trace file")
- }
- ver := int(buf[5] - '0')
- i := 0
- for ; buf[6+i] >= '0' && buf[6+i] <= '9' && i < 2; i++ {
- ver = ver*10 + int(buf[6+i]-'0')
- }
- ver += int(buf[3]-'0') * 1000
- if !bytes.Equal(buf[6+i:], []byte(" trace\x00\x00\x00\x00")[:10-i]) {
- return 0, fmt.Errorf("not a trace file")
- }
- return ver, nil
-}
-
-// Parse events transforms raw events into events.
-// It does analyze and verify per-event-type arguments.
-func parseEvents(ver int, rawEvents []rawEvent, strings map[uint64]string) (events []*Event, stacks map[uint64][]*Frame, err error) {
- var ticksPerSec, lastSeq, lastTs int64
- var lastG uint64
- var lastP int
- timerGoids := make(map[uint64]bool)
- lastGs := make(map[int]uint64) // last goroutine running on P
- stacks = make(map[uint64][]*Frame)
- batches := make(map[int][]*Event) // events by P
- for _, raw := range rawEvents {
- desc := EventDescriptions[raw.typ]
- if desc.Name == "" {
- err = fmt.Errorf("missing description for event type %v", raw.typ)
- return
- }
- narg := argNum(raw, ver)
- if len(raw.args) != narg {
- err = fmt.Errorf("%v has wrong number of arguments at offset 0x%x: want %v, got %v",
- desc.Name, raw.off, narg, len(raw.args))
- return
- }
- switch raw.typ {
- case EvBatch:
- lastGs[lastP] = lastG
- lastP = int(raw.args[0])
- lastG = lastGs[lastP]
- if ver < 1007 {
- lastSeq = int64(raw.args[1])
- lastTs = int64(raw.args[2])
- } else {
- lastTs = int64(raw.args[1])
- }
- case EvFrequency:
- ticksPerSec = int64(raw.args[0])
- if ticksPerSec <= 0 {
- // The most likely cause for this is tick skew on different CPUs.
- // For example, solaris/amd64 seems to have wildly different
- // ticks on different CPUs.
- err = ErrTimeOrder
- return
- }
- case EvTimerGoroutine:
- timerGoids[raw.args[0]] = true
- case EvStack:
- if len(raw.args) < 2 {
- err = fmt.Errorf("EvStack has wrong number of arguments at offset 0x%x: want at least 2, got %v",
- raw.off, len(raw.args))
- return
- }
- size := raw.args[1]
- if size > 1000 {
- err = fmt.Errorf("EvStack has bad number of frames at offset 0x%x: %v",
- raw.off, size)
- return
- }
- want := 2 + 4*size
- if ver < 1007 {
- want = 2 + size
- }
- if uint64(len(raw.args)) != want {
- err = fmt.Errorf("EvStack has wrong number of arguments at offset 0x%x: want %v, got %v",
- raw.off, want, len(raw.args))
- return
- }
- id := raw.args[0]
- if id != 0 && size > 0 {
- stk := make([]*Frame, size)
- for i := 0; i < int(size); i++ {
- if ver < 1007 {
- stk[i] = &Frame{PC: raw.args[2+i]}
- } else {
- pc := raw.args[2+i*4+0]
- fn := raw.args[2+i*4+1]
- file := raw.args[2+i*4+2]
- line := raw.args[2+i*4+3]
- stk[i] = &Frame{PC: pc, Fn: strings[fn], File: strings[file], Line: int(line)}
- }
- }
- stacks[id] = stk
- }
- default:
- e := &Event{Off: raw.off, Type: raw.typ, P: lastP, G: lastG}
- var argOffset int
- if ver < 1007 {
- e.seq = lastSeq + int64(raw.args[0])
- e.Ts = lastTs + int64(raw.args[1])
- lastSeq = e.seq
- argOffset = 2
- } else {
- e.Ts = lastTs + int64(raw.args[0])
- argOffset = 1
- }
- lastTs = e.Ts
- for i := argOffset; i < narg; i++ {
- if i == narg-1 && desc.Stack {
- e.StkID = raw.args[i]
- } else {
- e.Args[i-argOffset] = raw.args[i]
- }
- }
- switch raw.typ {
- case EvGoStart, EvGoStartLocal, EvGoStartLabel:
- lastG = e.Args[0]
- e.G = lastG
- if raw.typ == EvGoStartLabel {
- e.SArgs = []string{strings[e.Args[2]]}
- }
- case EvSTWStart:
- e.G = 0
- if ver < 1021 {
- switch e.Args[0] {
- case 0:
- e.SArgs = []string{"mark termination"}
- case 1:
- e.SArgs = []string{"sweep termination"}
- default:
- err = fmt.Errorf("unknown STW kind %d", e.Args[0])
- return
- }
- } else if ver == 1021 {
- if kind := e.Args[0]; kind < uint64(len(stwReasonStringsGo121)) {
- e.SArgs = []string{stwReasonStringsGo121[kind]}
- } else {
- e.SArgs = []string{"unknown"}
- }
- } else {
- // Can't make any assumptions.
- e.SArgs = []string{"unknown"}
- }
- case EvGCStart, EvGCDone, EvSTWDone:
- e.G = 0
- case EvGoEnd, EvGoStop, EvGoSched, EvGoPreempt,
- EvGoSleep, EvGoBlock, EvGoBlockSend, EvGoBlockRecv,
- EvGoBlockSelect, EvGoBlockSync, EvGoBlockCond, EvGoBlockNet,
- EvGoSysBlock, EvGoBlockGC:
- lastG = 0
- case EvGoSysExit, EvGoWaiting, EvGoInSyscall:
- e.G = e.Args[0]
- case EvUserTaskCreate:
- // e.Args 0: taskID, 1:parentID, 2:nameID
- e.SArgs = []string{strings[e.Args[2]]}
- case EvUserRegion:
- // e.Args 0: taskID, 1: mode, 2:nameID
- e.SArgs = []string{strings[e.Args[2]]}
- case EvUserLog:
- // e.Args 0: taskID, 1:keyID, 2: stackID
- e.SArgs = []string{strings[e.Args[1]], raw.sargs[0]}
- case EvCPUSample:
- e.Ts = int64(e.Args[0])
- e.P = int(e.Args[1])
- e.G = e.Args[2]
- e.Args[0] = 0
- }
- switch raw.typ {
- default:
- batches[lastP] = append(batches[lastP], e)
- case EvCPUSample:
- // Most events are written out by the active P at the exact
- // moment they describe. CPU profile samples are different
- // because they're written to the tracing log after some delay,
- // by a separate worker goroutine, into a separate buffer.
- //
- // We keep these in their own batch until all of the batches are
- // merged in timestamp order. We also (right before the merge)
- // re-sort these events by the timestamp captured in the
- // profiling signal handler.
- batches[ProfileP] = append(batches[ProfileP], e)
- }
- }
- }
- if len(batches) == 0 {
- err = fmt.Errorf("trace is empty")
- return
- }
- if ticksPerSec == 0 {
- err = fmt.Errorf("no EvFrequency event")
- return
- }
- if BreakTimestampsForTesting {
- var batchArr [][]*Event
- for _, batch := range batches {
- batchArr = append(batchArr, batch)
- }
- for i := 0; i < 5; i++ {
- batch := batchArr[rand.Intn(len(batchArr))]
- batch[rand.Intn(len(batch))].Ts += int64(rand.Intn(2000) - 1000)
- }
- }
- if ver < 1007 {
- events, err = order1005(batches)
- } else {
- events, err = order1007(batches)
- }
- if err != nil {
- return
- }
-
- // Translate cpu ticks to real time.
- minTs := events[0].Ts
- // Use floating point to avoid integer overflows.
- freq := 1e9 / float64(ticksPerSec)
- for _, ev := range events {
- ev.Ts = int64(float64(ev.Ts-minTs) * freq)
- // Move timers and syscalls to separate fake Ps.
- if timerGoids[ev.G] && ev.Type == EvGoUnblock {
- ev.P = TimerP
- }
- if ev.Type == EvGoSysExit {
- ev.P = SyscallP
- }
- }
-
- return
-}
-
-// removeFutile removes all constituents of futile wakeups (block, unblock, start).
-// For example, a goroutine was unblocked on a mutex, but another goroutine got
-// ahead and acquired the mutex before the first goroutine is scheduled,
-// so the first goroutine has to block again. Such wakeups happen on buffered
-// channels and sync.Mutex, but are generally not interesting for end user.
-func removeFutile(events []*Event) []*Event {
- // Two non-trivial aspects:
- // 1. A goroutine can be preempted during a futile wakeup and migrate to another P.
- // We want to remove all of that.
- // 2. Tracing can start in the middle of a futile wakeup.
- // That is, we can see a futile wakeup event w/o the actual wakeup before it.
- // postProcessTrace runs after us and ensures that we leave the trace in a consistent state.
-
- // Phase 1: determine futile wakeup sequences.
- type G struct {
- futile bool
- wakeup []*Event // wakeup sequence (subject for removal)
- }
- gs := make(map[uint64]G)
- futile := make(map[*Event]bool)
- for _, ev := range events {
- switch ev.Type {
- case EvGoUnblock:
- g := gs[ev.Args[0]]
- g.wakeup = []*Event{ev}
- gs[ev.Args[0]] = g
- case EvGoStart, EvGoPreempt, EvFutileWakeup:
- g := gs[ev.G]
- g.wakeup = append(g.wakeup, ev)
- if ev.Type == EvFutileWakeup {
- g.futile = true
- }
- gs[ev.G] = g
- case EvGoBlock, EvGoBlockSend, EvGoBlockRecv, EvGoBlockSelect, EvGoBlockSync, EvGoBlockCond:
- g := gs[ev.G]
- if g.futile {
- futile[ev] = true
- for _, ev1 := range g.wakeup {
- futile[ev1] = true
- }
- }
- delete(gs, ev.G)
- }
- }
-
- // Phase 2: remove futile wakeup sequences.
- newEvents := events[:0] // overwrite the original slice
- for _, ev := range events {
- if !futile[ev] {
- newEvents = append(newEvents, ev)
- }
- }
- return newEvents
-}
-
-// ErrTimeOrder is returned by Parse when the trace contains
-// time stamps that do not respect actual event ordering.
-var ErrTimeOrder = fmt.Errorf("time stamps out of order")
-
-// postProcessTrace does inter-event verification and information restoration.
-// The resulting trace is guaranteed to be consistent
-// (for example, a P does not run two Gs at the same time, or a G is indeed
-// blocked before an unblock event).
-func postProcessTrace(ver int, events []*Event) error {
- const (
- gDead = iota
- gRunnable
- gRunning
- gWaiting
- )
- type gdesc struct {
- state int
- ev *Event
- evStart *Event
- evCreate *Event
- evMarkAssist *Event
- }
- type pdesc struct {
- running bool
- g uint64
- evSTW *Event
- evSweep *Event
- }
-
- gs := make(map[uint64]gdesc)
- ps := make(map[int]pdesc)
- tasks := make(map[uint64]*Event) // task id to task creation events
- activeRegions := make(map[uint64][]*Event) // goroutine id to stack of regions
- gs[0] = gdesc{state: gRunning}
- var evGC, evSTW *Event
-
- checkRunning := func(p pdesc, g gdesc, ev *Event, allowG0 bool) error {
- name := EventDescriptions[ev.Type].Name
- if g.state != gRunning {
- return fmt.Errorf("g %v is not running while %v (offset %v, time %v)", ev.G, name, ev.Off, ev.Ts)
- }
- if p.g != ev.G {
- return fmt.Errorf("p %v is not running g %v while %v (offset %v, time %v)", ev.P, ev.G, name, ev.Off, ev.Ts)
- }
- if !allowG0 && ev.G == 0 {
- return fmt.Errorf("g 0 did %v (offset %v, time %v)", EventDescriptions[ev.Type].Name, ev.Off, ev.Ts)
- }
- return nil
- }
-
- for _, ev := range events {
- g := gs[ev.G]
- p := ps[ev.P]
-
- switch ev.Type {
- case EvProcStart:
- if p.running {
- return fmt.Errorf("p %v is running before start (offset %v, time %v)", ev.P, ev.Off, ev.Ts)
- }
- p.running = true
- case EvProcStop:
- if !p.running {
- return fmt.Errorf("p %v is not running before stop (offset %v, time %v)", ev.P, ev.Off, ev.Ts)
- }
- if p.g != 0 {
- return fmt.Errorf("p %v is running a goroutine %v during stop (offset %v, time %v)", ev.P, p.g, ev.Off, ev.Ts)
- }
- p.running = false
- case EvGCStart:
- if evGC != nil {
- return fmt.Errorf("previous GC is not ended before a new one (offset %v, time %v)", ev.Off, ev.Ts)
- }
- evGC = ev
- // Attribute this to the global GC state.
- ev.P = GCP
- case EvGCDone:
- if evGC == nil {
- return fmt.Errorf("bogus GC end (offset %v, time %v)", ev.Off, ev.Ts)
- }
- evGC.Link = ev
- evGC = nil
- case EvSTWStart:
- evp := &evSTW
- if ver < 1010 {
- // Before 1.10, EvSTWStart was per-P.
- evp = &p.evSTW
- }
- if *evp != nil {
- return fmt.Errorf("previous STW is not ended before a new one (offset %v, time %v)", ev.Off, ev.Ts)
- }
- *evp = ev
- case EvSTWDone:
- evp := &evSTW
- if ver < 1010 {
- // Before 1.10, EvSTWDone was per-P.
- evp = &p.evSTW
- }
- if *evp == nil {
- return fmt.Errorf("bogus STW end (offset %v, time %v)", ev.Off, ev.Ts)
- }
- (*evp).Link = ev
- *evp = nil
- case EvGCSweepStart:
- if p.evSweep != nil {
- return fmt.Errorf("previous sweeping is not ended before a new one (offset %v, time %v)", ev.Off, ev.Ts)
- }
- p.evSweep = ev
- case EvGCMarkAssistStart:
- if g.evMarkAssist != nil {
- return fmt.Errorf("previous mark assist is not ended before a new one (offset %v, time %v)", ev.Off, ev.Ts)
- }
- g.evMarkAssist = ev
- case EvGCMarkAssistDone:
- // Unlike most events, mark assists can be in progress when a
- // goroutine starts tracing, so we can't report an error here.
- if g.evMarkAssist != nil {
- g.evMarkAssist.Link = ev
- g.evMarkAssist = nil
- }
- case EvGCSweepDone:
- if p.evSweep == nil {
- return fmt.Errorf("bogus sweeping end (offset %v, time %v)", ev.Off, ev.Ts)
- }
- p.evSweep.Link = ev
- p.evSweep = nil
- case EvGoWaiting:
- if g.state != gRunnable {
- return fmt.Errorf("g %v is not runnable before EvGoWaiting (offset %v, time %v)", ev.G, ev.Off, ev.Ts)
- }
- g.state = gWaiting
- g.ev = ev
- case EvGoInSyscall:
- if g.state != gRunnable {
- return fmt.Errorf("g %v is not runnable before EvGoInSyscall (offset %v, time %v)", ev.G, ev.Off, ev.Ts)
- }
- g.state = gWaiting
- g.ev = ev
- case EvGoCreate:
- if err := checkRunning(p, g, ev, true); err != nil {
- return err
- }
- if _, ok := gs[ev.Args[0]]; ok {
- return fmt.Errorf("g %v already exists (offset %v, time %v)", ev.Args[0], ev.Off, ev.Ts)
- }
- gs[ev.Args[0]] = gdesc{state: gRunnable, ev: ev, evCreate: ev}
- case EvGoStart, EvGoStartLabel:
- if g.state != gRunnable {
- return fmt.Errorf("g %v is not runnable before start (offset %v, time %v)", ev.G, ev.Off, ev.Ts)
- }
- if p.g != 0 {
- return fmt.Errorf("p %v is already running g %v while start g %v (offset %v, time %v)", ev.P, p.g, ev.G, ev.Off, ev.Ts)
- }
- g.state = gRunning
- g.evStart = ev
- p.g = ev.G
- if g.evCreate != nil {
- if ver < 1007 {
- // +1 because symbolizer expects return pc.
- ev.Stk = []*Frame{{PC: g.evCreate.Args[1] + 1}}
- } else {
- ev.StkID = g.evCreate.Args[1]
- }
- g.evCreate = nil
- }
-
- if g.ev != nil {
- g.ev.Link = ev
- g.ev = nil
- }
- case EvGoEnd, EvGoStop:
- if err := checkRunning(p, g, ev, false); err != nil {
- return err
- }
- g.evStart.Link = ev
- g.evStart = nil
- g.state = gDead
- p.g = 0
-
- if ev.Type == EvGoEnd { // flush all active regions
- regions := activeRegions[ev.G]
- for _, s := range regions {
- s.Link = ev
- }
- delete(activeRegions, ev.G)
- }
-
- case EvGoSched, EvGoPreempt:
- if err := checkRunning(p, g, ev, false); err != nil {
- return err
- }
- g.state = gRunnable
- g.evStart.Link = ev
- g.evStart = nil
- p.g = 0
- g.ev = ev
- case EvGoUnblock:
- if g.state != gRunning {
- return fmt.Errorf("g %v is not running while unpark (offset %v, time %v)", ev.G, ev.Off, ev.Ts)
- }
- if ev.P != TimerP && p.g != ev.G {
- return fmt.Errorf("p %v is not running g %v while unpark (offset %v, time %v)", ev.P, ev.G, ev.Off, ev.Ts)
- }
- g1 := gs[ev.Args[0]]
- if g1.state != gWaiting {
- return fmt.Errorf("g %v is not waiting before unpark (offset %v, time %v)", ev.Args[0], ev.Off, ev.Ts)
- }
- if g1.ev != nil && g1.ev.Type == EvGoBlockNet && ev.P != TimerP {
- ev.P = NetpollP
- }
- if g1.ev != nil {
- g1.ev.Link = ev
- }
- g1.state = gRunnable
- g1.ev = ev
- gs[ev.Args[0]] = g1
- case EvGoSysCall:
- if err := checkRunning(p, g, ev, false); err != nil {
- return err
- }
- g.ev = ev
- case EvGoSysBlock:
- if err := checkRunning(p, g, ev, false); err != nil {
- return err
- }
- g.state = gWaiting
- g.evStart.Link = ev
- g.evStart = nil
- p.g = 0
- case EvGoSysExit:
- if g.state != gWaiting {
- return fmt.Errorf("g %v is not waiting during syscall exit (offset %v, time %v)", ev.G, ev.Off, ev.Ts)
- }
- if g.ev != nil && g.ev.Type == EvGoSysCall {
- g.ev.Link = ev
- }
- g.state = gRunnable
- g.ev = ev
- case EvGoSleep, EvGoBlock, EvGoBlockSend, EvGoBlockRecv,
- EvGoBlockSelect, EvGoBlockSync, EvGoBlockCond, EvGoBlockNet, EvGoBlockGC:
- if err := checkRunning(p, g, ev, false); err != nil {
- return err
- }
- g.state = gWaiting
- g.ev = ev
- g.evStart.Link = ev
- g.evStart = nil
- p.g = 0
- case EvUserTaskCreate:
- taskid := ev.Args[0]
- if prevEv, ok := tasks[taskid]; ok {
- return fmt.Errorf("task id conflicts (id:%d), %q vs %q", taskid, ev, prevEv)
- }
- tasks[ev.Args[0]] = ev
- case EvUserTaskEnd:
- taskid := ev.Args[0]
- if taskCreateEv, ok := tasks[taskid]; ok {
- taskCreateEv.Link = ev
- delete(tasks, taskid)
- }
- case EvUserRegion:
- mode := ev.Args[1]
- regions := activeRegions[ev.G]
- if mode == 0 { // region start
- activeRegions[ev.G] = append(regions, ev) // push
- } else if mode == 1 { // region end
- n := len(regions)
- if n > 0 { // matching region start event is in the trace.
- s := regions[n-1]
- if s.Args[0] != ev.Args[0] || s.SArgs[0] != ev.SArgs[0] { // task id, region name mismatch
- return fmt.Errorf("misuse of region in goroutine %d: span end %q when the inner-most active span start event is %q", ev.G, ev, s)
- }
- // Link region start event with span end event
- s.Link = ev
-
- if n > 1 {
- activeRegions[ev.G] = regions[:n-1]
- } else {
- delete(activeRegions, ev.G)
- }
- }
- } else {
- return fmt.Errorf("invalid user region mode: %q", ev)
- }
- }
-
- gs[ev.G] = g
- ps[ev.P] = p
- }
-
- // TODO(dvyukov): restore stacks for EvGoStart events.
- // TODO(dvyukov): test that all EvGoStart events has non-nil Link.
-
- return nil
-}
-
-// symbolize attaches func/file/line info to stack traces.
-func symbolize(events []*Event, bin string) error {
- // First, collect and dedup all pcs.
- pcs := make(map[uint64]*Frame)
- for _, ev := range events {
- for _, f := range ev.Stk {
- pcs[f.PC] = nil
- }
- }
-
- // Start addr2line.
- cmd := exec.Command(goCmd(), "tool", "addr2line", bin)
- in, err := cmd.StdinPipe()
- if err != nil {
- return fmt.Errorf("failed to pipe addr2line stdin: %v", err)
- }
- cmd.Stderr = os.Stderr
- out, err := cmd.StdoutPipe()
- if err != nil {
- return fmt.Errorf("failed to pipe addr2line stdout: %v", err)
- }
- err = cmd.Start()
- if err != nil {
- return fmt.Errorf("failed to start addr2line: %v", err)
- }
- outb := bufio.NewReader(out)
-
- // Write all pcs to addr2line.
- // Need to copy pcs to an array, because map iteration order is non-deterministic.
- var pcArray []uint64
- for pc := range pcs {
- pcArray = append(pcArray, pc)
- _, err := fmt.Fprintf(in, "0x%x\n", pc-1)
- if err != nil {
- return fmt.Errorf("failed to write to addr2line: %v", err)
- }
- }
- in.Close()
-
- // Read in answers.
- for _, pc := range pcArray {
- fn, err := outb.ReadString('\n')
- if err != nil {
- return fmt.Errorf("failed to read from addr2line: %v", err)
- }
- file, err := outb.ReadString('\n')
- if err != nil {
- return fmt.Errorf("failed to read from addr2line: %v", err)
- }
- f := &Frame{PC: pc}
- f.Fn = fn[:len(fn)-1]
- f.File = file[:len(file)-1]
- if colon := strings.LastIndex(f.File, ":"); colon != -1 {
- ln, err := strconv.Atoi(f.File[colon+1:])
- if err == nil {
- f.File = f.File[:colon]
- f.Line = ln
- }
- }
- pcs[pc] = f
- }
- cmd.Wait()
-
- // Replace frames in events array.
- for _, ev := range events {
- for i, f := range ev.Stk {
- ev.Stk[i] = pcs[f.PC]
- }
- }
-
- return nil
-}
-
-// readVal reads unsigned base-128 value from r.
-func readVal(r io.Reader, off0 int) (v uint64, off int, err error) {
- off = off0
- for i := 0; i < 10; i++ {
- var buf [1]byte
- var n int
- n, err = r.Read(buf[:])
- if err != nil || n != 1 {
- return 0, 0, fmt.Errorf("failed to read trace at offset %d: read %v, error %v", off0, n, err)
- }
- off++
- v |= uint64(buf[0]&0x7f) << (uint(i) * 7)
- if buf[0]&0x80 == 0 {
- return
- }
- }
- return 0, 0, fmt.Errorf("bad value at offset 0x%x", off0)
-}
-
-// Print dumps events to stdout. For debugging.
-func Print(events []*Event) {
- for _, ev := range events {
- PrintEvent(ev)
- }
-}
-
-// PrintEvent dumps the event to stdout. For debugging.
-func PrintEvent(ev *Event) {
- fmt.Printf("%s\n", ev)
-}
-
-func (ev *Event) String() string {
- desc := EventDescriptions[ev.Type]
- w := new(strings.Builder)
- fmt.Fprintf(w, "%v %v p=%v g=%v off=%v", ev.Ts, desc.Name, ev.P, ev.G, ev.Off)
- for i, a := range desc.Args {
- fmt.Fprintf(w, " %v=%v", a, ev.Args[i])
- }
- for i, a := range desc.SArgs {
- fmt.Fprintf(w, " %v=%v", a, ev.SArgs[i])
- }
- return w.String()
-}
-
-// argNum returns total number of args for the event accounting for timestamps,
-// sequence numbers and differences between trace format versions.
-func argNum(raw rawEvent, ver int) int {
- desc := EventDescriptions[raw.typ]
- if raw.typ == EvStack {
- return len(raw.args)
- }
- narg := len(desc.Args)
- if desc.Stack {
- narg++
- }
- switch raw.typ {
- case EvBatch, EvFrequency, EvTimerGoroutine:
- if ver < 1007 {
- narg++ // there was an unused arg before 1.7
- }
- return narg
- }
- narg++ // timestamp
- if ver < 1007 {
- narg++ // sequence
- }
- switch raw.typ {
- case EvGCSweepDone:
- if ver < 1009 {
- narg -= 2 // 1.9 added two arguments
- }
- case EvGCStart, EvGoStart, EvGoUnblock:
- if ver < 1007 {
- narg-- // 1.7 added an additional seq arg
- }
- case EvSTWStart:
- if ver < 1010 {
- narg-- // 1.10 added an argument
- }
- }
- return narg
-}
-
-// BreakTimestampsForTesting causes the parser to randomly alter timestamps (for testing of broken cputicks).
-var BreakTimestampsForTesting bool
-
// Event types in the trace.
// Verbatim copy from src/runtime/trace.go with the "trace" prefix removed.
const (
@@ -1099,83 +77,3 @@ const (
EvCPUSample = 49 // CPU profiling sample [timestamp, real timestamp, real P id (-1 when absent), goroutine id, stack]
EvCount = 50
)
-
-var EventDescriptions = [EvCount]struct {
- Name string
- minVersion int
- Stack bool
- Args []string
- SArgs []string // string arguments
-}{
- EvNone: {"None", 1005, false, []string{}, nil},
- EvBatch: {"Batch", 1005, false, []string{"p", "ticks"}, nil}, // in 1.5 format it was {"p", "seq", "ticks"}
- EvFrequency: {"Frequency", 1005, false, []string{"freq"}, nil}, // in 1.5 format it was {"freq", "unused"}
- EvStack: {"Stack", 1005, false, []string{"id", "siz"}, nil},
- EvGomaxprocs: {"Gomaxprocs", 1005, true, []string{"procs"}, nil},
- EvProcStart: {"ProcStart", 1005, false, []string{"thread"}, nil},
- EvProcStop: {"ProcStop", 1005, false, []string{}, nil},
- EvGCStart: {"GCStart", 1005, true, []string{"seq"}, nil}, // in 1.5 format it was {}
- EvGCDone: {"GCDone", 1005, false, []string{}, nil},
- EvSTWStart: {"STWStart", 1005, false, []string{"kindid"}, []string{"kind"}}, // <= 1.9, args was {} (implicitly {0})
- EvSTWDone: {"STWDone", 1005, false, []string{}, nil},
- EvGCSweepStart: {"GCSweepStart", 1005, true, []string{}, nil},
- EvGCSweepDone: {"GCSweepDone", 1005, false, []string{"swept", "reclaimed"}, nil}, // before 1.9, format was {}
- EvGoCreate: {"GoCreate", 1005, true, []string{"g", "stack"}, nil},
- EvGoStart: {"GoStart", 1005, false, []string{"g", "seq"}, nil}, // in 1.5 format it was {"g"}
- EvGoEnd: {"GoEnd", 1005, false, []string{}, nil},
- EvGoStop: {"GoStop", 1005, true, []string{}, nil},
- EvGoSched: {"GoSched", 1005, true, []string{}, nil},
- EvGoPreempt: {"GoPreempt", 1005, true, []string{}, nil},
- EvGoSleep: {"GoSleep", 1005, true, []string{}, nil},
- EvGoBlock: {"GoBlock", 1005, true, []string{}, nil},
- EvGoUnblock: {"GoUnblock", 1005, true, []string{"g", "seq"}, nil}, // in 1.5 format it was {"g"}
- EvGoBlockSend: {"GoBlockSend", 1005, true, []string{}, nil},
- EvGoBlockRecv: {"GoBlockRecv", 1005, true, []string{}, nil},
- EvGoBlockSelect: {"GoBlockSelect", 1005, true, []string{}, nil},
- EvGoBlockSync: {"GoBlockSync", 1005, true, []string{}, nil},
- EvGoBlockCond: {"GoBlockCond", 1005, true, []string{}, nil},
- EvGoBlockNet: {"GoBlockNet", 1005, true, []string{}, nil},
- EvGoSysCall: {"GoSysCall", 1005, true, []string{}, nil},
- EvGoSysExit: {"GoSysExit", 1005, false, []string{"g", "seq", "ts"}, nil},
- EvGoSysBlock: {"GoSysBlock", 1005, false, []string{}, nil},
- EvGoWaiting: {"GoWaiting", 1005, false, []string{"g"}, nil},
- EvGoInSyscall: {"GoInSyscall", 1005, false, []string{"g"}, nil},
- EvHeapAlloc: {"HeapAlloc", 1005, false, []string{"mem"}, nil},
- EvHeapGoal: {"HeapGoal", 1005, false, []string{"mem"}, nil},
- EvTimerGoroutine: {"TimerGoroutine", 1005, false, []string{"g"}, nil}, // in 1.5 format it was {"g", "unused"}
- EvFutileWakeup: {"FutileWakeup", 1005, false, []string{}, nil},
- EvString: {"String", 1007, false, []string{}, nil},
- EvGoStartLocal: {"GoStartLocal", 1007, false, []string{"g"}, nil},
- EvGoUnblockLocal: {"GoUnblockLocal", 1007, true, []string{"g"}, nil},
- EvGoSysExitLocal: {"GoSysExitLocal", 1007, false, []string{"g", "ts"}, nil},
- EvGoStartLabel: {"GoStartLabel", 1008, false, []string{"g", "seq", "labelid"}, []string{"label"}},
- EvGoBlockGC: {"GoBlockGC", 1008, true, []string{}, nil},
- EvGCMarkAssistStart: {"GCMarkAssistStart", 1009, true, []string{}, nil},
- EvGCMarkAssistDone: {"GCMarkAssistDone", 1009, false, []string{}, nil},
- EvUserTaskCreate: {"UserTaskCreate", 1011, true, []string{"taskid", "pid", "typeid"}, []string{"name"}},
- EvUserTaskEnd: {"UserTaskEnd", 1011, true, []string{"taskid"}, nil},
- EvUserRegion: {"UserRegion", 1011, true, []string{"taskid", "mode", "typeid"}, []string{"name"}},
- EvUserLog: {"UserLog", 1011, true, []string{"id", "keyid"}, []string{"category", "message"}},
- EvCPUSample: {"CPUSample", 1019, true, []string{"ts", "p", "g"}, nil},
-}
-
-// Copied from src/runtime/proc.go:stwReasonStrings in Go 1.21.
-var stwReasonStringsGo121 = [...]string{
- "unknown",
- "GC mark termination",
- "GC sweep termination",
- "write heap dump",
- "goroutine profile",
- "goroutine profile cleanup",
- "all goroutines stack trace",
- "read mem stats",
- "AllThreadsSyscall",
- "GOMAXPROCS",
- "start trace",
- "stop trace",
- "CountPagesInUse (test)",
- "ReadMetricsSlow (test)",
- "ReadMemStatsSlow (test)",
- "PageCachePagesLeaked (test)",
- "ResetDebugLog (test)",
-}
diff --git a/src/internal/trace/parser_test.go b/src/internal/trace/parser_test.go
deleted file mode 100644
index fce660c289..0000000000
--- a/src/internal/trace/parser_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2015 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.
-
-package trace
-
-import (
- "bytes"
- "os"
- "path/filepath"
- "strings"
- "testing"
-)
-
-func TestCorruptedInputs(t *testing.T) {
- // These inputs crashed parser previously.
- tests := []string{
- "gotrace\x00\x020",
- "gotrace\x00Q00\x020",
- "gotrace\x00T00\x020",
- "gotrace\x00\xc3\x0200",
- "go 1.5 trace\x00\x00\x00\x00\x020",
- "go 1.5 trace\x00\x00\x00\x00Q00\x020",
- "go 1.5 trace\x00\x00\x00\x00T00\x020",
- "go 1.5 trace\x00\x00\x00\x00\xc3\x0200",
- }
- for _, data := range tests {
- res, err := Parse(strings.NewReader(data), "")
- if err == nil || res.Events != nil || res.Stacks != nil {
- t.Fatalf("no error on input: %q", data)
- }
- }
-}
-
-func TestParseCanned(t *testing.T) {
- files, err := os.ReadDir("./testdata")
- if err != nil {
- t.Fatalf("failed to read ./testdata: %v", err)
- }
- for _, f := range files {
- info, err := f.Info()
- if err != nil {
- t.Fatal(err)
- }
- if testing.Short() && info.Size() > 10000 {
- continue
- }
- name := filepath.Join("./testdata", f.Name())
- data, err := os.ReadFile(name)
- if err != nil {
- t.Fatal(err)
- }
- // Instead of Parse that requires a proper binary name for old traces,
- // we use 'parse' that omits symbol lookup if an empty string is given.
- ver, res, err := parse(bytes.NewReader(data), "")
- switch {
- case strings.HasSuffix(f.Name(), "_good"):
- if err != nil {
- t.Errorf("failed to parse good trace %v: %v", f.Name(), err)
- }
- checkTrace(t, ver, res)
- case strings.HasSuffix(f.Name(), "_unordered"):
- if err != ErrTimeOrder {
- t.Errorf("unordered trace is not detected %v: %v", f.Name(), err)
- }
- default:
- t.Errorf("unknown input file suffix: %v", f.Name())
- }
- }
-}
-
-// checkTrace walks over a good trace and makes a bunch of additional checks
-// that may not cause the parser to outright fail.
-func checkTrace(t *testing.T, ver int, res ParseResult) {
- for _, ev := range res.Events {
- if ver >= 1021 {
- if ev.Type == EvSTWStart && ev.SArgs[0] == "unknown" {
- t.Errorf("found unknown STW event; update stwReasonStrings?")
- }
- }
- }
-}
-
-func TestParseVersion(t *testing.T) {
- tests := map[string]int{
- "go 1.5 trace\x00\x00\x00\x00": 1005,
- "go 1.7 trace\x00\x00\x00\x00": 1007,
- "go 1.10 trace\x00\x00\x00": 1010,
- "go 1.25 trace\x00\x00\x00": 1025,
- "go 1.234 trace\x00\x00": 1234,
- "go 1.2345 trace\x00": -1,
- "go 0.0 trace\x00\x00\x00\x00": -1,
- "go a.b trace\x00\x00\x00\x00": -1,
- }
- for header, ver := range tests {
- ver1, err := parseHeader([]byte(header))
- if ver == -1 {
- if err == nil {
- t.Fatalf("no error on input: %q, version %v", header, ver1)
- }
- } else {
- if err != nil {
- t.Fatalf("failed to parse: %q (%v)", header, err)
- }
- if ver != ver1 {
- t.Fatalf("wrong version: %v, want %v, input: %q", ver1, ver, header)
- }
- }
- }
-}
-
-func TestTimestampOverflow(t *testing.T) {
- // Test that parser correctly handles large timestamps (long tracing).
- w := NewWriter()
- w.Emit(EvBatch, 0, 0)
- w.Emit(EvFrequency, 1e9)
- for ts := uint64(1); ts < 1e16; ts *= 2 {
- w.Emit(EvGoCreate, ts, ts, 0, 0)
- }
- if _, err := Parse(w, ""); err != nil {
- t.Fatalf("failed to parse: %v", err)
- }
-}
diff --git a/src/internal/trace/summary.go b/src/internal/trace/summary.go
index b714e01f4a..6d889baca3 100644
--- a/src/internal/trace/summary.go
+++ b/src/internal/trace/summary.go
@@ -7,6 +7,7 @@ package trace
import (
tracev2 "internal/trace/v2"
"sort"
+ "strings"
"time"
)
@@ -665,3 +666,10 @@ func RelatedGoroutinesV2(events []tracev2.Event, goid tracev2.GoID) map[tracev2.
}
return gmap
}
+
+func IsSystemGoroutine(entryFn string) bool {
+ // This mimics runtime.isSystemGoroutine as closely as
+ // possible.
+ // Also, locked g in extra M (with empty entryFn) is system goroutine.
+ return entryFn == "" || entryFn != "runtime.main" && strings.HasPrefix(entryFn, "runtime.")
+}
diff --git a/src/internal/trace/testdata/http_1_10_good b/src/internal/trace/testdata/http_1_10_good
deleted file mode 100644
index a4f2ed83d8..0000000000
--- a/src/internal/trace/testdata/http_1_10_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/http_1_11_good b/src/internal/trace/testdata/http_1_11_good
deleted file mode 100644
index 0efcc6fba1..0000000000
--- a/src/internal/trace/testdata/http_1_11_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/http_1_19_good b/src/internal/trace/testdata/http_1_19_good
deleted file mode 100644
index c1d519e7d5..0000000000
--- a/src/internal/trace/testdata/http_1_19_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/http_1_21_good b/src/internal/trace/testdata/http_1_21_good
deleted file mode 100644
index b3295f9e5d..0000000000
--- a/src/internal/trace/testdata/http_1_21_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/http_1_5_good b/src/internal/trace/testdata/http_1_5_good
deleted file mode 100644
index 0736cae674..0000000000
--- a/src/internal/trace/testdata/http_1_5_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/http_1_7_good b/src/internal/trace/testdata/http_1_7_good
deleted file mode 100644
index b0e318e9a4..0000000000
--- a/src/internal/trace/testdata/http_1_7_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/http_1_9_good b/src/internal/trace/testdata/http_1_9_good
deleted file mode 100644
index ca89278838..0000000000
--- a/src/internal/trace/testdata/http_1_9_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_1_10_good b/src/internal/trace/testdata/stress_1_10_good
deleted file mode 100644
index 19778b0808..0000000000
--- a/src/internal/trace/testdata/stress_1_10_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_1_11_good b/src/internal/trace/testdata/stress_1_11_good
deleted file mode 100644
index 6468d89290..0000000000
--- a/src/internal/trace/testdata/stress_1_11_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_1_19_good b/src/internal/trace/testdata/stress_1_19_good
deleted file mode 100644
index 13f59268e6..0000000000
--- a/src/internal/trace/testdata/stress_1_19_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_1_21_good b/src/internal/trace/testdata/stress_1_21_good
deleted file mode 100644
index 1ade5e0eb6..0000000000
--- a/src/internal/trace/testdata/stress_1_21_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_1_5_good b/src/internal/trace/testdata/stress_1_5_good
deleted file mode 100644
index c5055ebd19..0000000000
--- a/src/internal/trace/testdata/stress_1_5_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_1_5_unordered b/src/internal/trace/testdata/stress_1_5_unordered
deleted file mode 100644
index 11f7d745ca..0000000000
--- a/src/internal/trace/testdata/stress_1_5_unordered
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_1_7_good b/src/internal/trace/testdata/stress_1_7_good
deleted file mode 100644
index b4d927de34..0000000000
--- a/src/internal/trace/testdata/stress_1_7_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_1_9_good b/src/internal/trace/testdata/stress_1_9_good
deleted file mode 100644
index dcf17f19e1..0000000000
--- a/src/internal/trace/testdata/stress_1_9_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_start_stop_1_10_good b/src/internal/trace/testdata/stress_start_stop_1_10_good
deleted file mode 100644
index b908e10f25..0000000000
--- a/src/internal/trace/testdata/stress_start_stop_1_10_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_start_stop_1_11_good b/src/internal/trace/testdata/stress_start_stop_1_11_good
deleted file mode 100644
index 457f01a6cd..0000000000
--- a/src/internal/trace/testdata/stress_start_stop_1_11_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_start_stop_1_19_good b/src/internal/trace/testdata/stress_start_stop_1_19_good
deleted file mode 100644
index 92d92789c4..0000000000
--- a/src/internal/trace/testdata/stress_start_stop_1_19_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_start_stop_1_21_good b/src/internal/trace/testdata/stress_start_stop_1_21_good
deleted file mode 100644
index fff46a9a07..0000000000
--- a/src/internal/trace/testdata/stress_start_stop_1_21_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_start_stop_1_5_good b/src/internal/trace/testdata/stress_start_stop_1_5_good
deleted file mode 100644
index 72a887b844..0000000000
--- a/src/internal/trace/testdata/stress_start_stop_1_5_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_start_stop_1_7_good b/src/internal/trace/testdata/stress_start_stop_1_7_good
deleted file mode 100644
index c23ed7dc08..0000000000
--- a/src/internal/trace/testdata/stress_start_stop_1_7_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/stress_start_stop_1_9_good b/src/internal/trace/testdata/stress_start_stop_1_9_good
deleted file mode 100644
index f00f190f32..0000000000
--- a/src/internal/trace/testdata/stress_start_stop_1_9_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/user_task_region_1_11_good b/src/internal/trace/testdata/user_task_region_1_11_good
deleted file mode 100644
index f4edb67e65..0000000000
--- a/src/internal/trace/testdata/user_task_region_1_11_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/user_task_region_1_19_good b/src/internal/trace/testdata/user_task_region_1_19_good
deleted file mode 100644
index 1daa3b25a6..0000000000
--- a/src/internal/trace/testdata/user_task_region_1_19_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/testdata/user_task_region_1_21_good b/src/internal/trace/testdata/user_task_region_1_21_good
deleted file mode 100644
index 5c01a6405d..0000000000
--- a/src/internal/trace/testdata/user_task_region_1_21_good
+++ /dev/null
Binary files differ
diff --git a/src/internal/trace/writer.go b/src/internal/trace/writer.go
deleted file mode 100644
index dd0b9f104e..0000000000
--- a/src/internal/trace/writer.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2017 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.
-
-package trace
-
-import "bytes"
-
-// Writer is a test trace writer.
-type Writer struct {
- bytes.Buffer
-}
-
-func NewWriter() *Writer {
- w := new(Writer)
- w.Write([]byte("go 1.9 trace\x00\x00\x00\x00"))
- return w
-}
-
-// Emit writes an event record to the trace.
-// See Event types for valid types and required arguments.
-func (w *Writer) Emit(typ byte, args ...uint64) {
- nargs := byte(len(args)) - 1
- if nargs > 3 {
- nargs = 3
- }
- buf := []byte{typ | nargs<<6}
- if nargs == 3 {
- buf = append(buf, 0)
- }
- for _, a := range args {
- buf = appendVarint(buf, a)
- }
- if nargs == 3 {
- buf[1] = byte(len(buf) - 2)
- }
- n, err := w.Write(buf)
- if n != len(buf) || err != nil {
- panic("failed to write")
- }
-}
-
-func appendVarint(buf []byte, v uint64) []byte {
- for ; v >= 0x80; v >>= 7 {
- buf = append(buf, 0x80|byte(v))
- }
- buf = append(buf, byte(v))
- return buf
-}