Source file src/internal/trace/raw/textreader.go

     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  	"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.
    21  type 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.
    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 !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.
    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() (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  
   139  var frameFields = []string{"pc", "func", "file", "line"}
   140  
   141  func 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  
   162  func 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  
   180  func 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  
   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)
   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  

View as plain text