textreader.mx raw

   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  
   5  package raw
   6  
   7  import (
   8  	"bufio"
   9  	"fmt"
  10  	"io"
  11  	"strconv"
  12  	"bytes"
  13  	"unicode"
  14  
  15  	"internal/trace/tracev2"
  16  	"internal/trace/version"
  17  )
  18  
  19  // TextReader parses a text format trace with only very basic validation
  20  // into an event stream.
  21  type TextReader struct {
  22  	v     version.Version
  23  	specs []tracev2.EventSpec
  24  	names map[string]tracev2.EventType
  25  	s     *bufio.Scanner
  26  }
  27  
  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()
  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 !bytes.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 = tracev2.EventNames(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.
  63  func (r *TextReader) Version() version.Version {
  64  	return r.v
  65  }
  66  
  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()
  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  
 122  func (r *TextReader) nextLine() ([]byte, 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  
 139  var frameFields = [][]byte{"pc", "func", "file", "line"}
 140  
 141  func readArgs(s []byte, names [][]byte) ([]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  
 162  func readArg(s []byte) (arg []byte, value uint64, rest []byte, err error) {
 163  	var tok []byte
 164  	tok, rest = readToken(s)
 165  	if len(tok) == 0 {
 166  		return "", 0, s, fmt.Errorf("no argument")
 167  	}
 168  	parts := bytes.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  
 180  func readToken(s []byte) (token, rest []byte) {
 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  
 207  func readData(line []byte) ([]byte, error) {
 208  	parts := bytes.SplitN(line, "=", 2)
 209  	if len(parts) < 2 || bytes.TrimSpace(parts[0]) != "data" {
 210  		return nil, fmt.Errorf("malformed data: %q", line)
 211  	}
 212  	data, err := strconv.Unquote(bytes.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