github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/convert/parser.go (about)

     1  package convert
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"compress/gzip"
     7  	"fmt"
     8  	"io"
     9  	"strconv"
    10  
    11  	"google.golang.org/protobuf/proto"
    12  
    13  	"github.com/pyroscope-io/pyroscope/pkg/storage/tree"
    14  )
    15  
    16  func ParseTreeNoDict(r io.Reader, cb func(name []byte, val int)) error {
    17  	t, err := tree.DeserializeNoDict(r)
    18  	if err != nil {
    19  		return err
    20  	}
    21  	t.Iterate(func(name []byte, val uint64) {
    22  		if len(name) > 2 && val != 0 {
    23  			cb(name[2:], int(val))
    24  		}
    25  	})
    26  	return nil
    27  }
    28  
    29  var gzipMagicBytes = []byte{0x1f, 0x8b}
    30  
    31  // format is pprof. See https://github.com/google/pprof/blob/master/proto/profile.proto
    32  func ParsePprof(r io.Reader) (*tree.Profile, error) {
    33  	// this allows us to support both gzipped and not gzipped pprof
    34  	// TODO: this might be allocating too much extra memory, maybe optimize later
    35  	bufioReader := bufio.NewReader(r)
    36  	header, err := bufioReader.Peek(2)
    37  	if err != nil {
    38  		return nil, fmt.Errorf("unable to read profile file header: %w", err)
    39  	}
    40  
    41  	if header[0] == gzipMagicBytes[0] && header[1] == gzipMagicBytes[1] {
    42  		r, err = gzip.NewReader(bufioReader)
    43  		if err != nil {
    44  			return nil, err
    45  		}
    46  	} else {
    47  		r = bufioReader
    48  	}
    49  
    50  	b, err := io.ReadAll(r)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	profile := &tree.Profile{}
    56  	if err := proto.Unmarshal(b, profile); err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	return profile, nil
    61  }
    62  
    63  // format:
    64  // stack-trace-foo 1
    65  // stack-trace-bar 2
    66  func ParseGroups(r io.Reader, cb func(name []byte, val int)) error {
    67  	scanner := bufio.NewScanner(r)
    68  	for scanner.Scan() {
    69  		if err := scanner.Err(); err != nil {
    70  			return err
    71  		}
    72  
    73  		line := scanner.Bytes()
    74  		line2 := make([]byte, len(line))
    75  		copy(line2, line)
    76  
    77  		index := bytes.LastIndexByte(line2, byte(' '))
    78  		if index == -1 {
    79  			continue
    80  		}
    81  		stacktrace := line2[:index]
    82  		count := line2[index+1:]
    83  
    84  		i, err := strconv.Atoi(string(count))
    85  		if err != nil {
    86  			return err
    87  		}
    88  		cb(stacktrace, i)
    89  	}
    90  	return nil
    91  }
    92  
    93  // format:
    94  // stack-trace-foo
    95  // stack-trace-bar
    96  // stack-trace-bar
    97  func ParseIndividualLines(r io.Reader, cb func(name []byte, val int)) error {
    98  	groups := make(map[string]int)
    99  	scanner := bufio.NewScanner(r)
   100  	// scanner.Buffer(make([]byte, bufio.MaxScanTokenSize*100), bufio.MaxScanTokenSize*100)
   101  	// scanner.Split(bufio.ScanLines)
   102  	for scanner.Scan() {
   103  		if err := scanner.Err(); err != nil {
   104  			return err
   105  		}
   106  		key := scanner.Text()
   107  		if _, ok := groups[key]; !ok {
   108  			groups[key] = 0
   109  		}
   110  		groups[key]++
   111  	}
   112  
   113  	if err := scanner.Err(); err != nil {
   114  		return err
   115  	}
   116  
   117  	for k, v := range groups {
   118  		if k != "" {
   119  			cb([]byte(k), v)
   120  		}
   121  	}
   122  
   123  	return nil
   124  }