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