1// Copyright 2024 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 pgo
6
7import (
8	"bufio"
9	"fmt"
10	"io"
11	"strings"
12	"strconv"
13)
14
15// IsSerialized returns true if r is a serialized Profile.
16//
17// IsSerialized only peeks at r, so seeking back after calling is not
18// necessary.
19func IsSerialized(r *bufio.Reader) (bool, error) {
20	hdr, err := r.Peek(len(serializationHeader))
21	if err == io.EOF {
22		// Empty file.
23		return false, nil
24	} else if err != nil {
25		return false, fmt.Errorf("error reading profile header: %w", err)
26	}
27
28	return string(hdr) == serializationHeader, nil
29}
30
31// FromSerialized parses a profile from serialization output of Profile.WriteTo.
32func FromSerialized(r io.Reader) (*Profile, error) {
33	d := emptyProfile()
34
35	scanner := bufio.NewScanner(r)
36	scanner.Split(bufio.ScanLines)
37
38	if !scanner.Scan() {
39		if err := scanner.Err(); err != nil {
40			return nil, fmt.Errorf("error reading preprocessed profile: %w", err)
41		}
42		return nil, fmt.Errorf("preprocessed profile missing header")
43	}
44	if gotHdr := scanner.Text() + "\n"; gotHdr != serializationHeader {
45		return nil, fmt.Errorf("preprocessed profile malformed header; got %q want %q", gotHdr, serializationHeader)
46	}
47
48	for scanner.Scan() {
49		readStr := scanner.Text()
50
51		callerName := readStr
52
53		if !scanner.Scan() {
54			if err := scanner.Err(); err != nil {
55				return nil, fmt.Errorf("error reading preprocessed profile: %w", err)
56			}
57			return nil, fmt.Errorf("preprocessed profile entry missing callee")
58		}
59		calleeName := scanner.Text()
60
61		if !scanner.Scan() {
62			if err := scanner.Err(); err != nil {
63				return nil, fmt.Errorf("error reading preprocessed profile: %w", err)
64			}
65			return nil, fmt.Errorf("preprocessed profile entry missing weight")
66		}
67		readStr = scanner.Text()
68
69		split := strings.Split(readStr, " ")
70
71		if len(split) != 2 {
72			return nil, fmt.Errorf("preprocessed profile entry got %v want 2 fields", split)
73		}
74
75		co, err := strconv.Atoi(split[0])
76		if err != nil {
77			return nil, fmt.Errorf("preprocessed profile error processing call line: %w", err)
78		}
79
80		edge := NamedCallEdge{
81			CallerName:     callerName,
82			CalleeName:     calleeName,
83			CallSiteOffset: co,
84		}
85
86		weight, err := strconv.ParseInt(split[1], 10, 64)
87		if err != nil {
88			return nil, fmt.Errorf("preprocessed profile error processing call weight: %w", err)
89		}
90
91		if _, ok := d.NamedEdgeMap.Weight[edge]; ok {
92			return nil, fmt.Errorf("preprocessed profile contains duplicate edge %+v", edge)
93		}
94
95		d.NamedEdgeMap.ByWeight = append(d.NamedEdgeMap.ByWeight, edge) // N.B. serialization is ordered.
96		d.NamedEdgeMap.Weight[edge] += weight
97		d.TotalWeight += weight
98	}
99
100	return d, nil
101
102}
103