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. 4 5package raw 6 7import ( 8 "bufio" 9 "fmt" 10 "io" 11 "strconv" 12 "strings" 13 "unicode" 14 15 "internal/trace/event" 16 "internal/trace/version" 17) 18 19// TextReader parses a text format trace with only very basic validation 20// into an event stream. 21type TextReader struct { 22 v version.Version 23 specs []event.Spec 24 names map[string]event.Type 25 s *bufio.Scanner 26} 27 28// NewTextReader creates a new reader for the trace text format. 29func NewTextReader(r io.Reader) (*TextReader, error) { 30 tr := &TextReader{s: bufio.NewScanner(r)} 31 line, err := tr.nextLine() 32 if err != nil { 33 return nil, err 34 } 35 trace, line := readToken(line) 36 if trace != "Trace" { 37 return nil, fmt.Errorf("failed to parse header") 38 } 39 gover, line := readToken(line) 40 if !strings.HasPrefix(gover, "Go1.") { 41 return nil, fmt.Errorf("failed to parse header Go version") 42 } 43 rawv, err := strconv.ParseUint(gover[len("Go1."):], 10, 64) 44 if err != nil { 45 return nil, fmt.Errorf("failed to parse header Go version: %v", err) 46 } 47 v := version.Version(rawv) 48 if !v.Valid() { 49 return nil, fmt.Errorf("unknown or unsupported Go version 1.%d", v) 50 } 51 tr.v = v 52 tr.specs = v.Specs() 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) 57 } 58 } 59 return tr, nil 60} 61 62// Version returns the version of the trace that we're reading. 63func (r *TextReader) Version() version.Version { 64 return r.v 65} 66 67// ReadEvent reads and returns the next trace event in the text stream. 68func (r *TextReader) ReadEvent() (Event, error) { 69 line, err := r.nextLine() 70 if err != nil { 71 return Event{}, err 72 } 73 evStr, line := readToken(line) 74 ev, ok := r.names[evStr] 75 if !ok { 76 return Event{}, fmt.Errorf("unidentified event: %s", evStr) 77 } 78 spec := r.specs[ev] 79 args, err := readArgs(line, spec.Args) 80 if err != nil { 81 return Event{}, fmt.Errorf("reading args for %s: %v", evStr, err) 82 } 83 if spec.IsStack { 84 len := int(args[1]) 85 for i := 0; i < len; i++ { 86 line, err := r.nextLine() 87 if err == io.EOF { 88 return Event{}, fmt.Errorf("unexpected EOF while reading stack: args=%v", args) 89 } 90 if err != nil { 91 return Event{}, err 92 } 93 frame, err := readArgs(line, frameFields) 94 if err != nil { 95 return Event{}, err 96 } 97 args = append(args, frame...) 98 } 99 } 100 var data []byte 101 if spec.HasData { 102 line, err := r.nextLine() 103 if err == io.EOF { 104 return Event{}, fmt.Errorf("unexpected EOF while reading data for %s: args=%v", evStr, args) 105 } 106 if err != nil { 107 return Event{}, err 108 } 109 data, err = readData(line) 110 if err != nil { 111 return Event{}, err 112 } 113 } 114 return Event{ 115 Version: r.v, 116 Ev: ev, 117 Args: args, 118 Data: data, 119 }, nil 120} 121 122func (r *TextReader) nextLine() (string, error) { 123 for { 124 if !r.s.Scan() { 125 if err := r.s.Err(); err != nil { 126 return "", err 127 } 128 return "", io.EOF 129 } 130 txt := r.s.Text() 131 tok, _ := readToken(txt) 132 if tok == "" { 133 continue // Empty line or comment. 134 } 135 return txt, nil 136 } 137} 138 139var frameFields = []string{"pc", "func", "file", "line"} 140 141func readArgs(s string, names []string) ([]uint64, error) { 142 var args []uint64 143 for _, name := range names { 144 arg, value, rest, err := readArg(s) 145 if err != nil { 146 return nil, err 147 } 148 if arg != name { 149 return nil, fmt.Errorf("expected argument %q, but got %q", name, arg) 150 } 151 args = append(args, value) 152 s = rest 153 } 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) 157 } 158 } 159 return args, nil 160} 161 162func readArg(s string) (arg string, value uint64, rest string, err error) { 163 var tok string 164 tok, rest = readToken(s) 165 if len(tok) == 0 { 166 return "", 0, s, fmt.Errorf("no argument") 167 } 168 parts := strings.SplitN(tok, "=", 2) 169 if len(parts) < 2 { 170 return "", 0, s, fmt.Errorf("malformed argument: %q", tok) 171 } 172 arg = parts[0] 173 value, err = strconv.ParseUint(parts[1], 10, 64) 174 if err != nil { 175 return arg, value, s, fmt.Errorf("failed to parse argument value %q for arg %q", parts[1], parts[0]) 176 } 177 return 178} 179 180func readToken(s string) (token, rest string) { 181 tkStart := -1 182 for i, r := range s { 183 if r == '#' { 184 return "", "" 185 } 186 if !unicode.IsSpace(r) { 187 tkStart = i 188 break 189 } 190 } 191 if tkStart < 0 { 192 return "", "" 193 } 194 tkEnd := -1 195 for i, r := range s[tkStart:] { 196 if unicode.IsSpace(r) || r == '#' { 197 tkEnd = i + tkStart 198 break 199 } 200 } 201 if tkEnd < 0 { 202 return s[tkStart:], "" 203 } 204 return s[tkStart:tkEnd], s[tkEnd:] 205} 206 207func 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) 211 } 212 data, err := strconv.Unquote(strings.TrimSpace(parts[1])) 213 if err != nil { 214 return nil, fmt.Errorf("failed to parse data: %q: %v", line, err) 215 } 216 return []byte(data), nil 217} 218