Commit 08816cb8 authored by Peter Weinberger's avatar Peter Weinberger

cmd/trace: use new traceparser to parse the raw trace files

Change-Id: I8b224ae48a2f8acd5a64c9ff283e97821479a9a8
Reviewed-on: https://go-review.googlesource.com/c/145457
Run-TryBot: Peter Weinberger <pjw@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarHyang-Ah Hana Kim <hyangah@gmail.com>
parent f570b54c
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"html/template" "html/template"
"internal/trace"
"log" "log"
"math" "math"
"net/http" "net/http"
...@@ -17,6 +16,8 @@ import ( ...@@ -17,6 +16,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
trace "internal/traceparser"
) )
func init() { func init() {
...@@ -308,7 +309,7 @@ func analyzeAnnotations() (annotationAnalysisResult, error) { ...@@ -308,7 +309,7 @@ func analyzeAnnotations() (annotationAnalysisResult, error) {
} }
} }
// combine region info. // combine region info.
analyzeGoroutines(events) analyzeGoroutines(res)
for goid, stats := range gs { for goid, stats := range gs {
// gs is a global var defined in goroutines.go as a result // gs is a global var defined in goroutines.go as a result
// of analyzeGoroutines. TODO(hyangah): fix this not to depend // of analyzeGoroutines. TODO(hyangah): fix this not to depend
...@@ -321,7 +322,7 @@ func analyzeAnnotations() (annotationAnalysisResult, error) { ...@@ -321,7 +322,7 @@ func analyzeAnnotations() (annotationAnalysisResult, error) {
} }
var frame trace.Frame var frame trace.Frame
if s.Start != nil { if s.Start != nil {
frame = *s.Start.Stk[0] frame = *res.Stacks[s.Start.StkID][0]
} }
id := regionTypeID{Frame: frame, Type: s.Name} id := regionTypeID{Frame: frame, Type: s.Name}
regions[id] = append(regions[id], regionDesc{UserRegionDesc: s, G: goid}) regions[id] = append(regions[id], regionDesc{UserRegionDesc: s, G: goid})
......
...@@ -11,7 +11,7 @@ import ( ...@@ -11,7 +11,7 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
traceparser "internal/trace" "internal/traceparser"
"io/ioutil" "io/ioutil"
"reflect" "reflect"
"runtime/debug" "runtime/debug"
...@@ -338,10 +338,8 @@ func traceProgram(t *testing.T, f func(), name string) error { ...@@ -338,10 +338,8 @@ func traceProgram(t *testing.T, f func(), name string) error {
trace.Stop() trace.Stop()
saveTrace(buf, name) saveTrace(buf, name)
res, err := traceparser.Parse(buf, name+".faketrace") res, err := traceparser.ParseBuffer(buf)
if err == traceparser.ErrTimeOrder { if err != nil {
t.Skipf("skipping due to golang.org/issue/16755: %v", err)
} else if err != nil {
return err return err
} }
...@@ -370,15 +368,15 @@ func childrenNames(task *taskDesc) (ret []string) { ...@@ -370,15 +368,15 @@ func childrenNames(task *taskDesc) (ret []string) {
return ret return ret
} }
func swapLoaderData(res traceparser.ParseResult, err error) { func swapLoaderData(res *traceparser.Parsed, err error) {
// swap loader's data. // swap loader's data.
parseTrace() // fool loader.once. parseTrace() // fool loader.once.
loader.res = res loader.res = res
loader.err = err loader.err = err
analyzeGoroutines(nil) // fool gsInit once. analyzeGoroutines(res) // fool gsInit once.
gs = traceparser.GoroutineStats(res.Events) gs = res.GoroutineStats()
} }
......
...@@ -9,7 +9,6 @@ package main ...@@ -9,7 +9,6 @@ package main
import ( import (
"fmt" "fmt"
"html/template" "html/template"
"internal/trace"
"log" "log"
"net/http" "net/http"
"reflect" "reflect"
...@@ -17,6 +16,8 @@ import ( ...@@ -17,6 +16,8 @@ import (
"strconv" "strconv"
"sync" "sync"
"time" "time"
trace "internal/traceparser"
) )
func init() { func init() {
...@@ -38,15 +39,15 @@ var ( ...@@ -38,15 +39,15 @@ var (
) )
// analyzeGoroutines generates statistics about execution of all goroutines and stores them in gs. // analyzeGoroutines generates statistics about execution of all goroutines and stores them in gs.
func analyzeGoroutines(events []*trace.Event) { func analyzeGoroutines(res *trace.Parsed) {
gsInit.Do(func() { gsInit.Do(func() {
gs = trace.GoroutineStats(events) gs = res.GoroutineStats()
}) })
} }
// httpGoroutines serves list of goroutine groups. // httpGoroutines serves list of goroutine groups.
func httpGoroutines(w http.ResponseWriter, r *http.Request) { func httpGoroutines(w http.ResponseWriter, r *http.Request) {
events, err := parseEvents() events, err := parseTrace()
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
...@@ -89,7 +90,7 @@ Goroutines: <br> ...@@ -89,7 +90,7 @@ Goroutines: <br>
func httpGoroutine(w http.ResponseWriter, r *http.Request) { func httpGoroutine(w http.ResponseWriter, r *http.Request) {
// TODO(hyangah): support format=csv (raw data) // TODO(hyangah): support format=csv (raw data)
events, err := parseEvents() events, err := parseTrace()
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
......
...@@ -5,12 +5,12 @@ ...@@ -5,12 +5,12 @@
package main package main
import ( import (
"bufio" "bytes"
"cmd/internal/browser" "cmd/internal/browser"
"flag" "flag"
"fmt" "fmt"
"html/template" "html/template"
"internal/trace" trace "internal/traceparser"
"io" "io"
"log" "log"
"net" "net"
...@@ -115,8 +115,22 @@ func main() { ...@@ -115,8 +115,22 @@ func main() {
dief("%v\n", err) dief("%v\n", err)
} }
if *debugFlag { if *debugFlag { // match go tool trace -d (except for Offset and Seq)
trace.Print(res.Events) f := func(ev *trace.Event) {
desc := trace.EventDescriptions[ev.Type]
w := new(bytes.Buffer)
fmt.Fprintf(w, "%v %v p=%v g=%v", ev.Ts, desc.Name, ev.P, ev.G)
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])
}
fmt.Println(w.String())
}
for i := 0; i < len(res.Events); i++ {
f(res.Events[i])
}
os.Exit(0) os.Exit(0)
} }
reportMemoryUsage("after parsing trace") reportMemoryUsage("after parsing trace")
...@@ -141,36 +155,23 @@ var ranges []Range ...@@ -141,36 +155,23 @@ var ranges []Range
var loader struct { var loader struct {
once sync.Once once sync.Once
res trace.ParseResult res *trace.Parsed
err error err error
} }
// parseEvents is a compatibility wrapper that returns only func parseTrace() (*trace.Parsed, error) {
// the Events part of trace.ParseResult returned by parseTrace.
func parseEvents() ([]*trace.Event, error) {
res, err := parseTrace()
if err != nil {
return nil, err
}
return res.Events, err
}
func parseTrace() (trace.ParseResult, error) {
loader.once.Do(func() { loader.once.Do(func() {
tracef, err := os.Open(traceFile) x, err := trace.New(traceFile)
if err != nil { if err != nil {
loader.err = fmt.Errorf("failed to open trace file: %v", err) loader.err = err
return return
} }
defer tracef.Close() err = x.Parse(0, x.MaxTs, nil)
// Parse and symbolize.
res, err := trace.Parse(bufio.NewReader(tracef), programBinary)
if err != nil { if err != nil {
loader.err = fmt.Errorf("failed to parse trace: %v", err) loader.err = err
return return
} }
loader.res = res loader.res = x
}) })
return loader.res, loader.err return loader.res, loader.err
} }
......
...@@ -9,7 +9,6 @@ package main ...@@ -9,7 +9,6 @@ package main
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"internal/trace"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
...@@ -21,6 +20,8 @@ import ( ...@@ -21,6 +20,8 @@ import (
"strconv" "strconv"
"time" "time"
trace "internal/traceparser"
"github.com/google/pprof/profile" "github.com/google/pprof/profile"
) )
...@@ -60,22 +61,22 @@ type interval struct { ...@@ -60,22 +61,22 @@ type interval struct {
begin, end int64 // nanoseconds. begin, end int64 // nanoseconds.
} }
func pprofByGoroutine(compute func(io.Writer, map[uint64][]interval, []*trace.Event) error) func(w io.Writer, r *http.Request) error { func pprofByGoroutine(compute func(io.Writer, map[uint64][]interval, *trace.Parsed) error) func(w io.Writer, r *http.Request) error {
return func(w io.Writer, r *http.Request) error { return func(w io.Writer, r *http.Request) error {
id := r.FormValue("id") id := r.FormValue("id")
events, err := parseEvents() res, err := parseTrace()
if err != nil { if err != nil {
return err return err
} }
gToIntervals, err := pprofMatchingGoroutines(id, events) gToIntervals, err := pprofMatchingGoroutines(id, res)
if err != nil { if err != nil {
return err return err
} }
return compute(w, gToIntervals, events) return compute(w, gToIntervals, res)
} }
} }
func pprofByRegion(compute func(io.Writer, map[uint64][]interval, []*trace.Event) error) func(w io.Writer, r *http.Request) error { func pprofByRegion(compute func(io.Writer, map[uint64][]interval, *trace.Parsed) error) func(w io.Writer, r *http.Request) error {
return func(w io.Writer, r *http.Request) error { return func(w io.Writer, r *http.Request) error {
filter, err := newRegionFilter(r) filter, err := newRegionFilter(r)
if err != nil { if err != nil {
...@@ -85,7 +86,7 @@ func pprofByRegion(compute func(io.Writer, map[uint64][]interval, []*trace.Event ...@@ -85,7 +86,7 @@ func pprofByRegion(compute func(io.Writer, map[uint64][]interval, []*trace.Event
if err != nil { if err != nil {
return err return err
} }
events, _ := parseEvents() events, _ := parseTrace()
return compute(w, gToIntervals, events) return compute(w, gToIntervals, events)
} }
...@@ -94,7 +95,7 @@ func pprofByRegion(compute func(io.Writer, map[uint64][]interval, []*trace.Event ...@@ -94,7 +95,7 @@ func pprofByRegion(compute func(io.Writer, map[uint64][]interval, []*trace.Event
// pprofMatchingGoroutines parses the goroutine type id string (i.e. pc) // pprofMatchingGoroutines parses the goroutine type id string (i.e. pc)
// and returns the ids of goroutines of the matching type and its interval. // and returns the ids of goroutines of the matching type and its interval.
// If the id string is empty, returns nil without an error. // If the id string is empty, returns nil without an error.
func pprofMatchingGoroutines(id string, events []*trace.Event) (map[uint64][]interval, error) { func pprofMatchingGoroutines(id string, p *trace.Parsed) (map[uint64][]interval, error) {
if id == "" { if id == "" {
return nil, nil return nil, nil
} }
...@@ -102,7 +103,7 @@ func pprofMatchingGoroutines(id string, events []*trace.Event) (map[uint64][]int ...@@ -102,7 +103,7 @@ func pprofMatchingGoroutines(id string, events []*trace.Event) (map[uint64][]int
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid goroutine type: %v", id) return nil, fmt.Errorf("invalid goroutine type: %v", id)
} }
analyzeGoroutines(events) analyzeGoroutines(p)
var res map[uint64][]interval var res map[uint64][]interval
for _, g := range gs { for _, g := range gs {
if g.PC != pc { if g.PC != pc {
...@@ -171,17 +172,25 @@ func pprofMatchingRegions(filter *regionFilter) (map[uint64][]interval, error) { ...@@ -171,17 +172,25 @@ func pprofMatchingRegions(filter *regionFilter) (map[uint64][]interval, error) {
return gToIntervals, nil return gToIntervals, nil
} }
func stklen(p *trace.Parsed, ev *trace.Event) int {
if ev.StkID == 0 {
return 0
}
return len(p.Stacks[ev.StkID])
}
// computePprofIO generates IO pprof-like profile (time spent in IO wait, currently only network blocking event). // computePprofIO generates IO pprof-like profile (time spent in IO wait, currently only network blocking event).
func computePprofIO(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error { func computePprofIO(w io.Writer, gToIntervals map[uint64][]interval, res *trace.Parsed) error {
prof := make(map[uint64]Record) events := res.Events
prof := make(map[uint32]Record)
for _, ev := range events { for _, ev := range events {
if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { if ev.Type != trace.EvGoBlockNet || ev.Link == nil || ev.StkID == 0 || stklen(res, ev) == 0 {
continue continue
} }
overlapping := pprofOverlappingDuration(gToIntervals, ev) overlapping := pprofOverlappingDuration(gToIntervals, ev)
if overlapping > 0 { if overlapping > 0 {
rec := prof[ev.StkID] rec := prof[ev.StkID]
rec.stk = ev.Stk rec.stk = res.Stacks[ev.StkID]
rec.n++ rec.n++
rec.time += overlapping.Nanoseconds() rec.time += overlapping.Nanoseconds()
prof[ev.StkID] = rec prof[ev.StkID] = rec
...@@ -191,8 +200,9 @@ func computePprofIO(w io.Writer, gToIntervals map[uint64][]interval, events []*t ...@@ -191,8 +200,9 @@ func computePprofIO(w io.Writer, gToIntervals map[uint64][]interval, events []*t
} }
// computePprofBlock generates blocking pprof-like profile (time spent blocked on synchronization primitives). // computePprofBlock generates blocking pprof-like profile (time spent blocked on synchronization primitives).
func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error { func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, res *trace.Parsed) error {
prof := make(map[uint64]Record) events := res.Events
prof := make(map[uint32]Record)
for _, ev := range events { for _, ev := range events {
switch ev.Type { switch ev.Type {
case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect, case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect,
...@@ -203,13 +213,13 @@ func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, events [ ...@@ -203,13 +213,13 @@ func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, events [
default: default:
continue continue
} }
if ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { if ev.Link == nil || ev.StkID == 0 || stklen(res, ev) == 0 {
continue continue
} }
overlapping := pprofOverlappingDuration(gToIntervals, ev) overlapping := pprofOverlappingDuration(gToIntervals, ev)
if overlapping > 0 { if overlapping > 0 {
rec := prof[ev.StkID] rec := prof[ev.StkID]
rec.stk = ev.Stk rec.stk = res.Stacks[ev.StkID]
rec.n++ rec.n++
rec.time += overlapping.Nanoseconds() rec.time += overlapping.Nanoseconds()
prof[ev.StkID] = rec prof[ev.StkID] = rec
...@@ -219,16 +229,17 @@ func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, events [ ...@@ -219,16 +229,17 @@ func computePprofBlock(w io.Writer, gToIntervals map[uint64][]interval, events [
} }
// computePprofSyscall generates syscall pprof-like profile (time spent blocked in syscalls). // computePprofSyscall generates syscall pprof-like profile (time spent blocked in syscalls).
func computePprofSyscall(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error { func computePprofSyscall(w io.Writer, gToIntervals map[uint64][]interval, res *trace.Parsed) error {
prof := make(map[uint64]Record) events := res.Events
prof := make(map[uint32]Record)
for _, ev := range events { for _, ev := range events {
if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { if ev.Type != trace.EvGoSysCall || ev.Link == nil || ev.StkID == 0 || stklen(res, ev) == 0 {
continue continue
} }
overlapping := pprofOverlappingDuration(gToIntervals, ev) overlapping := pprofOverlappingDuration(gToIntervals, ev)
if overlapping > 0 { if overlapping > 0 {
rec := prof[ev.StkID] rec := prof[ev.StkID]
rec.stk = ev.Stk rec.stk = res.Stacks[ev.StkID]
rec.n++ rec.n++
rec.time += overlapping.Nanoseconds() rec.time += overlapping.Nanoseconds()
prof[ev.StkID] = rec prof[ev.StkID] = rec
...@@ -239,17 +250,18 @@ func computePprofSyscall(w io.Writer, gToIntervals map[uint64][]interval, events ...@@ -239,17 +250,18 @@ func computePprofSyscall(w io.Writer, gToIntervals map[uint64][]interval, events
// computePprofSched generates scheduler latency pprof-like profile // computePprofSched generates scheduler latency pprof-like profile
// (time between a goroutine become runnable and actually scheduled for execution). // (time between a goroutine become runnable and actually scheduled for execution).
func computePprofSched(w io.Writer, gToIntervals map[uint64][]interval, events []*trace.Event) error { func computePprofSched(w io.Writer, gToIntervals map[uint64][]interval, res *trace.Parsed) error {
prof := make(map[uint64]Record) events := res.Events
prof := make(map[uint32]Record)
for _, ev := range events { for _, ev := range events {
if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) || if (ev.Type != trace.EvGoUnblock && ev.Type != trace.EvGoCreate) ||
ev.Link == nil || ev.StkID == 0 || len(ev.Stk) == 0 { ev.Link == nil || ev.StkID == 0 || stklen(res, ev) == 0 {
continue continue
} }
overlapping := pprofOverlappingDuration(gToIntervals, ev) overlapping := pprofOverlappingDuration(gToIntervals, ev)
if overlapping > 0 { if overlapping > 0 {
rec := prof[ev.StkID] rec := prof[ev.StkID]
rec.stk = ev.Stk rec.stk = res.Stacks[ev.StkID]
rec.n++ rec.n++
rec.time += overlapping.Nanoseconds() rec.time += overlapping.Nanoseconds()
prof[ev.StkID] = rec prof[ev.StkID] = rec
...@@ -327,7 +339,7 @@ func serveSVGProfile(prof func(w io.Writer, r *http.Request) error) http.Handler ...@@ -327,7 +339,7 @@ func serveSVGProfile(prof func(w io.Writer, r *http.Request) error) http.Handler
} }
} }
func buildProfile(prof map[uint64]Record) *profile.Profile { func buildProfile(prof map[uint32]Record) *profile.Profile {
p := &profile.Profile{ p := &profile.Profile{
PeriodType: &profile.ValueType{Type: "trace", Unit: "count"}, PeriodType: &profile.ValueType{Type: "trace", Unit: "count"},
Period: 1, Period: 1,
......
This diff is collapsed.
...@@ -8,26 +8,27 @@ package main ...@@ -8,26 +8,27 @@ package main
import ( import (
"context" "context"
"internal/trace"
"io/ioutil" "io/ioutil"
rtrace "runtime/trace" rtrace "runtime/trace"
"strings" "strings"
"testing" "testing"
trace "internal/traceparser"
) )
// stacks is a fake stack map populated for test. // stacks is a fake stack map populated for test.
type stacks map[uint64][]*trace.Frame type stacks map[uint32][]*trace.Frame
// add adds a stack with a single frame whose Fn field is // add adds a stack with a single frame whose Fn field is
// set to the provided fname and returns a unique stack id. // set to the provided fname and returns a unique stack id.
func (s *stacks) add(fname string) uint64 { func (s *stacks) add(fname string) uint64 {
if *s == nil { if *s == nil {
*s = make(map[uint64][]*trace.Frame) *s = make(map[uint32][]*trace.Frame)
} }
id := uint64(len(*s)) id := uint32(len(*s))
(*s)[id] = []*trace.Frame{{Fn: fname}} (*s)[id] = []*trace.Frame{{Fn: fname}}
return id return uint64(id)
} }
// TestGoroutineCount tests runnable/running goroutine counts computed by generateTrace // TestGoroutineCount tests runnable/running goroutine counts computed by generateTrace
...@@ -36,8 +37,7 @@ func (s *stacks) add(fname string) uint64 { ...@@ -36,8 +37,7 @@ func (s *stacks) add(fname string) uint64 {
// - the counts must not include goroutines blocked waiting on channels or in syscall. // - the counts must not include goroutines blocked waiting on channels or in syscall.
func TestGoroutineCount(t *testing.T) { func TestGoroutineCount(t *testing.T) {
w := trace.NewWriter() w := trace.NewWriter()
w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp] w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp]
w.Emit(trace.EvFrequency, 1) // [ticks per second]
var s stacks var s stacks
...@@ -61,8 +61,9 @@ func TestGoroutineCount(t *testing.T) { ...@@ -61,8 +61,9 @@ func TestGoroutineCount(t *testing.T) {
w.Emit(trace.EvGoCreate, 1, 40, s.add("pkg.f4"), s.add("main.f4")) w.Emit(trace.EvGoCreate, 1, 40, s.add("pkg.f4"), s.add("main.f4"))
w.Emit(trace.EvGoStartLocal, 1, 40) // [timestamp, goroutine id] w.Emit(trace.EvGoStartLocal, 1, 40) // [timestamp, goroutine id]
w.Emit(trace.EvGoSched, 1, s.add("main.f4")) // [timestamp, stack] w.Emit(trace.EvGoSched, 1, s.add("main.f4")) // [timestamp, stack]
w.Emit(trace.EvFrequency, 1) // [ticks per second]
res, err := trace.Parse(w, "") res, err := trace.ParseBuffer(w)
if err != nil { if err != nil {
t.Fatalf("failed to parse test trace: %v", err) t.Fatalf("failed to parse test trace: %v", err)
} }
...@@ -74,9 +75,9 @@ func TestGoroutineCount(t *testing.T) { ...@@ -74,9 +75,9 @@ func TestGoroutineCount(t *testing.T) {
} }
// Use the default viewerDataTraceConsumer but replace // Use the default viewerDataTraceConsumer but replace
// consumeViewerEvent to intercept the ViewerEvents for testing. // consumeViewerEvent to intercept the viewerEvents for testing.
c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1) c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1)
c.consumeViewerEvent = func(ev *ViewerEvent, _ bool) { c.consumeViewerEvent = func(ev *viewerEvent, _ bool) {
if ev.Name == "Goroutines" { if ev.Name == "Goroutines" {
cnt := ev.Arg.(*goroutineCountersArg) cnt := ev.Arg.(*goroutineCountersArg)
if cnt.Runnable+cnt.Running > 2 { if cnt.Runnable+cnt.Running > 2 {
...@@ -87,7 +88,7 @@ func TestGoroutineCount(t *testing.T) { ...@@ -87,7 +88,7 @@ func TestGoroutineCount(t *testing.T) {
} }
// If the counts drop below 0, generateTrace will return an error. // If the counts drop below 0, generateTrace will return an error.
if err := generateTrace(params, c); err != nil { if err := generateTrace(res, params, c); err != nil {
t.Fatalf("generateTrace failed: %v", err) t.Fatalf("generateTrace failed: %v", err)
} }
} }
...@@ -99,8 +100,7 @@ func TestGoroutineFilter(t *testing.T) { ...@@ -99,8 +100,7 @@ func TestGoroutineFilter(t *testing.T) {
var s stacks var s stacks
w := trace.NewWriter() w := trace.NewWriter()
w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp] w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp]
w.Emit(trace.EvFrequency, 1) // [ticks per second]
// goroutine 10: blocked // goroutine 10: blocked
w.Emit(trace.EvGoCreate, 1, 10, s.add("pkg.f1"), s.add("main.f1")) // [timestamp, new goroutine id, new stack id, stack id] w.Emit(trace.EvGoCreate, 1, 10, s.add("pkg.f1"), s.add("main.f1")) // [timestamp, new goroutine id, new stack id, stack id]
...@@ -115,8 +115,9 @@ func TestGoroutineFilter(t *testing.T) { ...@@ -115,8 +115,9 @@ func TestGoroutineFilter(t *testing.T) {
// goroutine 10: runnable->running->block // goroutine 10: runnable->running->block
w.Emit(trace.EvGoStartLocal, 1, 10) // [timestamp, goroutine id] w.Emit(trace.EvGoStartLocal, 1, 10) // [timestamp, goroutine id]
w.Emit(trace.EvGoBlock, 1, s.add("pkg.f3")) // [timestamp, stack] w.Emit(trace.EvGoBlock, 1, s.add("pkg.f3")) // [timestamp, stack]
w.Emit(trace.EvFrequency, 1) // [ticks per second]
res, err := trace.Parse(w, "") res, err := trace.ParseBuffer(w)
if err != nil { if err != nil {
t.Fatalf("failed to parse test trace: %v", err) t.Fatalf("failed to parse test trace: %v", err)
} }
...@@ -129,15 +130,14 @@ func TestGoroutineFilter(t *testing.T) { ...@@ -129,15 +130,14 @@ func TestGoroutineFilter(t *testing.T) {
} }
c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1) c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1)
if err := generateTrace(params, c); err != nil { if err := generateTrace(res, params, c); err != nil {
t.Fatalf("generateTrace failed: %v", err) t.Fatalf("generateTrace failed: %v", err)
} }
} }
func TestPreemptedMarkAssist(t *testing.T) { func TestPreemptedMarkAssist(t *testing.T) {
w := trace.NewWriter() w := trace.NewWriter()
w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp] w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp]
w.Emit(trace.EvFrequency, 1) // [ticks per second]
var s stacks var s stacks
// goroutine 9999: running -> mark assisting -> preempted -> assisting -> running -> block // goroutine 9999: running -> mark assisting -> preempted -> assisting -> running -> block
...@@ -148,11 +148,13 @@ func TestPreemptedMarkAssist(t *testing.T) { ...@@ -148,11 +148,13 @@ func TestPreemptedMarkAssist(t *testing.T) {
w.Emit(trace.EvGoStartLocal, 1, 9999) // [timestamp, goroutine id] w.Emit(trace.EvGoStartLocal, 1, 9999) // [timestamp, goroutine id]
w.Emit(trace.EvGCMarkAssistDone, 1) // [timestamp] w.Emit(trace.EvGCMarkAssistDone, 1) // [timestamp]
w.Emit(trace.EvGoBlock, 1, s.add("main.f2")) // [timestamp, stack] w.Emit(trace.EvGoBlock, 1, s.add("main.f2")) // [timestamp, stack]
w.Emit(trace.EvFrequency, 1) // [ticks per second]
res, err := trace.Parse(w, "") res, err := trace.ParseBuffer(w)
if err != nil { if err != nil {
t.Fatalf("failed to parse test trace: %v", err) t.Fatalf("failed to parse test trace: %v", err)
} }
t.Logf("%+v", *res)
res.Stacks = s // use fake stacks res.Stacks = s // use fake stacks
params := &traceParams{ params := &traceParams{
...@@ -163,12 +165,12 @@ func TestPreemptedMarkAssist(t *testing.T) { ...@@ -163,12 +165,12 @@ func TestPreemptedMarkAssist(t *testing.T) {
c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1) c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1)
marks := 0 marks := 0
c.consumeViewerEvent = func(ev *ViewerEvent, _ bool) { c.consumeViewerEvent = func(ev *viewerEvent, _ bool) {
if strings.Contains(ev.Name, "MARK ASSIST") { if strings.Contains(ev.Name, "MARK ASSIST") {
marks++ marks++
} }
} }
if err := generateTrace(params, c); err != nil { if err := generateTrace(res, params, c); err != nil {
t.Fatalf("generateTrace failed: %v", err) t.Fatalf("generateTrace failed: %v", err)
} }
...@@ -214,7 +216,7 @@ func TestFoo(t *testing.T) { ...@@ -214,7 +216,7 @@ func TestFoo(t *testing.T) {
c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1) c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1)
var logBeforeTaskEnd, logAfterTaskEnd bool var logBeforeTaskEnd, logAfterTaskEnd bool
c.consumeViewerEvent = func(ev *ViewerEvent, _ bool) { c.consumeViewerEvent = func(ev *viewerEvent, _ bool) {
if ev.Name == "log before task ends" { if ev.Name == "log before task ends" {
logBeforeTaskEnd = true logBeforeTaskEnd = true
} }
...@@ -222,7 +224,7 @@ func TestFoo(t *testing.T) { ...@@ -222,7 +224,7 @@ func TestFoo(t *testing.T) {
logAfterTaskEnd = true logAfterTaskEnd = true
} }
} }
if err := generateTrace(params, c); err != nil { if err := generateTrace(res, params, c); err != nil {
t.Fatalf("generateTrace failed: %v", err) t.Fatalf("generateTrace failed: %v", err)
} }
if !logBeforeTaskEnd { if !logBeforeTaskEnd {
......
...@@ -8,7 +8,7 @@ package main ...@@ -8,7 +8,7 @@ package main
import ( import (
"bytes" "bytes"
traceparser "internal/trace" "internal/traceparser"
"io/ioutil" "io/ioutil"
"runtime" "runtime"
"runtime/trace" "runtime/trace"
...@@ -73,17 +73,15 @@ func TestGoroutineInSyscall(t *testing.T) { ...@@ -73,17 +73,15 @@ func TestGoroutineInSyscall(t *testing.T) {
} }
trace.Stop() trace.Stop()
res, err := traceparser.Parse(buf, "") res, err := traceparser.ParseBuffer(buf)
if err == traceparser.ErrTimeOrder { if err != nil {
t.Skipf("skipping due to golang.org/issue/16755 (timestamps are unreliable): %v", err)
} else if err != nil {
t.Fatalf("failed to parse trace: %v", err) t.Fatalf("failed to parse trace: %v", err)
} }
// Check only one thread for the pipe read goroutine is // Check only one thread for the pipe read goroutine is
// considered in-syscall. // considered in-syscall.
c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1) c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1)
c.consumeViewerEvent = func(ev *ViewerEvent, _ bool) { c.consumeViewerEvent = func(ev *viewerEvent, _ bool) {
if ev.Name == "Threads" { if ev.Name == "Threads" {
arg := ev.Arg.(*threadCountersArg) arg := ev.Arg.(*threadCountersArg)
if arg.InSyscall > 1 { if arg.InSyscall > 1 {
...@@ -96,7 +94,7 @@ func TestGoroutineInSyscall(t *testing.T) { ...@@ -96,7 +94,7 @@ func TestGoroutineInSyscall(t *testing.T) {
parsed: res, parsed: res,
endTime: int64(1<<63 - 1), endTime: int64(1<<63 - 1),
} }
if err := generateTrace(param, c); err != nil { if err := generateTrace(res, param, c); err != nil {
t.Fatalf("failed to generate ViewerData: %v", err) t.Fatalf("failed to generate ViewerData: %v", err)
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment