native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #211 from onevcat/feature/sentry-hang-filter

Filter system-induced App Hangs and raise threshold to 3s

authored by

Wei Wang and committed by
GitHub
a2947c0b 6cc3cb54

+108
+2
supacode/App/supacodeApp.swift
··· 129 129 if let releaseName { options.releaseName = releaseName } 130 130 options.tracesSampleRate = 0.05 131 131 options.enableAppHangTracking = true 132 + options.appHangTimeoutInterval = 3 133 + options.beforeSend = SentryEventFilter.filterSystemHang 132 134 } 133 135 } 134 136 if initialSettings.analyticsEnabled,
+40
supacode/Support/SentryEventFilter.swift
··· 1 + import Foundation 2 + import Sentry 3 + 4 + /// Client-side filter for Sentry events. 5 + /// 6 + /// Wired into `SentrySDK.start { options.beforeSend = SentryEventFilter.filterSystemHang }`. 7 + enum SentryEventFilter { 8 + /// Known stack frame function-name fragments that indicate a system-induced 9 + /// App Hang (wake-from-sleep, active space change, external display connect, 10 + /// menu bar redraw, etc.). These hangs are observable but have no app-level 11 + /// remedy — filter them out to avoid drowning real hangs in noise. 12 + static let systemHangSignatures = [ 13 + "_NSMenuBarDisplayManagerActiveSpaceChanged", 14 + "NSMenuBarLocalDisplayWindow", 15 + "NSMenuBarPresentationInstance", 16 + "NSMenuBarReplicantWindow", 17 + ] 18 + 19 + /// Drop App Hang events whose stack contains zero in-app frames AND matches 20 + /// at least one known system signature. Conservative by design: if the hang 21 + /// involves any app code, keep it; if the stack is all-system but matches no 22 + /// known pattern, keep it too (so we still see novel system-level issues). 23 + static func filterSystemHang(_ event: Event) -> Event? { 24 + guard let exception = event.exceptions?.first, 25 + exception.mechanism?.type == "AppHang" 26 + else { 27 + return event 28 + } 29 + let frames = exception.stacktrace?.frames ?? [] 30 + let hasAppFrame = frames.contains { $0.inApp?.boolValue == true } 31 + let hasSystemSignature = frames.contains { frame in 32 + guard let function = frame.function else { return false } 33 + return systemHangSignatures.contains { function.contains($0) } 34 + } 35 + if !hasAppFrame && hasSystemSignature { 36 + return nil 37 + } 38 + return event 39 + } 40 + }
+66
supacodeTests/SentryEventFilterTests.swift
··· 1 + import Foundation 2 + import Sentry 3 + import Testing 4 + 5 + @testable import supacode 6 + 7 + struct SentryEventFilterTests { 8 + @Test func nonHangEventPassesThrough() { 9 + let event = makeEvent(mechanismType: "nsexception", frames: []) 10 + #expect(SentryEventFilter.filterSystemHang(event) === event) 11 + } 12 + 13 + @Test func appHangWithSystemSignatureAndNoAppFrameIsDropped() { 14 + let event = makeEvent( 15 + mechanismType: "AppHang", 16 + frames: [ 17 + makeFrame(function: "mach_msg2_trap", inApp: false), 18 + makeFrame(function: "_NSMenuBarDisplayManagerActiveSpaceChanged", inApp: false), 19 + ] 20 + ) 21 + #expect(SentryEventFilter.filterSystemHang(event) == nil) 22 + } 23 + 24 + @Test func appHangWithAnyAppFrameIsKept() { 25 + let event = makeEvent( 26 + mechanismType: "AppHang", 27 + frames: [ 28 + makeFrame(function: "_NSMenuBarDisplayManagerActiveSpaceChanged", inApp: false), 29 + makeFrame(function: "WorktreeTerminalManager.didReceiveNotification", inApp: true), 30 + ] 31 + ) 32 + #expect(SentryEventFilter.filterSystemHang(event) === event) 33 + } 34 + 35 + @Test func appHangWithNoKnownSystemSignatureIsKept() { 36 + let event = makeEvent( 37 + mechanismType: "AppHang", 38 + frames: [ 39 + makeFrame(function: "mach_msg2_trap", inApp: false), 40 + makeFrame(function: "__CFRunLoopRun", inApp: false), 41 + ] 42 + ) 43 + #expect(SentryEventFilter.filterSystemHang(event) === event) 44 + } 45 + 46 + @Test func eventWithoutExceptionsPassesThrough() { 47 + let event = Event() 48 + #expect(SentryEventFilter.filterSystemHang(event) === event) 49 + } 50 + 51 + private func makeEvent(mechanismType: String, frames: [Frame]) -> Event { 52 + let event = Event() 53 + let exception = Exception(value: "test", type: "test") 54 + exception.mechanism = Mechanism(type: mechanismType) 55 + exception.stacktrace = SentryStacktrace(frames: frames, registers: [:]) 56 + event.exceptions = [exception] 57 + return event 58 + } 59 + 60 + private func makeFrame(function: String, inApp: Bool) -> Frame { 61 + let frame = Frame() 62 + frame.function = function 63 + frame.inApp = NSNumber(value: inApp) 64 + return frame 65 + } 66 + }