1 // Copyright 2023 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
15 "internal/trace/v2/event"
16 "internal/trace/v2/version"
19 // TextReader parses a text format trace with only very basic validation
20 // into an event stream.
21 type TextReader struct {
24 names map[string]event.Type
28 // NewTextReader creates a new reader for the trace text format.
29 func NewTextReader(r io.Reader) (*TextReader, error) {
30 tr := &TextReader{s: bufio.NewScanner(r)}
31 line, err := tr.nextLine()
35 trace, line := readToken(line)
37 return nil, fmt.Errorf("failed to parse header")
39 gover, line := readToken(line)
40 if !strings.HasPrefix(gover, "Go1.") {
41 return nil, fmt.Errorf("failed to parse header Go version")
43 rawv, err := strconv.ParseUint(gover[len("Go1."):], 10, 64)
45 return nil, fmt.Errorf("failed to parse header Go version: %v", err)
47 v := version.Version(rawv)
49 return nil, fmt.Errorf("unknown or unsupported Go version 1.%d", v)
53 tr.names = event.Names(tr.specs)
54 for _, r := range line {
55 if !unicode.IsSpace(r) {
56 return nil, fmt.Errorf("encountered unexpected non-space at the end of the header: %q", line)
62 // Version returns the version of the trace that we're reading.
63 func (r *TextReader) Version() version.Version {
67 // ReadEvent reads and returns the next trace event in the text stream.
68 func (r *TextReader) ReadEvent() (Event, error) {
69 line, err := r.nextLine()
73 evStr, line := readToken(line)
74 ev, ok := r.names[evStr]
76 return Event{}, fmt.Errorf("unidentified event: %s", evStr)
79 args, err := readArgs(line, spec.Args)
81 return Event{}, fmt.Errorf("reading args for %s: %v", evStr, err)
85 for i := 0; i < len; i++ {
86 line, err := r.nextLine()
88 return Event{}, fmt.Errorf("unexpected EOF while reading stack: args=%v", args)
93 frame, err := readArgs(line, frameFields)
97 args = append(args, frame...)
102 line, err := r.nextLine()
104 return Event{}, fmt.Errorf("unexpected EOF while reading data for %s: args=%v", evStr, args)
109 data, err = readData(line)
122 func (r *TextReader) nextLine() (string, error) {
125 if err := r.s.Err(); err != nil {
131 tok, _ := readToken(txt)
133 continue // Empty line or comment.
139 var frameFields = []string{"pc", "func", "file", "line"}
141 func readArgs(s string, names []string) ([]uint64, error) {
143 for _, name := range names {
144 arg, value, rest, err := readArg(s)
149 return nil, fmt.Errorf("expected argument %q, but got %q", name, arg)
151 args = append(args, value)
154 for _, r := range s {
155 if !unicode.IsSpace(r) {
156 return nil, fmt.Errorf("encountered unexpected non-space at the end of an event: %q", s)
162 func readArg(s string) (arg string, value uint64, rest string, err error) {
164 tok, rest = readToken(s)
166 return "", 0, s, fmt.Errorf("no argument")
168 parts := strings.SplitN(tok, "=", 2)
170 return "", 0, s, fmt.Errorf("malformed argument: %q", tok)
173 value, err = strconv.ParseUint(parts[1], 10, 64)
175 return arg, value, s, fmt.Errorf("failed to parse argument value %q for arg %q", parts[1], parts[0])
180 func readToken(s string) (token, rest string) {
182 for i, r := range s {
186 if !unicode.IsSpace(r) {
195 for i, r := range s[tkStart:] {
196 if unicode.IsSpace(r) || r == '#' {
202 return s[tkStart:], ""
204 return s[tkStart:tkEnd], s[tkEnd:]
207 func readData(line string) ([]byte, error) {
208 parts := strings.SplitN(line, "=", 2)
209 if len(parts) < 2 || strings.TrimSpace(parts[0]) != "data" {
210 return nil, fmt.Errorf("malformed data: %q", line)
212 data, err := strconv.Unquote(strings.TrimSpace(parts[1]))
214 return nil, fmt.Errorf("failed to parse data: %q: %v", line, err)
216 return []byte(data), nil