aboutsummaryrefslogtreecommitdiff
path: root/src/internal/profile
diff options
context:
space:
mode:
authorMichael Pratt <mpratt@google.com>2023-12-06 13:29:03 -0500
committerMichael Pratt <mpratt@google.com>2023-12-07 19:52:28 +0000
commite1c0349a7c607cdfcfa8f7c0c6b70aceff9d3752 (patch)
tree867a43ae4a6ca02387b5074d273e0be3b4714ca3 /src/internal/profile
parentc71eedf90aff3fc73a645b88d2e5166b8a0179fd (diff)
downloadgo-e1c0349a7c607cdfcfa8f7c0c6b70aceff9d3752.tar.xz
internal/profile: fully decode proto even if there are no samples
This is a partial revert of CL 483137. CL 483137 started checking errors in postDecode, which is good. Now we can catch more malformed pprof protos. However this made TestEmptyProfile fail, so an early return was added when the profile was "empty" (no samples). Unfortunately, this was problematic. Profiles with no samples can still be valid, but skipping postDecode meant that the resulting Profile was missing values from the string table. In particular, net/http/pprof needs to parse empty profiles in order to pass through the sample and period types to a final output proto. CL 483137 broke this behavior. internal/profile.Parse is only used in two places: in cmd/compile to parse PGO pprof profiles, and in net/http/pprof to parse before/after pprof profiles for delta profiles. In both cases, the input is never literally empty (0 bytes). Even a pprof proto with no samples still contains some header fields, such as sample and period type. Upstream github.com/google/pprof/profile even has an explicit error on 0 byte input, so `go tool pprof` will not support such an input. Thus TestEmptyProfile was misleading; this profile doesn't need to support empty input at all. Resolve this by removing TestEmptyProfile and replacing it with an explicit error on empty input, as upstream github.com/google/pprof/profile has. For non-empty input, always run postDecode to ensure the string table is processed. TestConvertCPUProfileEmpty is reverted back to assert the values from before CL 483137. Note that in this case "Empty" means no samples, not a 0 byte input. Continue to allow empty files for PGO in order to minimize the chance of last minute breakage if some users have empty files. Fixes #64566. Change-Id: I83a1f0200ae225ac6da0009d4b2431fe215b283f Reviewed-on: https://go-review.googlesource.com/c/go/+/547996 Reviewed-by: Michael Knyszek <mknyszek@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Cherry Mui <cherryyz@google.com>
Diffstat (limited to 'src/internal/profile')
-rw-r--r--src/internal/profile/encode.go3
-rw-r--r--src/internal/profile/profile.go17
-rw-r--r--src/internal/profile/profile_test.go15
3 files changed, 13 insertions, 22 deletions
diff --git a/src/internal/profile/encode.go b/src/internal/profile/encode.go
index 72d6fe2fa7..94d04bf094 100644
--- a/src/internal/profile/encode.go
+++ b/src/internal/profile/encode.go
@@ -207,9 +207,6 @@ var profileDecoder = []decoder{
// suffix X) and populates the corresponding exported fields.
// The unexported fields are cleared up to facilitate testing.
func (p *Profile) postDecode() error {
- if p.Empty() {
- return nil
- }
var err error
mappings := make(map[uint64]*Mapping)
diff --git a/src/internal/profile/profile.go b/src/internal/profile/profile.go
index c779bb2b11..02d1bed3de 100644
--- a/src/internal/profile/profile.go
+++ b/src/internal/profile/profile.go
@@ -141,10 +141,14 @@ func Parse(r io.Reader) (*Profile, error) {
}
orig = data
}
- if p, err = parseUncompressed(orig); err != nil {
- if p, err = parseLegacy(orig); err != nil {
- return nil, fmt.Errorf("parsing profile: %v", err)
- }
+
+ var lErr error
+ p, pErr := parseUncompressed(orig)
+ if pErr != nil {
+ p, lErr = parseLegacy(orig)
+ }
+ if pErr != nil && lErr != nil {
+ return nil, fmt.Errorf("parsing profile: not a valid proto profile (%w) or legacy profile (%w)", pErr, lErr)
}
if err := p.CheckValid(); err != nil {
@@ -155,6 +159,7 @@ func Parse(r io.Reader) (*Profile, error) {
var errUnrecognized = fmt.Errorf("unrecognized profile format")
var errMalformed = fmt.Errorf("malformed profile format")
+var ErrNoData = fmt.Errorf("empty input file")
func parseLegacy(data []byte) (*Profile, error) {
parsers := []func([]byte) (*Profile, error){
@@ -180,6 +185,10 @@ func parseLegacy(data []byte) (*Profile, error) {
}
func parseUncompressed(data []byte) (*Profile, error) {
+ if len(data) == 0 {
+ return nil, ErrNoData
+ }
+
p := &Profile{}
if err := unmarshal(data, p); err != nil {
return nil, err
diff --git a/src/internal/profile/profile_test.go b/src/internal/profile/profile_test.go
index e1963f3351..84158b6233 100644
--- a/src/internal/profile/profile_test.go
+++ b/src/internal/profile/profile_test.go
@@ -5,24 +5,9 @@
package profile
import (
- "bytes"
"testing"
)
-func TestEmptyProfile(t *testing.T) {
- var buf bytes.Buffer
- p, err := Parse(&buf)
- if err != nil {
- t.Error("Want no error, got", err)
- }
- if p == nil {
- t.Fatal("Want a valid profile, got <nil>")
- }
- if !p.Empty() {
- t.Errorf("Profile should be empty, got %#v", p)
- }
-}
-
func TestParseContention(t *testing.T) {
tests := []struct {
name string