Commit 3fafe2e8 authored by Dmitry Vyukov's avatar Dmitry Vyukov

internal/trace: support parsing of 1.5 traces

1. Parse out version from trace header.
2. Restore handling of 1.5 traces.
3. Restore optional symbolization of traces.
4. Add some canned 1.5 traces for regression testing
   (http benchmark trace, runtime/trace stress traces,
    plus one with broken timestamps).

Change-Id: Idb18a001d03ded8e13c2730eeeb37c5836e31256
Reviewed-on: https://go-review.googlesource.com/21803
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarAustin Clements <austin@google.com>
parent b04e1452
...@@ -109,6 +109,10 @@ go src=.. ...@@ -109,6 +109,10 @@ go src=..
png png
testdata testdata
+ +
internal
trace
testdata
+
io io
+ +
mime mime
......
...@@ -99,7 +99,7 @@ func parseEvents() ([]*trace.Event, error) { ...@@ -99,7 +99,7 @@ func parseEvents() ([]*trace.Event, error) {
defer tracef.Close() defer tracef.Close()
// Parse and symbolize. // Parse and symbolize.
events, err := trace.Parse(bufio.NewReader(tracef)) events, 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 = fmt.Errorf("failed to parse trace: %v", err)
return return
......
...@@ -5,17 +5,22 @@ ...@@ -5,17 +5,22 @@
package trace package trace
import ( import (
"bufio"
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"os"
"os/exec"
"sort" "sort"
"strconv"
"strings"
) )
// Event describes one event in the trace. // Event describes one event in the trace.
type Event struct { type Event struct {
Off int // offset in input file (for debugging and error reporting) Off int // offset in input file (for debugging and error reporting)
Type byte // one of Ev* Type byte // one of Ev*
Seq int64 // sequence number seq int64 // sequence number
Ts int64 // timestamp in nanoseconds Ts int64 // timestamp in nanoseconds
P int // P on which the event happened (can be one of TimerP, NetpollP, SyscallP) P int // P on which the event happened (can be one of TimerP, NetpollP, SyscallP)
G uint64 // G on which the event happened G uint64 // G on which the event happened
...@@ -53,12 +58,12 @@ const ( ...@@ -53,12 +58,12 @@ const (
) )
// Parse parses, post-processes and verifies the trace. // Parse parses, post-processes and verifies the trace.
func Parse(r io.Reader) ([]*Event, error) { func Parse(r io.Reader, bin string) ([]*Event, error) {
rawEvents, strings, err := readTrace(r) ver, rawEvents, strings, err := readTrace(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
events, stacks, err := parseEvents(rawEvents, strings) events, stacks, err := parseEvents(ver, rawEvents, strings)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -66,7 +71,7 @@ func Parse(r io.Reader) ([]*Event, error) { ...@@ -66,7 +71,7 @@ func Parse(r io.Reader) ([]*Event, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = postProcessTrace(events) err = postProcessTrace(ver, events)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -76,6 +81,11 @@ func Parse(r io.Reader) ([]*Event, error) { ...@@ -76,6 +81,11 @@ func Parse(r io.Reader) ([]*Event, error) {
ev.Stk = stacks[ev.StkID] ev.Stk = stacks[ev.StkID]
} }
} }
if ver < 1007 && bin != "" {
if err := symbolize(events, bin); err != nil {
return nil, err
}
}
return events, nil return events, nil
} }
...@@ -88,61 +98,82 @@ type rawEvent struct { ...@@ -88,61 +98,82 @@ type rawEvent struct {
// readTrace does wire-format parsing and verification. // readTrace does wire-format parsing and verification.
// It does not care about specific event types and argument meaning. // It does not care about specific event types and argument meaning.
func readTrace(r io.Reader) ([]rawEvent, map[uint64]string, error) { func readTrace(r io.Reader) (ver int, events []rawEvent, strings map[uint64]string, err error) {
// Read and validate trace header. // Read and validate trace header.
var buf [16]byte var buf [16]byte
off, err := r.Read(buf[:]) off, err := io.ReadFull(r, buf[:])
if off != 16 || err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to read header: read %v, err %v", off, err) err = fmt.Errorf("failed to read header: read %v, err %v", off, err)
return
} }
if !bytes.Equal(buf[:], []byte("go 1.5 trace\x00\x00\x00\x00")) { ver, err = parseHeader(buf[:])
return nil, nil, fmt.Errorf("not a trace file") if err != nil {
return
}
switch ver {
case 1005, 1007:
break
default:
err = fmt.Errorf("unsupported trace file version %v.%v (update Go toolchain) %v", ver/1000, ver%1000, ver)
return
} }
// Read events. // Read events.
var events []rawEvent strings = make(map[uint64]string)
strings := make(map[uint64]string)
for { for {
// Read event type and number of arguments (1 byte). // Read event type and number of arguments (1 byte).
off0 := off off0 := off
n, err := r.Read(buf[:1]) var n int
n, err = r.Read(buf[:1])
if err == io.EOF { if err == io.EOF {
err = nil
break break
} }
if err != nil || n != 1 { if err != nil || n != 1 {
return nil, nil, fmt.Errorf("failed to read trace at offset 0x%x: n=%v err=%v", off0, n, err) err = fmt.Errorf("failed to read trace at offset 0x%x: n=%v err=%v", off0, n, err)
return
} }
off += n off += n
typ := buf[0] << 2 >> 2 typ := buf[0] << 2 >> 2
narg := buf[0] >> 6 narg := buf[0] >> 6
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 { if typ == EvString {
// String dictionary entry [ID, length, string]. // String dictionary entry [ID, length, string].
var id uint64 var id uint64
id, off, err = readVal(r, off) id, off, err = readVal(r, off)
if err != nil { if err != nil {
return nil, nil, err return
} }
if id == 0 { if id == 0 {
return nil, nil, fmt.Errorf("string at offset %d has invalid id 0", off) err = fmt.Errorf("string at offset %d has invalid id 0", off)
return
} }
if strings[id] != "" { if strings[id] != "" {
return nil, nil, fmt.Errorf("string at offset %d has duplicate id %v", off, id) err = fmt.Errorf("string at offset %d has duplicate id %v", off, id)
return
} }
var ln uint64 var ln uint64
ln, off, err = readVal(r, off) ln, off, err = readVal(r, off)
if err != nil { if err != nil {
return nil, nil, err return
} }
if ln == 0 { if ln == 0 {
return nil, nil, fmt.Errorf("string at offset %d has invalid length 0", off) err = fmt.Errorf("string at offset %d has invalid length 0", off)
return
} }
if ln > 1e6 { if ln > 1e6 {
return nil, nil, fmt.Errorf("string at offset %d has too large length %v", off, ln) err = fmt.Errorf("string at offset %d has too large length %v", off, ln)
return
} }
buf := make([]byte, ln) buf := make([]byte, ln)
n, err := io.ReadFull(r, buf) var n int
n, err = io.ReadFull(r, buf)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to read trace at offset %d: read %v, want %v, error %v", off, n, ln, err) err = fmt.Errorf("failed to read trace at offset %d: read %v, want %v, error %v", off, n, ln, err)
return
} }
off += n off += n
strings[id] = string(buf) strings[id] = string(buf)
...@@ -154,7 +185,8 @@ func readTrace(r io.Reader) ([]rawEvent, map[uint64]string, error) { ...@@ -154,7 +185,8 @@ func readTrace(r io.Reader) ([]rawEvent, map[uint64]string, error) {
var v uint64 var v uint64
v, off, err = readVal(r, off) v, off, err = readVal(r, off)
if err != nil { if err != nil {
return nil, nil, err err = fmt.Errorf("failed to read event %v argument at offset %v (%v)", typ, off, err)
return
} }
ev.args = append(ev.args, v) ev.args = append(ev.args, v)
} }
...@@ -163,39 +195,62 @@ func readTrace(r io.Reader) ([]rawEvent, map[uint64]string, error) { ...@@ -163,39 +195,62 @@ func readTrace(r io.Reader) ([]rawEvent, map[uint64]string, error) {
var v uint64 var v uint64
v, off, err = readVal(r, off) v, off, err = readVal(r, off)
if err != nil { if err != nil {
return nil, nil, err err = fmt.Errorf("failed to read event %v argument at offset %v (%v)", typ, off, err)
return
} }
evLen := v evLen := v
off1 := off off1 := off
for evLen > uint64(off-off1) { for evLen > uint64(off-off1) {
v, off, err = readVal(r, off) v, off, err = readVal(r, off)
if err != nil { if err != nil {
return nil, nil, err err = fmt.Errorf("failed to read event %v argument at offset %v (%v)", typ, off, err)
return
} }
ev.args = append(ev.args, v) ev.args = append(ev.args, v)
} }
if evLen != uint64(off-off1) { if evLen != uint64(off-off1) {
return nil, nil, fmt.Errorf("event has wrong length at offset 0x%x: want %v, got %v", off0, evLen, off-off1) err = fmt.Errorf("event has wrong length at offset 0x%x: want %v, got %v", off0, evLen, off-off1)
return
} }
} }
events = append(events, ev) events = append(events, ev)
} }
return events, strings, nil return
}
// 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. // Parse events transforms raw events into events.
// It does analyze and verify per-event-type arguments. // It does analyze and verify per-event-type arguments.
func parseEvents(rawEvents []rawEvent, strings map[uint64]string) (events []*Event, stacks map[uint64][]*Frame, err error) { func parseEvents(ver int, rawEvents []rawEvent, strings map[uint64]string) (events []*Event, stacks map[uint64][]*Frame, err error) {
var ticksPerSec, lastSeq, lastTs int64 var ticksPerSec, lastSeq, lastTs int64
var lastG, timerGoid uint64 var lastG, timerGoid uint64
var lastP int var lastP int
lastGs := make(map[int]uint64) // last goroutine running on P lastGs := make(map[int]uint64) // last goroutine running on P
stacks = make(map[uint64][]*Frame) stacks = make(map[uint64][]*Frame)
for _, raw := range rawEvents { for _, raw := range rawEvents {
if raw.typ == EvNone || raw.typ >= EvCount {
err = fmt.Errorf("unknown event type %v at offset 0x%x", raw.typ, raw.off)
return
}
desc := EventDescriptions[raw.typ] desc := EventDescriptions[raw.typ]
if desc.Name == "" { if desc.Name == "" {
err = fmt.Errorf("missing description for event type %v", raw.typ) err = fmt.Errorf("missing description for event type %v", raw.typ)
...@@ -246,7 +301,11 @@ func parseEvents(rawEvents []rawEvent, strings map[uint64]string) (events []*Eve ...@@ -246,7 +301,11 @@ func parseEvents(rawEvents []rawEvent, strings map[uint64]string) (events []*Eve
raw.off, size) raw.off, size)
return return
} }
if want := 2 + 4*size; uint64(len(raw.args)) != want { 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", err = fmt.Errorf("EvStack has wrong number of arguments at offset 0x%x: want %v, got %v",
raw.off, want, len(raw.args)) raw.off, want, len(raw.args))
return return
...@@ -255,19 +314,23 @@ func parseEvents(rawEvents []rawEvent, strings map[uint64]string) (events []*Eve ...@@ -255,19 +314,23 @@ func parseEvents(rawEvents []rawEvent, strings map[uint64]string) (events []*Eve
if id != 0 && size > 0 { if id != 0 && size > 0 {
stk := make([]*Frame, size) stk := make([]*Frame, size)
for i := 0; i < int(size); i++ { 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] pc := raw.args[2+i*4+0]
fn := raw.args[2+i*4+1] fn := raw.args[2+i*4+1]
file := raw.args[2+i*4+2] file := raw.args[2+i*4+2]
line := raw.args[2+i*4+3] line := raw.args[2+i*4+3]
stk[i] = &Frame{PC: pc, Fn: strings[fn], File: strings[file], Line: int(line)} stk[i] = &Frame{PC: pc, Fn: strings[fn], File: strings[file], Line: int(line)}
} }
}
stacks[id] = stk stacks[id] = stk
} }
default: default:
e := &Event{Off: raw.off, Type: raw.typ, P: lastP, G: lastG} e := &Event{Off: raw.off, Type: raw.typ, P: lastP, G: lastG}
e.Seq = lastSeq + int64(raw.args[0]) e.seq = lastSeq + int64(raw.args[0])
e.Ts = lastTs + int64(raw.args[1]) e.Ts = lastTs + int64(raw.args[1])
lastSeq = e.Seq lastSeq = e.seq
lastTs = e.Ts lastTs = e.Ts
for i := range desc.Args { for i := range desc.Args {
e.Args[i] = raw.args[i+2] e.Args[i] = raw.args[i+2]
...@@ -289,7 +352,7 @@ func parseEvents(rawEvents []rawEvent, strings map[uint64]string) (events []*Eve ...@@ -289,7 +352,7 @@ func parseEvents(rawEvents []rawEvent, strings map[uint64]string) (events []*Eve
case EvGoSysExit: case EvGoSysExit:
// EvGoSysExit emission is delayed until the thread has a P. // EvGoSysExit emission is delayed until the thread has a P.
// Give it the real sequence number and time stamp. // Give it the real sequence number and time stamp.
e.Seq = int64(e.Args[1]) e.seq = int64(e.Args[1])
if e.Args[2] != 0 { if e.Args[2] != 0 {
e.Ts = int64(e.Args[2]) e.Ts = int64(e.Args[2])
} }
...@@ -387,7 +450,7 @@ var ErrTimeOrder = fmt.Errorf("time stamps out of order") ...@@ -387,7 +450,7 @@ var ErrTimeOrder = fmt.Errorf("time stamps out of order")
// The resulting trace is guaranteed to be consistent // 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 // (for example, a P does not run two Gs at the same time, or a G is indeed
// blocked before an unblock event). // blocked before an unblock event).
func postProcessTrace(events []*Event) error { func postProcessTrace(ver int, events []*Event) error {
const ( const (
gDead = iota gDead = iota
gRunnable gRunnable
...@@ -510,7 +573,12 @@ func postProcessTrace(events []*Event) error { ...@@ -510,7 +573,12 @@ func postProcessTrace(events []*Event) error {
g.evStart = ev g.evStart = ev
p.g = ev.G p.g = ev.G
if g.evCreate != nil { 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] ev.StkID = g.evCreate.Args[1]
}
g.evCreate = nil g.evCreate = nil
} }
...@@ -611,6 +679,79 @@ func postProcessTrace(events []*Event) error { ...@@ -611,6 +679,79 @@ func postProcessTrace(events []*Event) error {
return nil 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("go", "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. // readVal reads unsigned base-128 value from r.
func readVal(r io.Reader, off0 int) (v uint64, off int, err error) { func readVal(r io.Reader, off0 int) (v uint64, off int, err error) {
off = off0 off = off0
...@@ -637,7 +778,7 @@ func (l eventList) Len() int { ...@@ -637,7 +778,7 @@ func (l eventList) Len() int {
} }
func (l eventList) Less(i, j int) bool { func (l eventList) Less(i, j int) bool {
return l[i].Seq < l[j].Seq return l[i].seq < l[j].seq
} }
func (l eventList) Swap(i, j int) { func (l eventList) Swap(i, j int) {
...@@ -702,45 +843,46 @@ const ( ...@@ -702,45 +843,46 @@ const (
var EventDescriptions = [EvCount]struct { var EventDescriptions = [EvCount]struct {
Name string Name string
minVersion int
Stack bool Stack bool
Args []string Args []string
}{ }{
EvNone: {"None", false, []string{}}, EvNone: {"None", 1005, false, []string{}},
EvBatch: {"Batch", false, []string{"p", "seq", "ticks"}}, EvBatch: {"Batch", 1005, false, []string{"p", "seq", "ticks"}},
EvFrequency: {"Frequency", false, []string{"freq", "unused"}}, EvFrequency: {"Frequency", 1005, false, []string{"freq", "unused"}},
EvStack: {"Stack", false, []string{"id", "siz"}}, EvStack: {"Stack", 1005, false, []string{"id", "siz"}},
EvGomaxprocs: {"Gomaxprocs", true, []string{"procs"}}, EvGomaxprocs: {"Gomaxprocs", 1005, true, []string{"procs"}},
EvProcStart: {"ProcStart", false, []string{"thread"}}, EvProcStart: {"ProcStart", 1005, false, []string{"thread"}},
EvProcStop: {"ProcStop", false, []string{}}, EvProcStop: {"ProcStop", 1005, false, []string{}},
EvGCStart: {"GCStart", true, []string{}}, EvGCStart: {"GCStart", 1005, true, []string{}},
EvGCDone: {"GCDone", false, []string{}}, EvGCDone: {"GCDone", 1005, false, []string{}},
EvGCScanStart: {"GCScanStart", false, []string{}}, EvGCScanStart: {"GCScanStart", 1005, false, []string{}},
EvGCScanDone: {"GCScanDone", false, []string{}}, EvGCScanDone: {"GCScanDone", 1005, false, []string{}},
EvGCSweepStart: {"GCSweepStart", true, []string{}}, EvGCSweepStart: {"GCSweepStart", 1005, true, []string{}},
EvGCSweepDone: {"GCSweepDone", false, []string{}}, EvGCSweepDone: {"GCSweepDone", 1005, false, []string{}},
EvGoCreate: {"GoCreate", true, []string{"g", "stack"}}, EvGoCreate: {"GoCreate", 1005, true, []string{"g", "stack"}},
EvGoStart: {"GoStart", false, []string{"g"}}, EvGoStart: {"GoStart", 1005, false, []string{"g"}},
EvGoEnd: {"GoEnd", false, []string{}}, EvGoEnd: {"GoEnd", 1005, false, []string{}},
EvGoStop: {"GoStop", true, []string{}}, EvGoStop: {"GoStop", 1005, true, []string{}},
EvGoSched: {"GoSched", true, []string{}}, EvGoSched: {"GoSched", 1005, true, []string{}},
EvGoPreempt: {"GoPreempt", true, []string{}}, EvGoPreempt: {"GoPreempt", 1005, true, []string{}},
EvGoSleep: {"GoSleep", true, []string{}}, EvGoSleep: {"GoSleep", 1005, true, []string{}},
EvGoBlock: {"GoBlock", true, []string{}}, EvGoBlock: {"GoBlock", 1005, true, []string{}},
EvGoUnblock: {"GoUnblock", true, []string{"g"}}, EvGoUnblock: {"GoUnblock", 1005, true, []string{"g"}},
EvGoBlockSend: {"GoBlockSend", true, []string{}}, EvGoBlockSend: {"GoBlockSend", 1005, true, []string{}},
EvGoBlockRecv: {"GoBlockRecv", true, []string{}}, EvGoBlockRecv: {"GoBlockRecv", 1005, true, []string{}},
EvGoBlockSelect: {"GoBlockSelect", true, []string{}}, EvGoBlockSelect: {"GoBlockSelect", 1005, true, []string{}},
EvGoBlockSync: {"GoBlockSync", true, []string{}}, EvGoBlockSync: {"GoBlockSync", 1005, true, []string{}},
EvGoBlockCond: {"GoBlockCond", true, []string{}}, EvGoBlockCond: {"GoBlockCond", 1005, true, []string{}},
EvGoBlockNet: {"GoBlockNet", true, []string{}}, EvGoBlockNet: {"GoBlockNet", 1005, true, []string{}},
EvGoSysCall: {"GoSysCall", true, []string{}}, EvGoSysCall: {"GoSysCall", 1005, true, []string{}},
EvGoSysExit: {"GoSysExit", false, []string{"g", "seq", "ts"}}, EvGoSysExit: {"GoSysExit", 1005, false, []string{"g", "seq", "ts"}},
EvGoSysBlock: {"GoSysBlock", false, []string{}}, EvGoSysBlock: {"GoSysBlock", 1005, false, []string{}},
EvGoWaiting: {"GoWaiting", false, []string{"g"}}, EvGoWaiting: {"GoWaiting", 1005, false, []string{"g"}},
EvGoInSyscall: {"GoInSyscall", false, []string{"g"}}, EvGoInSyscall: {"GoInSyscall", 1005, false, []string{"g"}},
EvHeapAlloc: {"HeapAlloc", false, []string{"mem"}}, EvHeapAlloc: {"HeapAlloc", 1005, false, []string{"mem"}},
EvNextGC: {"NextGC", false, []string{"mem"}}, EvNextGC: {"NextGC", 1005, false, []string{"mem"}},
EvTimerGoroutine: {"TimerGoroutine", false, []string{"g", "unused"}}, EvTimerGoroutine: {"TimerGoroutine", 1005, false, []string{"g", "unused"}},
EvFutileWakeup: {"FutileWakeup", false, []string{}}, EvFutileWakeup: {"FutileWakeup", 1005, false, []string{}},
EvString: {"String", false, []string{}}, EvString: {"String", 1007, false, []string{}},
} }
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
package trace package trace
import ( import (
"bytes"
"io/ioutil"
"path/filepath"
"strings" "strings"
"testing" "testing"
) )
...@@ -22,9 +25,63 @@ func TestCorruptedInputs(t *testing.T) { ...@@ -22,9 +25,63 @@ func TestCorruptedInputs(t *testing.T) {
"go 1.5 trace\x00\x00\x00\x00\xc3\x0200", "go 1.5 trace\x00\x00\x00\x00\xc3\x0200",
} }
for _, data := range tests { for _, data := range tests {
events, err := Parse(strings.NewReader(data)) events, err := Parse(strings.NewReader(data), "")
if err == nil || events != nil { if err == nil || events != nil {
t.Fatalf("no error on input: %q\n", data) t.Fatalf("no error on input: %q", data)
}
}
}
func TestParseCanned(t *testing.T) {
files, err := ioutil.ReadDir("./testdata")
if err != nil {
t.Fatalf("failed to read ./testdata: %v", err)
}
for _, f := range files {
data, err := ioutil.ReadFile(filepath.Join("./testdata", f.Name()))
if err != nil {
t.Fatalf("failed to read input file: %v", err)
}
_, 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)
}
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())
}
}
}
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)
}
} }
} }
} }
...@@ -356,7 +356,7 @@ func ReadTrace() []byte { ...@@ -356,7 +356,7 @@ func ReadTrace() []byte {
trace.headerWritten = true trace.headerWritten = true
trace.lockOwner = nil trace.lockOwner = nil
unlock(&trace.lock) unlock(&trace.lock)
return []byte("go 1.5 trace\x00\x00\x00\x00") return []byte("go 1.7 trace\x00\x00\x00\x00")
} }
// Wait for new data. // Wait for new data.
if trace.fullHead == 0 && !trace.shutdown { if trace.fullHead == 0 && !trace.shutdown {
......
...@@ -52,7 +52,7 @@ func TestTrace(t *testing.T) { ...@@ -52,7 +52,7 @@ func TestTrace(t *testing.T) {
t.Fatalf("failed to start tracing: %v", err) t.Fatalf("failed to start tracing: %v", err)
} }
Stop() Stop()
_, err := trace.Parse(buf) _, err := trace.Parse(buf, "")
if err == trace.ErrTimeOrder { if err == trace.ErrTimeOrder {
t.Skipf("skipping trace: %v", err) t.Skipf("skipping trace: %v", err)
} }
...@@ -62,7 +62,7 @@ func TestTrace(t *testing.T) { ...@@ -62,7 +62,7 @@ func TestTrace(t *testing.T) {
} }
func parseTrace(t *testing.T, r io.Reader) ([]*trace.Event, map[uint64]*trace.GDesc, error) { func parseTrace(t *testing.T, r io.Reader) ([]*trace.Event, map[uint64]*trace.GDesc, error) {
events, err := trace.Parse(r) events, err := trace.Parse(r, "")
if err == trace.ErrTimeOrder { if err == trace.ErrTimeOrder {
t.Skipf("skipping trace: %v", err) t.Skipf("skipping trace: %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