Skip to content

Commit

Permalink
feat: add some basic flamebearer validation. (#785)
Browse files Browse the repository at this point in the history
* feat: add some basic flamebearer validation.

* fix: remove empty line at the end of block.
  • Loading branch information
abeaumont committed Feb 3, 2022
1 parent 1d431c9 commit bee6483
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 0 deletions.
3 changes: 3 additions & 0 deletions pkg/adhoc/server/convert.go
Expand Up @@ -21,6 +21,9 @@ func JSONToProfileV1(b []byte, _ string, _ int) (*flamebearer.FlamebearerProfile
if err := json.Unmarshal(b, &profile); err != nil {
return nil, fmt.Errorf("unable to unmarshall JSON: %w", err)
}
if err := profile.Validate(); err != nil {
return nil, fmt.Errorf("invalid profile: %w", err)
}
return &profile, nil
}

Expand Down
45 changes: 45 additions & 0 deletions pkg/structs/flamebearer/flamebearer.go
@@ -1,6 +1,8 @@
package flamebearer

import (
"fmt"

"github.com/pyroscope-io/pyroscope/pkg/storage"
"github.com/pyroscope-io/pyroscope/pkg/storage/segment"
"github.com/pyroscope-io/pyroscope/pkg/storage/tree"
Expand Down Expand Up @@ -100,3 +102,46 @@ func newTimeline(timeline *segment.Timeline) *FlamebearerTimelineV1 {
Watermarks: timeline.Watermarks,
}
}

func (fb FlamebearerProfile) Validate() error {
if fb.Version > 1 {
return fmt.Errorf("unsupported version %d", fb.Version)
}
return fb.FlamebearerProfileV1.Validate()
}

// Validate the V1 profile.
// A custom validation is used as the constraints are hard to define in a generic way
// (e.g. using https://github.com/go-playground/validator)
func (fb FlamebearerProfileV1) Validate() error {
format := tree.Format(fb.Metadata.Format)
if format != tree.FormatSingle && format != tree.FormatDouble {
return fmt.Errorf("unsupported format %s", format)
}
if len(fb.Flamebearer.Names) == 0 {
return fmt.Errorf("a profile must have at least one symbol name")
}
if len(fb.Flamebearer.Levels) == 0 {
return fmt.Errorf("a profile must have at least one profiling level")
}
var mod int
switch format {
case tree.FormatSingle:
mod = 4
case tree.FormatDouble:
mod = 7
default: // This shouldn't happen at this point.
return fmt.Errorf("unsupported format %s", format)
}
for _, l := range fb.Flamebearer.Levels {
if len(l)%mod != 0 {
return fmt.Errorf("a profile level should have a multiple of %d values, but there's a level with %d values", mod, len(l))
}
for i := mod - 1; i < len(l); i += mod {
if l[i] >= len(fb.Flamebearer.Names) {
return fmt.Errorf("invalid name index %d, it should be smaller than %d", l[i], len(fb.Flamebearer.Levels))
}
}
}
return nil
}
142 changes: 142 additions & 0 deletions pkg/structs/flamebearer/flamebearer_test.go
Expand Up @@ -69,6 +69,9 @@ var _ = Describe("FlamebearerProfile", func() {
// Ticks
Expect(p.LeftTicks).To(BeZero())
Expect(p.RightTicks).To(BeZero())

// Validate
Expect(p.Validate()).To(BeNil())
})
})

Expand Down Expand Up @@ -124,6 +127,145 @@ var _ = Describe("FlamebearerProfile", func() {
// Ticks
Expect(p.LeftTicks).To(Equal(uint64(3)))
Expect(p.RightTicks).To(Equal(uint64(12)))

// Validate
Expect(p.Validate()).To(BeNil())
})
})
})

var _ = Describe("Checking profile validation", func() {
When("the version is invalid", func() {
var fb FlamebearerProfile
BeforeEach(func() {
fb.Version = 2
})

Context("and we validate the profile", func() {
It("returns an error", func() {
Expect(fb.Validate()).To(MatchError("unsupported version 2"))
})
})
})

When("the format is unsupported", func() {
var fb FlamebearerProfile

Context("and we validate the profile", func() {
It("returns an error", func() {
Expect(fb.Validate()).To(MatchError("unsupported format "))
})
})
})

When("there are no names", func() {
var fb FlamebearerProfile
BeforeEach(func() {
fb.Metadata.Format = "single"
})

Context("and we validate the profile", func() {
It("returns an error", func() {
Expect(fb.Validate()).To(MatchError("a profile must have at least one symbol name"))
})
})
})

When("there are no levels", func() {
var fb FlamebearerProfile
BeforeEach(func() {
fb.Metadata.Format = "single"
fb.Flamebearer.Names = []string{"name"}
})

Context("and we validate the profile", func() {
It("returns an error", func() {
Expect(fb.Validate()).To(MatchError("a profile must have at least one profiling level"))
})
})
})

When("the level size is invalid for the profile format", func() {
Context("and we validate a single profile", func() {
var fb FlamebearerProfile
BeforeEach(func() {
fb.Metadata.Format = "single"
fb.Flamebearer.Names = []string{"name"}
fb.Flamebearer.Levels = [][]int{{0, 0, 0, 0, 0, 0, 0}}
})

It("returns an error", func() {
Expect(fb.Validate()).To(MatchError("a profile level should have a multiple of 4 values, but there's a level with 7 values"))
})
})

Context("and we validate a double profile", func() {
var fb FlamebearerProfile
BeforeEach(func() {
fb.Metadata.Format = "double"
fb.Flamebearer.Names = []string{"name"}
fb.Flamebearer.Levels = [][]int{{0, 0, 0, 0}}
})

It("returns an error", func() {
Expect(fb.Validate()).To(MatchError("a profile level should have a multiple of 7 values, but there's a level with 4 values"))
})
})
})

When("the name index is invalid", func() {
Context("and we validate a single profile", func() {
var fb FlamebearerProfile
BeforeEach(func() {
fb.Metadata.Format = "single"
fb.Flamebearer.Names = []string{"name"}
fb.Flamebearer.Levels = [][]int{{0, 0, 0, 1}}
})

It("returns an error", func() {
Expect(fb.Validate()).To(MatchError("invalid name index 1, it should be smaller than 1"))
})
})

Context("and we validate a double profile", func() {
var fb FlamebearerProfile
BeforeEach(func() {
fb.Metadata.Format = "double"
fb.Flamebearer.Names = []string{"name"}
fb.Flamebearer.Levels = [][]int{{0, 0, 0, 0, 0, 0, 1}}
})

It("returns an error", func() {
Expect(fb.Validate()).To(MatchError("invalid name index 1, it should be smaller than 1"))
})
})
})

When("everything is valid", func() {
Context("and we validate a single profile", func() {
var fb FlamebearerProfile
BeforeEach(func() {
fb.Metadata.Format = "single"
fb.Flamebearer.Names = []string{"name"}
fb.Flamebearer.Levels = [][]int{{0, 0, 0, 0}}
})

It("returns no error", func() {
Expect(fb.Validate()).To(BeNil())
})
})

Context("and we validate a double profile", func() {
var fb FlamebearerProfile
BeforeEach(func() {
fb.Metadata.Format = "double"
fb.Flamebearer.Names = []string{"name"}
fb.Flamebearer.Levels = [][]int{{0, 0, 0, 0, 0, 0, 0}}
})

It("returns an error", func() {
Expect(fb.Validate()).To(BeNil())
})
})
})
})

0 comments on commit bee6483

Please sign in to comment.