Skip to content

Commit

Permalink
add test case for concurrent profiled transactions; fix Invocations c…
Browse files Browse the repository at this point in the history
…lass using NSMutableArray to prevent nils creeping in
  • Loading branch information
armcknight committed Sep 7, 2022
1 parent 696bd23 commit 693bd04
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 47 deletions.
11 changes: 5 additions & 6 deletions Sources/Sentry/SentryTracer.m
Original file line number Diff line number Diff line change
Expand Up @@ -493,18 +493,16 @@ - (void)maybeStopProfilerWithReason:(SentryProfilerStopReason)reason {
*/
- (void)captureProfilingEnvelopeIfFinished {
#if SENTRY_TARGET_PROFILING_SUPPORTED
SentryEnvelope *profileEnvelope;
if (_profilesSamplerDecision.decision == kSentrySampleDecisionYes) {
[profilerLock lock];
if (profiler != nil && !profiler.isRunning) {
profileEnvelope = [profiler buildEnvelopeItemForTransactions:_gProfiledTransactions
hub:_hub
frameInfo:_gProfilerFrameInfo
stopReason:_gProfilerStopReason];
[_hub.client captureEnvelope:[profiler buildEnvelopeItemForTransactions:_gProfiledTransactions
hub:_hub
frameInfo:_gProfilerFrameInfo
stopReason:_gProfilerStopReason]];
profiler = nil;
}
[profilerLock unlock];
[_hub.client captureEnvelope:profileEnvelope];
}
#endif
}
Expand Down Expand Up @@ -559,6 +557,7 @@ - (void)finishInternal
if (_gProfiledTransactions == nil) {
_gProfiledTransactions = [NSMutableArray<SentryTransaction *> array];
}
[SentryLog logWithMessage:[NSString stringWithFormat:@"Adding transaction %@ to list of profiled transactions.", transaction] andLevel:kSentryLevelDebug];
[_gProfiledTransactions addObject:transaction];

[self captureProfilingEnvelopeIfFinished];
Expand Down
48 changes: 47 additions & 1 deletion Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,53 @@ class SentryProfilerSwiftTests: XCTestCase {
#endif
}

// This test is only available on newer APIs because of the availability of `DispatchQueue.activate`
@available(tvOS 10.0, *)
@available(OSX 10.12, *)
@available(iOS 10.0, *)
func testConcurrentProfilingTransactions() {
let options = fixture.options
options.profilesSampleRate = 1.0
options.tracesSampler = {(_: SamplingContext) -> NSNumber in
return 1
}
let sut = fixture.getSut(options)

let queue = DispatchQueue(label: "SentryProfilerSwiftTests", attributes: [.concurrent, .initiallyInactive])
let group = DispatchGroup()

let numberOfTransactions = 10
for _ in 0 ..< numberOfTransactions {
group.enter()
let span = sut.startTransaction(name: fixture.transactionName, operation: fixture.transactionOperation)

// Some busy work to try and get it to show up in the profile.
let str = "a"
var concatStr = ""
for _ in 0..<100_000 {
concatStr = concatStr.appending(str)
}

queue.asyncAfter(deadline: .now() + 2) {
span.finish()
group.leave()
}
}

queue.activate()
group.wait()

guard let envelope = self.fixture.client.captureEnvelopeInvocations.first else {
XCTFail("Expected to capture at least 1 event")
return
}
XCTAssertEqual(1, envelope.items.count)
guard let profileItem = envelope.items.first else {
XCTFail("Expected at least 1 additional envelope item")
return
}
XCTAssertEqual("profile", profileItem.header.type)
self.assertValidProfileData(data: profileItem.data, numberOfTransactions: numberOfTransactions)
}

func testStartTransaction_ProfilingDataIsValid() {
Expand Down Expand Up @@ -140,12 +185,13 @@ class SentryProfilerSwiftTests: XCTestCase {
}

private extension SentryProfilerSwiftTests {
func assertValidProfileData(data: Data, transactionEnvironment: String) {
func assertValidProfileData(data: Data, transactionEnvironment: String = kSentryDefaultEnvironment, numberOfTransactions: Int = 1) {
let profile = try! JSONSerialization.jsonObject(with: data) as! [String: Any]
XCTAssertEqual("Apple", profile["device_manufacturer"] as! String)
XCTAssertEqual("cocoa", profile["platform"] as! String)
XCTAssertNotNil(profile["transactions"])
if let transactions = profile["transactions"] as? [[String: String]] {
XCTAssertEqual(transactions.count, numberOfTransactions)
for transaction in transactions {
XCTAssertEqual(fixture.transactionName, transaction["name"])
XCTAssertNotNil(transaction["id"])
Expand Down
80 changes: 40 additions & 40 deletions Tests/SentryTests/TestUtils/Invocations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,44 @@ import Foundation
* For recording invocations of methods in a list in a thread safe manner.
*/
class Invocations<T> {
private let queue = DispatchQueue(label: "Invocations", attributes: .concurrent)
private var _invocations: [T] = []
var invocations: [T] {
return queue.sync {
return self._invocations
}
}
var count: Int {
return queue.sync {
return self._invocations.count
}
}
var first: T? {
return queue.sync {
return self._invocations.first
}
}
var last: T? {
return queue.sync {
return self._invocations.last
}
}
var isEmpty: Bool {
return queue.sync {
return self._invocations.isEmpty
}
}
func record(_ invocation: T) {
queue.async(flags: .barrier) {
self._invocations.append(invocation)
}
}

private let queue = DispatchQueue(label: "Invocations", attributes: .concurrent)

private var _invocations = NSMutableArray()

var invocations: [T] {
return queue.sync {
return self._invocations as! [T]
}
}

var count: Int {
return queue.sync {
return self._invocations.count
}
}

var first: T? {
return queue.sync {
return self._invocations.firstObject as? T
}
}

var last: T? {
return queue.sync {
return self._invocations.lastObject as? T
}
}

var isEmpty: Bool {
return queue.sync {
return self._invocations.count == 0
}
}

func record(_ invocation: T) {
queue.async(flags: .barrier) {
self._invocations.add(invocation)
}
}
}

0 comments on commit 693bd04

Please sign in to comment.