Skip to content

Commit d3a65fb

Browse files
authored
impr(profiling): increase buffer length from 10s to 60s (#4826)
1 parent 7b032f3 commit d3a65fb

File tree

8 files changed

+153
-73
lines changed

8 files changed

+153
-73
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- Add `sample_rand` to baggage (#4751)
1616
- Add timeIntervalSince1970 to log messages (#4781)
1717
- Add `waitForFullDisplay` to `sentryTrace` view modifier (#4797)
18+
- Increase continuous profiling buffer size to 60 seconds (#4826)
1819

1920
### Fixes
2021

+67-23
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,96 @@
11
import Foundation
2-
import Sentry
2+
@testable import Sentry
33

44
// We must not subclass NSTimer, see https://developer.apple.com/documentation/foundation/nstimer#1770465.
55
// Therefore we return a NSTimer instance here with TimeInterval.infinity.
66
public class TestSentryNSTimerFactory: SentryNSTimerFactory {
77
public struct Overrides {
8-
private var _timer: Timer?
9-
10-
public var timer: Timer {
11-
get {
12-
_timer ?? Timer()
13-
}
14-
set(newValue) {
15-
_timer = newValue
16-
}
17-
}
18-
8+
private var _interval: TimeInterval?
9+
10+
public var timer: Timer
1911
var block: ((Timer) -> Void)?
20-
12+
13+
var interval: TimeInterval {
14+
get {
15+
_interval ?? TimeInterval.infinity
16+
}
17+
set {
18+
_interval = newValue
19+
}
20+
}
21+
22+
var lastFireDate: Date
23+
2124
struct InvocationInfo {
2225
var target: NSObject
2326
var selector: Selector
2427
}
2528
var invocationInfo: InvocationInfo?
29+
30+
init(timer: Timer, interval: TimeInterval? = nil, block: ((Timer) -> Void)? = nil, lastFireDate: Date, invocationInfo: InvocationInfo? = nil) {
31+
self.timer = timer
32+
self._interval = interval
33+
self.block = block
34+
self.lastFireDate = lastFireDate
35+
self.invocationInfo = invocationInfo
36+
}
2637
}
38+
39+
public var overrides: Overrides?
40+
41+
private var currentDateProvider: SentryCurrentDateProvider
42+
43+
public init(currentDateProvider: SentryCurrentDateProvider) {
44+
self.currentDateProvider = currentDateProvider
45+
super.init()
46+
}
47+
}
2748

28-
public var overrides = Overrides()
29-
30-
public override func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer {
49+
// MARK: Superclass overrides
50+
public extension TestSentryNSTimerFactory {
51+
override func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer {
3152
let timer = Timer.scheduledTimer(withTimeInterval: TimeInterval.infinity, repeats: repeats, block: block)
32-
overrides.timer = timer
33-
overrides.block = block
53+
overrides = Overrides(timer: timer, interval: interval, block: block, lastFireDate: currentDateProvider.date())
3454
return timer
3555
}
36-
37-
public override func scheduledTimer(withTimeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats yesOrNo: Bool) -> Timer {
56+
57+
override func scheduledTimer(withTimeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats yesOrNo: Bool) -> Timer {
3858
let timer = Timer.scheduledTimer(timeInterval: ti, target: aTarget, selector: aSelector, userInfo: userInfo, repeats: yesOrNo)
3959
//swiftlint:disable force_cast
40-
overrides.invocationInfo = Overrides.InvocationInfo(target: aTarget as! NSObject, selector: aSelector)
60+
let invocationInfo = Overrides.InvocationInfo(target: aTarget as! NSObject, selector: aSelector)
4161
//swiftlint:enable force_cast
62+
overrides = Overrides(timer: timer, interval: ti, lastFireDate: currentDateProvider.date(), invocationInfo: invocationInfo)
4263
return timer
4364
}
65+
}
66+
67+
// MARK: Extensions
68+
public extension TestSentryNSTimerFactory {
69+
enum TestTimerError: Error {
70+
case timerNotInitialized
71+
}
72+
73+
// check the current time against the last fire time and interval, and if enough time has elapsed, execute any block/invocation registered with the timer
74+
func check() throws {
75+
guard let overrides = overrides else { throw TestTimerError.timerNotInitialized }
76+
let currentDate = currentDateProvider.date()
77+
if currentDate.timeIntervalSince(overrides.lastFireDate) >= overrides.interval {
78+
try fire()
79+
}
80+
}
4481

45-
public func fire() {
82+
// immediately execute any block/invocation registered with the timer
83+
func fire() throws {
84+
guard var overrides = overrides else { throw TestTimerError.timerNotInitialized }
85+
overrides.lastFireDate = currentDateProvider.date()
4686
if let block = overrides.block {
4787
block(overrides.timer)
4888
} else if let invocationInfo = overrides.invocationInfo {
49-
try! Invocation(target: invocationInfo.target, selector: invocationInfo.selector).invoke()
89+
try Invocation(target: invocationInfo.target, selector: invocationInfo.selector).invoke()
5090
}
5191
}
92+
93+
func isTimerInitialized() -> Bool {
94+
return overrides != nil
95+
}
5296
}

Sources/Sentry/Profiling/SentryProfilerDefines.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ typedef NS_ENUM(NSUInteger, SentryProfilerTruncationReason) {
1515
SentryProfilerTruncationReasonAppMovedToBackground,
1616
};
1717

18-
static NSTimeInterval kSentryProfilerChunkExpirationInterval = 10;
18+
static NSTimeInterval kSentryProfilerChunkExpirationInterval = 60;
1919
static NSTimeInterval kSentryProfilerTimeoutInterval = 30;
2020

2121
NS_ASSUME_NONNULL_BEGIN

Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift

+37-12
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ final class SentryContinuousProfilerTests: XCTestCase {
2323
super.tearDown()
2424
clearTestState()
2525
}
26-
26+
27+
func testSentryProfilerChunkExpirationInterval() {
28+
XCTAssertEqual(60, kSentryProfilerChunkExpirationInterval)
29+
}
30+
2731
func testStartingAndStoppingContinuousProfiler() throws {
2832
try performContinuousProfilingTest()
2933
}
@@ -94,12 +98,12 @@ final class SentryContinuousProfilerTests: XCTestCase {
9498
XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling())
9599
}
96100

97-
func testClosingSDKStopsProfile() {
101+
func testClosingSDKStopsProfile() throws {
98102
XCTAssertFalse(SentryContinuousProfiler.isCurrentlyProfiling())
99103
SentryContinuousProfiler.start()
100104
XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling())
101105
SentrySDK.close()
102-
assertContinuousProfileStoppage()
106+
try assertContinuousProfileStoppage()
103107
}
104108

105109
func testStartingAPerformanceTransactionDoesNotStartProfiler() throws {
@@ -118,15 +122,15 @@ final class SentryContinuousProfilerTests: XCTestCase {
118122
XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling())
119123

120124
// assert that the first chunk was sent
121-
fixture.currentDateProvider.advanceBy(interval: kSentryProfilerChunkExpirationInterval)
122-
fixture.timeoutTimerFactory.fire()
125+
fixture.currentDateProvider.advanceBy(interval: 60)
126+
try fixture.timeoutTimerFactory.check()
123127
let envelope = try XCTUnwrap(self.fixture.client?.captureEnvelopeInvocations.last)
124128
let profileItem = try XCTUnwrap(envelope.items.first)
125129
XCTAssertEqual("profile_chunk", profileItem.header.type)
126130

127131
// assert that the profiler doesn't stop until after the next timer period elapses
128132
SentryContinuousProfiler.stop()
129-
assertContinuousProfileStoppage()
133+
try assertContinuousProfileStoppage()
130134

131135
// check that the last full chunk was sent
132136
let lastEnvelope = try XCTUnwrap(self.fixture.client?.captureEnvelopeInvocations.last)
@@ -136,13 +140,34 @@ final class SentryContinuousProfilerTests: XCTestCase {
136140
// check that two chunks were sent in total
137141
XCTAssertEqual(2, self.fixture.client?.captureEnvelopeInvocations.count)
138142
}
143+
144+
func testChunkSerializationAfterBufferInterval() throws {
145+
SentryContinuousProfiler.start()
146+
XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling())
147+
148+
// Advance time by the buffer interval to trigger chunk serialization
149+
fixture.currentDateProvider.advanceBy(interval: 60)
150+
try fixture.timeoutTimerFactory.check()
151+
152+
// Check that a chunk was serialized and sent
153+
let envelope = try XCTUnwrap(self.fixture.client?.captureEnvelopeInvocations.last)
154+
let profileItem = try XCTUnwrap(envelope.items.first)
155+
XCTAssertEqual("profile_chunk", profileItem.header.type)
156+
157+
// Ensure the profiler is still running
158+
XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling())
159+
160+
// Stop the profiler
161+
SentryContinuousProfiler.stop()
162+
try assertContinuousProfileStoppage()
163+
}
139164
}
140165

141166
private extension SentryContinuousProfilerTests {
142167
func addMockSamples(mockAddresses: [NSNumber]) throws {
143168
let mockThreadMetadata = SentryProfileTestFixture.ThreadMetadata(id: 1, priority: 2, name: "main")
144169
let state = try XCTUnwrap(SentryContinuousProfiler.profiler()?.state)
145-
for _ in 0..<Int(kSentryProfilerChunkExpirationInterval) {
170+
for _ in 0..<Int(60) {
146171
fixture.currentDateProvider.advanceBy(interval: 1)
147172
SentryProfilerMocksSwiftCompatible.appendMockBacktrace(to: state, threadID: mockThreadMetadata.id, threadPriority: mockThreadMetadata.priority, threadName: mockThreadMetadata.name, addresses: mockAddresses)
148173
}
@@ -159,7 +184,7 @@ private extension SentryContinuousProfilerTests {
159184
try fixture.gatherMockedContinuousProfileMetrics()
160185
try addMockSamples(mockAddresses: expectedAddresses)
161186
fixture.currentDateProvider.advanceBy(interval: 1)
162-
fixture.timeoutTimerFactory.fire()
187+
try fixture.timeoutTimerFactory.check()
163188
XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling())
164189
try assertValidData(expectedEnvironment: expectedEnvironment, expectedAddresses: expectedAddresses, countMetricsReadingAtProfileStart: countMetricsReadingAtProfileStart)
165190
#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
@@ -174,13 +199,13 @@ private extension SentryContinuousProfilerTests {
174199

175200
XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling())
176201
SentryContinuousProfiler.stop()
177-
assertContinuousProfileStoppage()
202+
try assertContinuousProfileStoppage()
178203
}
179204

180-
func assertContinuousProfileStoppage() {
205+
func assertContinuousProfileStoppage() throws {
181206
XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling())
182-
fixture.currentDateProvider.advance(by: kSentryProfilerTimeoutInterval)
183-
fixture.timeoutTimerFactory.fire()
207+
fixture.currentDateProvider.advance(by: 60)
208+
try fixture.timeoutTimerFactory.check()
184209
XCTAssertFalse(SentryContinuousProfiler.isCurrentlyProfiling())
185210
}
186211

Tests/SentryProfilerTests/SentryProfileTestFixture.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class SentryProfileTestFixture {
2828
let processInfoWrapper = TestSentryNSProcessInfoWrapper()
2929
let dispatchFactory = TestDispatchFactory()
3030
var metricTimerFactory: TestDispatchSourceWrapper?
31-
let timeoutTimerFactory = TestSentryNSTimerFactory()
31+
var timeoutTimerFactory: TestSentryNSTimerFactory
3232
let dispatchQueueWrapper = TestSentryDispatchQueueWrapper()
3333
let notificationCenter = TestNSNotificationCenterWrapper()
3434

@@ -46,8 +46,10 @@ class SentryProfileTestFixture {
4646
SentryDependencyContainer.sharedInstance().systemWrapper = systemWrapper
4747
SentryDependencyContainer.sharedInstance().processInfoWrapper = processInfoWrapper
4848
SentryDependencyContainer.sharedInstance().dispatchFactory = dispatchFactory
49-
SentryDependencyContainer.sharedInstance().timerFactory = timeoutTimerFactory
5049
SentryDependencyContainer.sharedInstance().notificationCenterWrapper = notificationCenter
50+
51+
timeoutTimerFactory = TestSentryNSTimerFactory(currentDateProvider: self.currentDateProvider)
52+
SentryDependencyContainer.sharedInstance().timerFactory = timeoutTimerFactory
5153

5254
let image = DebugMeta()
5355
image.name = "sentrytest"

Tests/SentryProfilerTests/SentryTraceProfilerTests.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ class SentryTraceProfilerTests: XCTestCase {
2323
clearTestState()
2424
}
2525

26+
func testSentryProfilerTimoutInterval() {
27+
XCTAssertEqual(30, kSentryProfilerTimeoutInterval)
28+
}
29+
2630
func testMetricProfiler() throws {
2731
let span = try fixture.newTransaction()
2832
try addMockSamples()
@@ -138,7 +142,7 @@ class SentryTraceProfilerTests: XCTestCase {
138142

139143
// time out profiler for span A
140144
fixture.currentDateProvider.advanceBy(nanoseconds: 30.toNanoSeconds())
141-
fixture.timeoutTimerFactory.fire()
145+
try fixture.timeoutTimerFactory.fire()
142146

143147
fixture.currentDateProvider.advanceBy(nanoseconds: 0.5.toNanoSeconds())
144148

@@ -405,7 +409,7 @@ private extension SentryTraceProfilerTests {
405409
try addMockSamples()
406410
fixture.currentDateProvider.advance(by: 31)
407411
if shouldTimeOut {
408-
self.fixture.timeoutTimerFactory.fire()
412+
try self.fixture.timeoutTimerFactory.fire()
409413
}
410414

411415
let exp = expectation(description: "finished span")

Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase {
99

1010
private class Fixture {
1111
let dateProvider: TestCurrentDateProvider = TestCurrentDateProvider()
12-
let timerFactory = TestSentryNSTimerFactory()
12+
lazy var timerFactory = TestSentryNSTimerFactory(currentDateProvider: dateProvider)
1313

1414
var displayLinkWrapper = TestDisplayLinkWrapper()
1515
var framesTracker: SentryFramesTracker
@@ -280,7 +280,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase {
280280
fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 11))
281281

282282
// Timeout for tracer times out
283-
fixture.timerFactory.fire()
283+
try fixture.timerFactory.fire()
284284

285285
fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 12))
286286
sut.reportFullyDisplayed()
@@ -330,7 +330,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase {
330330
fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 11))
331331

332332
// Timeout for tracer times out
333-
fixture.timerFactory.fire()
333+
try fixture.timerFactory.fire()
334334

335335
fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 12))
336336
sut.reportFullyDisplayed()

0 commit comments

Comments
 (0)