| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package pgo |
| |
| import ( |
| "bufio" |
| "fmt" |
| "io" |
| "strings" |
| "strconv" |
| ) |
| |
| // IsSerialized returns true if r is a serialized Profile. |
| // |
| // IsSerialized only peeks at r, so seeking back after calling is not |
| // necessary. |
| func IsSerialized(r *bufio.Reader) (bool, error) { |
| hdr, err := r.Peek(len(serializationHeader)) |
| if err == io.EOF { |
| // Empty file. |
| return false, nil |
| } else if err != nil { |
| return false, fmt.Errorf("error reading profile header: %w", err) |
| } |
| |
| return string(hdr) == serializationHeader, nil |
| } |
| |
| // FromSerialized parses a profile from serialization output of Profile.WriteTo. |
| func FromSerialized(r io.Reader) (*Profile, error) { |
| d := emptyProfile() |
| |
| scanner := bufio.NewScanner(r) |
| scanner.Split(bufio.ScanLines) |
| |
| if !scanner.Scan() { |
| if err := scanner.Err(); err != nil { |
| return nil, fmt.Errorf("error reading preprocessed profile: %w", err) |
| } |
| return nil, fmt.Errorf("preprocessed profile missing header") |
| } |
| if gotHdr := scanner.Text() + "\n"; gotHdr != serializationHeader { |
| return nil, fmt.Errorf("preprocessed profile malformed header; got %q want %q", gotHdr, serializationHeader) |
| } |
| |
| for scanner.Scan() { |
| readStr := scanner.Text() |
| |
| callerName := readStr |
| |
| if !scanner.Scan() { |
| if err := scanner.Err(); err != nil { |
| return nil, fmt.Errorf("error reading preprocessed profile: %w", err) |
| } |
| return nil, fmt.Errorf("preprocessed profile entry missing callee") |
| } |
| calleeName := scanner.Text() |
| |
| if !scanner.Scan() { |
| if err := scanner.Err(); err != nil { |
| return nil, fmt.Errorf("error reading preprocessed profile: %w", err) |
| } |
| return nil, fmt.Errorf("preprocessed profile entry missing weight") |
| } |
| readStr = scanner.Text() |
| |
| split := strings.Split(readStr, " ") |
| |
| if len(split) != 2 { |
| return nil, fmt.Errorf("preprocessed profile entry got %v want 2 fields", split) |
| } |
| |
| co, err := strconv.Atoi(split[0]) |
| if err != nil { |
| return nil, fmt.Errorf("preprocessed profile error processing call line: %w", err) |
| } |
| |
| edge := NamedCallEdge{ |
| CallerName: callerName, |
| CalleeName: calleeName, |
| CallSiteOffset: co, |
| } |
| |
| weight, err := strconv.ParseInt(split[1], 10, 64) |
| if err != nil { |
| return nil, fmt.Errorf("preprocessed profile error processing call weight: %w", err) |
| } |
| |
| if _, ok := d.NamedEdgeMap.Weight[edge]; ok { |
| return nil, fmt.Errorf("preprocessed profile contains duplicate edge %+v", edge) |
| } |
| |
| d.NamedEdgeMap.ByWeight = append(d.NamedEdgeMap.ByWeight, edge) // N.B. serialization is ordered. |
| d.NamedEdgeMap.Weight[edge] += weight |
| d.TotalWeight += weight |
| } |
| |
| return d, nil |
| |
| } |