diff --git a/cmd/vroom/chunk.go b/cmd/vroom/chunk.go index 0cf9799a..3c719c2d 100644 --- a/cmd/vroom/chunk.go +++ b/cmd/vroom/chunk.go @@ -49,6 +49,8 @@ func (env *environment) postChunk(w http.ResponseWriter, r *http.Request) { return } + c.Normalize() + if hub != nil { hub.Scope().SetContext("Profile metadata", map[string]interface{}{ "chunk_id": c.ID, diff --git a/cmd/vroom/chunk_test.go b/cmd/vroom/chunk_test.go index 2850925d..9a6d8a84 100644 --- a/cmd/vroom/chunk_test.go +++ b/cmd/vroom/chunk_test.go @@ -12,6 +12,7 @@ import ( "github.com/getsentry/vroom/internal/chunk" "github.com/getsentry/vroom/internal/frame" + "github.com/getsentry/vroom/internal/platform" "github.com/getsentry/vroom/internal/storageutil" "github.com/getsentry/vroom/internal/testutil" "github.com/google/uuid" @@ -60,7 +61,11 @@ func TestPostAndReadChunk(t *testing.T) { ProjectID: 1, Profile: chunk.Data{ Frames: []frame.Frame{ - {Function: "test"}, + { + Function: "test", + InApp: &testutil.True, + Platform: platform.Python, + }, }, Stacks: [][]int{ {0}, @@ -141,6 +146,7 @@ type KafkaWriterMock struct{} func (k KafkaWriterMock) WriteMessages(_ context.Context, _ ...kafka.Message) error { return nil } + func (k KafkaWriterMock) Close() error { return nil } diff --git a/internal/chunk/chunk.go b/internal/chunk/chunk.go index c3d95a29..f243ece7 100644 --- a/internal/chunk/chunk.go +++ b/internal/chunk/chunk.go @@ -74,6 +74,14 @@ func (c *Chunk) StartEndTimestamps() (float64, float64) { return c.Profile.Samples[0].Timestamp, c.Profile.Samples[count-1].Timestamp } +func (c *Chunk) Normalize() { + for i := range c.Profile.Frames { + f := c.Profile.Frames[i] + f.Normalize(c.Platform) + c.Profile.Frames[i] = f + } +} + func StoragePath(OrganizationID uint64, ProjectID uint64, ProfilerID string, ID string) string { return fmt.Sprintf( "%d/%d/%s/%s", diff --git a/internal/frame/frame.go b/internal/frame/frame.go index 70d54ce5..7a889f45 100644 --- a/internal/frame/frame.go +++ b/internal/frame/frame.go @@ -201,7 +201,8 @@ func (f Frame) IsPythonApplicationFrame() bool { if strings.Contains(f.Path, "/site-packages/") || strings.Contains(f.Path, "/dist-packages/") || strings.Contains(f.Path, "\\site-packages\\") || - strings.Contains(f.Path, "\\dist-packages\\") { + strings.Contains(f.Path, "\\dist-packages\\") || + strings.HasPrefix(f.Path, "/usr/local/") { return false } @@ -278,3 +279,59 @@ func (f Frame) FullyQualifiedName(p platform.Platform) string { } return formatter(f) } + +func (f *Frame) SetInApp(p platform.Platform) { + // for react-native the in_app field seems to be messed up most of the times, + // with system libraries and other frames that are clearly system frames + // labelled as `in_app`. + // This is likely because RN uses static libraries which are bundled into the app binary. + // When symbolicated they are marked in_app. + // + // For this reason, for react-native app (p.Platform != f.Platform), we skip the f.InApp!=nil + // check as this field would be highly unreliable, and rely on our rules instead + if f.InApp != nil && (p == f.Platform) { + return + } + var isApplication bool + switch f.Platform { + case platform.Node: + isApplication = f.IsNodeApplicationFrame() + case platform.JavaScript: + isApplication = f.IsJavaScriptApplicationFrame() + case platform.Cocoa: + isApplication = f.IsCocoaApplicationFrame() + case platform.Rust: + isApplication = f.IsRustApplicationFrame() + case platform.Python: + isApplication = f.IsPythonApplicationFrame() + case platform.PHP: + isApplication = f.IsPHPApplicationFrame() + } + f.InApp = &isApplication +} + +func (f *Frame) IsInApp() bool { + if f.InApp == nil { + return false + } + return *f.InApp +} + +func (f *Frame) SetPlatform(p platform.Platform) { + if f.Platform == "" { + f.Platform = p + } +} + +func (f *Frame) SetStatus() { + if f.Data.SymbolicatorStatus != "" { + f.Status = f.Data.SymbolicatorStatus + } +} + +func (f *Frame) Normalize(p platform.Platform) { + // Call order is important since SetInApp uses Status and Platform + f.SetStatus() + f.SetPlatform(p) + f.SetInApp(p) +} diff --git a/internal/sample/sample.go b/internal/sample/sample.go index 2f225de0..e372278d 100644 --- a/internal/sample/sample.go +++ b/internal/sample/sample.go @@ -344,7 +344,7 @@ func (p *Profile) Speedscope() (speedscope.Output, error) { // it alone for now Image: fr.ModuleOrPackage(), Inline: fr.IsInline(), - IsApplication: p.IsApplicationFrame(fr), + IsApplication: fr.IsInApp(), Line: fr.Line, Name: symbolName, Path: fr.Path, @@ -402,35 +402,6 @@ func (p *Profile) Speedscope() (speedscope.Output, error) { }, nil } -func (p *Profile) IsApplicationFrame(f frame.Frame) bool { - // for react-native the in_app field seems to be messed up most of the times, - // with system libraries and other frames that are clearly system frames - // labelled as `in_app`. - // This is likely because RN uses static libraries which are bundled into the app binary. - // When symbolicated they are marked in_app. - // - // For this reason, for react-native app (p.Platform != f.Platform), we skip the f.InApp!=nil - // check as this field would be highly unreliable, and rely on our rules instead - if f.InApp != nil && (p.Platform == f.Platform) { - return *f.InApp - } - switch f.Platform { - case platform.Node: - return f.IsNodeApplicationFrame() - case platform.JavaScript: - return f.IsJavaScriptApplicationFrame() - case platform.Cocoa: - return f.IsCocoaApplicationFrame() - case platform.Rust: - return f.IsRustApplicationFrame() - case platform.Python: - return f.IsPythonApplicationFrame() - case platform.PHP: - return f.IsPHPApplicationFrame() - } - return true -} - func (p *Profile) Metadata() metadata.Metadata { return metadata.Metadata{ Architecture: p.Device.Architecture, @@ -452,7 +423,11 @@ func (p *Profile) Metadata() metadata.Metadata { } func (p *Profile) Normalize() { - p.normalizeFrames() + for i := range p.Trace.Frames { + f := p.Trace.Frames[i] + f.Normalize(p.Platform) + p.Trace.Frames[i] = f + } if p.Platform == platform.Cocoa { p.Trace.trimCocoaStacks() @@ -594,23 +569,6 @@ func (t Trace) CollectFrames(stackID int) []frame.Frame { return frames } -func (p *Profile) normalizeFrames() { - for i := range p.Trace.Frames { - f := p.Trace.Frames[i] - - // Set if frame is in application - inApp := p.IsApplicationFrame(f) - f.InApp = &inApp - - // Set Symbolicator status - if f.Status != "" { - f.Data.SymbolicatorStatus = f.Status - } - - p.Trace.Frames[i] = f - } -} - func (p *RawProfile) moveTransaction() { if len(p.Transactions) > 0 { p.Transaction = p.Transactions[0] diff --git a/internal/sample/sample_test.go b/internal/sample/sample_test.go index 8c53d0ce..e6e7e302 100644 --- a/internal/sample/sample_test.go +++ b/internal/sample/sample_test.go @@ -585,23 +585,27 @@ func TestTrimCocoaStacks(t *testing.T) { Function: "function1", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, { Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "function2", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, { Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "main", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, { Data: frame.Data{SymbolicatorStatus: "missing"}, InApp: &testutil.False, Platform: "cocoa", + Status: "missing", }, }, Stacks: []Stack{ @@ -663,29 +667,34 @@ func TestTrimCocoaStacks(t *testing.T) { Function: "function1", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, { Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "function2", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, { Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "main", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, { Data: frame.Data{SymbolicatorStatus: "missing"}, InApp: &testutil.False, Platform: "cocoa", + Status: "missing", }, { Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "start_sim", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, }, Stacks: []Stack{ @@ -747,29 +756,34 @@ func TestTrimCocoaStacks(t *testing.T) { Function: "function1", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, { Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "function2", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, { Data: frame.Data{SymbolicatorStatus: "missing"}, Function: "unsymbolicated_main", InApp: &testutil.True, Platform: "cocoa", + Status: "missing", }, { Data: frame.Data{SymbolicatorStatus: "missing"}, InApp: &testutil.False, Platform: "cocoa", + Status: "missing", }, { Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "start_sim", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, }, Stacks: []Stack{ @@ -835,29 +849,34 @@ func TestTrimCocoaStacks(t *testing.T) { Function: "function1", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, { Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "function2", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, { Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "main", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, { Data: frame.Data{SymbolicatorStatus: "missing"}, InApp: &testutil.False, Platform: "cocoa", + Status: "missing", }, { Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "start_sim", InApp: &testutil.True, Platform: "cocoa", + Status: "symbolicated", }, }, Stacks: []Stack{ @@ -917,7 +936,9 @@ func TestNormalizeFramesPerPlatform(t *testing.T) { Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "main", Package: "/private/var/containers/foo", - InApp: &testutil.True, + Platform: platform.Cocoa, + InApp: &testutil.False, + Status: "symbolicated", }, }, Stacks: []Stack{ @@ -955,7 +976,9 @@ func TestNormalizeFramesPerPlatform(t *testing.T) { Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "main", Package: "/usr/local/foo", + Platform: platform.Rust, InApp: &testutil.True, + Status: "symbolicated", }, }, Stacks: []Stack{ @@ -1039,6 +1062,7 @@ func TestNormalizeFramesPerPlatform(t *testing.T) { Package: "/private/var/containers/Bundle/Application/0DA082D7-05F5-413F-892B-642FD331230C/BIGW.app/Frameworks/hermes.framework/hermes", InApp: &testutil.False, Platform: "cocoa", + Status: "symbolicated", }, }, Stacks: []Stack{ @@ -1080,6 +1104,7 @@ func TestNormalizeFramesPerPlatform(t *testing.T) { Package: "/usr/lib/swift/libswiftCore.dylib", InApp: &testutil.False, Platform: "cocoa", + Status: "symbolicated", }, }, Stacks: []Stack{ @@ -1144,7 +1169,7 @@ func TestCallTreesFingerprintPerPlatform(t *testing.T) { DurationNS: 10, EndNS: 10, Fingerprint: 1628006971372193492, - IsApplication: true, + IsApplication: false, Name: "main", Package: "foo", SampleCount: 1, @@ -1152,8 +1177,10 @@ func TestCallTreesFingerprintPerPlatform(t *testing.T) { Frame: frame.Frame{ Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "main", + InApp: &testutil.False, Package: "/private/var/containers/foo", - InApp: &testutil.True, + Platform: platform.Cocoa, + Status: "symbolicated", }, }, }, @@ -1205,6 +1232,8 @@ func TestCallTreesFingerprintPerPlatform(t *testing.T) { Data: frame.Data{SymbolicatorStatus: "symbolicated"}, Function: "main", Package: "/usr/local/foo", + Status: "symbolicated", + Platform: platform.Rust, InApp: &testutil.True, }, },