Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: monitor for new metric values via sampling and notifications #2493

Merged
merged 75 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
bd8f158
feat: monitor for new metric values via sampling and notifications
armcknight Dec 6, 2022
1337fd2
changelog
armcknight Dec 6, 2022
8a87e63
fix changelog
armcknight Dec 6, 2022
da9e287
register for low power mode notifications; improve notification wrapp…
armcknight Dec 6, 2022
5f7412b
extract syscalls and NSProcess work to wrappers with test mocks; lots…
armcknight Dec 8, 2022
288e631
start writing the test; only add metrics to payload if any were gathered
armcknight Dec 8, 2022
bb77d06
get test passing with various fixes
armcknight Dec 9, 2022
92f1c09
typedef system type for better understandability
armcknight Dec 12, 2022
7d0666c
Merge branch '8.0.0' into armcknight/feat/profiling-metrics
armcknight Dec 12, 2022
5c94ce7
add and use a wrapper method around NSProcessInfo to get core count
armcknight Dec 12, 2022
de82f6a
fix merge issue
armcknight Dec 12, 2022
01ca6f5
dont need lock around cpu usage gethering since its always on the sam…
armcknight Dec 12, 2022
ea2fde9
add NSTimer wrapper
armcknight Dec 13, 2022
6e1aed9
rename paramater target->observer
armcknight Dec 13, 2022
762cd42
add info on where to find doc info
armcknight Dec 13, 2022
246d01b
fix timer wrapper issue and assert number of times it "fired"
armcknight Dec 13, 2022
aa47004
fix ci build error
armcknight Dec 13, 2022
a032fb0
again
armcknight Dec 13, 2022
e323e77
fix macos build
armcknight Dec 13, 2022
5acf5de
fix tvos build
armcknight Dec 13, 2022
9bad80a
fix linker errors
armcknight Dec 13, 2022
039e934
again
armcknight Dec 13, 2022
3c2653e
try to fix possible timing issue in CI
armcknight Dec 13, 2022
58bf05f
add debug log
armcknight Dec 14, 2022
3c0da5e
more logging
armcknight Dec 14, 2022
2492abe
format log
armcknight Dec 14, 2022
c11991a
only print metrics
armcknight Dec 14, 2022
4475048
only add slow frame renders if info exists
armcknight Dec 14, 2022
7b6ccf3
logging
armcknight Dec 14, 2022
e3f4b10
fixup! logging
armcknight Dec 14, 2022
621d340
logging
armcknight Dec 14, 2022
34a4b94
more logging
armcknight Dec 14, 2022
6cf38cc
override processor count
armcknight Dec 14, 2022
cf54864
remove some verbose debug logs for testing
armcknight Dec 14, 2022
8c1a6fc
fix build
armcknight Dec 14, 2022
3f5ec52
Merge remote-tracking branch 'origin/8.0.0' into armcknight/feat/prof…
armcknight Dec 15, 2022
65044c1
add dispatch source wrapper to mock memory pressure notifications
armcknight Dec 16, 2022
b40d64a
extract method to start metric profiler; init missing timer wrapper
armcknight Dec 16, 2022
feb2055
store string values and put measurements under profile
armcknight Dec 23, 2022
8e58476
rename invalidate->cancel
armcknight Dec 28, 2022
6212b2a
add comment for metric sampling rate
armcknight Dec 28, 2022
b9990af
fixup! rename invalidate->cancel
armcknight Dec 28, 2022
ca88f04
remove unused ivar
armcknight Dec 28, 2022
fe63d02
move test header extensions to test target
armcknight Dec 28, 2022
e3ccdb1
set values back to doubles instead of strings; fix test after moving …
armcknight Dec 28, 2022
13134a9
synchronize reads and writes to metric profiler data structures
armcknight Dec 28, 2022
d71398a
convert kabob case to snake case
armcknight Jan 4, 2023
bd6165c
pass low power mode enabled as float value instead of bool
armcknight Jan 4, 2023
827dd5a
make measurements sibling to profile
armcknight Jan 4, 2023
acc122d
split slow/frozen frames; use singular unit names
armcknight Jan 4, 2023
2f6c51d
fixup! make measurements sibling to profile
armcknight Jan 4, 2023
66b4bf0
Merge remote-tracking branch 'origin/8.0.0' into armcknight/feat/prof…
armcknight Jan 9, 2023
075c08f
dont declare abstract fire method
armcknight Jan 9, 2023
071e57d
fix macos build
armcknight Jan 9, 2023
80e5a39
wrap bare nanosecond conversions in function
armcknight Jan 9, 2023
74e2375
use the correct routine to process frame rates, which is different fr…
armcknight Jan 10, 2023
360b2a9
remove all metrics but cpu usage and memory footprint
armcknight Jan 10, 2023
2b727d3
fix test by removing async dispatch
armcknight Jan 10, 2023
6b690bc
add test case for new error function; reenable another test case for …
armcknight Jan 12, 2023
16b2ba3
test errors gathering cpu and memory usages
armcknight Jan 12, 2023
94e8c3b
add missing asserts for memory footprint; generalize logic to perform…
armcknight Jan 12, 2023
c940b1a
add slow/frozen frames to test case
armcknight Jan 12, 2023
1cfed1c
fix typo; simplify lazy property init
armcknight Jan 12, 2023
5443b84
fix older swift builds
armcknight Jan 12, 2023
acb3cfd
test amount of GPU frame render timestamps recorded for profiling met…
armcknight Jan 13, 2023
6b4a169
fix macos build
armcknight Jan 13, 2023
52a1b38
fixup! fix macos build
armcknight Jan 13, 2023
48f2b5a
add system and process info wrapper tests
armcknight Jan 13, 2023
08c0979
add nstimer wrapper tests
armcknight Jan 13, 2023
cf11d7f
avoid possible negative interval between transaction end and profile …
armcknight Jan 13, 2023
9c51294
Merge remote-tracking branch 'origin/8.0.0' into armcknight/feat/prof…
armcknight Jan 13, 2023
483bbe6
Merge remote-tracking branch 'origin/main' into armcknight/feat/profi…
armcknight Jan 18, 2023
eea6153
fix changelog
armcknight Jan 18, 2023
adef44c
fixup! dont declare abstract fire method
armcknight Jan 18, 2023
bccfd3c
fix broken nstimer test
armcknight Jan 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions Sentry.xcodeproj/xcshareddata/xcschemes/Sentry.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,6 @@
<Test
Identifier = "SentryFileIOTrackingIntegrationTests/test_DataConsistency_readUrl_disabled()">
</Test>
<Test
Identifier = "SentryNSErrorTests/testSerializeWithUnderlyingNSException_disabled()">
</Test>
<Test
Identifier = "SentryNetworkTrackerIntegrationTests/testGetRequest_SpanCreatedAndBaggageHeaderAdded_disabled()">
</Test>
Expand Down
63 changes: 47 additions & 16 deletions Sources/Sentry/SentryProfiler.mm
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
const int kSentryProfilerFrequencyHz = 101;
NSString *const kTestStringConst = @"test";

NSString *const kSentryProfilerSerializationKeySlowFrameRenders = @"slow_frame_renders";
NSString *const kSentryProfilerSerializationKeyFrozenFrameRenders = @"frozen_frame_renders";
NSString *const kSentryProfilerSerializationKeyFrameRates = @"screen_frame_rates";

using namespace sentry::profiling;

NSString *
Expand Down Expand Up @@ -154,6 +158,9 @@
SentryNSProcessInfoWrapper *_gCurrentProcessInfoWrapper;
SentrySystemWrapper *_gCurrentSystemWrapper;
SentryNSTimerWrapper *_gCurrentTimerWrapper;
# if SENTRY_HAS_UIKIT
SentryFramesTracker *_gCurrentFramesTracker;
# endif // SENTRY_HAS_UIKIT

NSString *
profilerTruncationReasonName(SentryProfilerTruncationReason reason)
Expand All @@ -170,28 +177,42 @@

# if SENTRY_HAS_UIKIT
NSArray *
processFrameRenders(SentryFrameInfoTimeSeries *frameInfo, uint64_t start, uint64_t duration)
processFrameRenders(
SentryFrameInfoTimeSeries *frameInfo, uint64_t profileStart, uint64_t profileDuration)
{
auto relativeFrameInfo = [NSMutableArray array];
[frameInfo enumerateObjectsUsingBlock:^(
NSDictionary<NSString *, NSNumber *> *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
const auto begin = timeIntervalToNanoseconds(obj[@"start_timestamp"].doubleValue);
if (begin < start) {
const auto frameRenderStart
= timeIntervalToNanoseconds(obj[@"start_timestamp"].doubleValue);

# if defined(TEST) || defined(TESTCI)
// we don't currently validate the timestamps in tests, and the mock doesn't provide
// realistic ones, so they'd fail the checks below. just write them directly into the data
// structure so we can count *how many* were recorded
[relativeFrameInfo addObject:@{
@"elapsed_since_start_ns" : @(frameRenderStart),
@"value" : @(frameRenderStart),
}];
return;
Comment on lines +189 to +197
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: Why can't you use useFramesTracker to test this with mocked values?

What you could do instead of this is to just call processFrameRenders directly in your tests with your desired values. You could achieve this by using a test category making processFrameRenders public in tests or use Dynamic(profiler).processFrameRenders().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I thought about it but wasn't quite sure how to mock the values so that the original logic won't wind up crashing with negative durations, which is what the early returns in the logic are for. This workaround essentially allows us to ensure that the frame tracker did in fact record frame render info and pass it to the profiler. As I mentioned in #2493 (comment), I will write more tests to ensure the slicing logic in here and added in that PR are tested for correctness. Are you OK kicking that can down the road a bit more?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you OK kicking that can down the road a bit more?

Obviously, otherwise, I wouldn't have approved your PR.

# else // if not testing, ie, development or production
if (frameRenderStart < profileStart) {
return;
}
const auto end = timeIntervalToNanoseconds(obj[@"end_timestamp"].doubleValue);
const auto relativeEnd = getDurationNs(start, end);
if (relativeEnd > duration) {
const auto frameRenderEnd = timeIntervalToNanoseconds(obj[@"end_timestamp"].doubleValue);
const auto frameRenderEndRelativeToProfileStart = getDurationNs(profileStart, frameRenderEnd);
if (frameRenderEndRelativeToProfileStart > profileDuration) {
SENTRY_LOG_DEBUG(@"The last slow/frozen frame extended past the end of the profile, "
@"will not report it.");
return;
}
const auto relativeStart = getDurationNs(start, begin);
const auto frameDuration = relativeEnd - relativeStart;
const auto frameRenderStartRelativeToProfileStartNs = getDurationNs(profileStart, frameRenderStart);
const auto frameRenderDurationNs = frameRenderEndRelativeToProfileStart - frameRenderStartRelativeToProfileStartNs;
[relativeFrameInfo addObject:@{
@"elapsed_since_start_ns" : @(relativeStart),
@"value" : @(frameDuration),
@"elapsed_since_start_ns" : @(frameRenderStartRelativeToProfileStartNs),
@"value" : @(frameRenderDurationNs),
}];
# endif // defined(TEST) || defined(TESTCI)
}];
return relativeFrameInfo;
}
Expand Down Expand Up @@ -284,7 +305,7 @@ + (void)startForSpanID:(SentrySpanId *)spanID
return;
}
# if SENTRY_HAS_UIKIT
[SentryFramesTracker.sharedInstance resetProfilingTimestamps];
[_gCurrentFramesTracker resetProfilingTimestamps];
# endif // SENTRY_HAS_UIKIT
[_gCurrentProfiler start];
_gCurrentProfiler->_timeoutTimer =
Expand Down Expand Up @@ -383,6 +404,14 @@ + (void)useTimerWrapper:(SentryNSTimerWrapper *)timerWrapper
_gCurrentTimerWrapper = timerWrapper;
}

# if SENTRY_HAS_UIKIT
+ (void)useFramesTracker:(SentryFramesTracker *)framesTracker
{
std::lock_guard<std::mutex> l(_gProfilerLock);
_gCurrentFramesTracker = framesTracker;
}
# endif // SENTRY_HAS_UIKIT

# pragma mark - Private

+ (void)captureEnvelopeIfFinished:(SentryProfiler *)profiler spanID:(SentrySpanId *)spanID
Expand Down Expand Up @@ -431,8 +460,8 @@ + (void)stopProfilerForReason:(SentryProfilerTruncationReason)reason
[_gCurrentProfiler stop];
_gCurrentProfiler->_truncationReason = reason;
# if SENTRY_HAS_UIKIT
_gCurrentProfiler->_frameInfo = SentryFramesTracker.sharedInstance.currentFrames;
[SentryFramesTracker.sharedInstance resetProfilingTimestamps];
_gCurrentProfiler->_frameInfo = _gCurrentFramesTracker.currentFrames;
[_gCurrentFramesTracker resetProfilingTimestamps];
# endif // SENTRY_HAS_UIKIT
_gCurrentProfiler = nil;
}
Expand Down Expand Up @@ -644,19 +673,21 @@ - (void)captureEnvelope
const auto slowTimestamps
= processFrameRenders(_frameInfo.slowFrameTimestamps, _startTimestamp, profileDuration);
if (slowTimestamps.count > 0) {
metrics[@"slow_frame_renders"] = @{ @"unit" : @"nanosecond", @"values" : slowTimestamps };
metrics[kSentryProfilerSerializationKeySlowFrameRenders] =
@{ @"unit" : @"nanosecond", @"values" : slowTimestamps };
}

const auto frozenTimestamps
= processFrameRenders(_frameInfo.frozenFrameTimestamps, _startTimestamp, profileDuration);
if (frozenTimestamps.count > 0) {
metrics[@"frozen_frame_renders"] =
metrics[kSentryProfilerSerializationKeyFrozenFrameRenders] =
@{ @"unit" : @"nanosecond", @"values" : frozenTimestamps };
}

const auto frameRates = processFrameRates(_frameInfo.frameRateTimestamps, _startTimestamp);
if (frameRates.count > 0) {
metrics[@"screen_frame_rates"] = @{ @"unit" : @"hz", @"values" : frameRates };
metrics[kSentryProfilerSerializationKeyFrameRates] =
@{ @"unit" : @"hz", @"values" : frameRates };
}
# endif // SENTRY_HAS_UIKIT

Expand Down
8 changes: 6 additions & 2 deletions Sources/Sentry/include/SentryProfiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ typedef NS_ENUM(NSUInteger, SentryProfilerTruncationReason) {

NS_ASSUME_NONNULL_BEGIN

FOUNDATION_EXPORT const int kSentryProfilerFrequencyHz;
FOUNDATION_EXPORT NSString *const kTestStringConst;
SENTRY_EXTERN const int kSentryProfilerFrequencyHz;
SENTRY_EXTERN NSString *const kTestStringConst;

SENTRY_EXTERN NSString *const kSentryProfilerSerializationKeySlowFrameRenders;
SENTRY_EXTERN NSString *const kSentryProfilerSerializationKeyFrozenFrameRenders;
SENTRY_EXTERN NSString *const kSentryProfilerSerializationKeyFrameRates;

SENTRY_EXTERN_C_BEGIN

Expand Down
4 changes: 4 additions & 0 deletions Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ SentryProfiler ()

+ (void)useTimerWrapper:(SentryNSTimerWrapper *)timerWrapper NS_SWIFT_NAME(useTimerWrapper(_:));

# if SENTRY_HAS_UIKIT
+ (void)useFramesTracker:(SentryFramesTracker *)framesTracker NS_SWIFT_NAME(useFramesTracker(_:));
# endif // SENTRY_HAS_UIKIT

@end

#endif // SENTRY_TARGET_PROFILING_SUPPORTED
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ class SentryFramesTrackerTests: XCTestCase {

private class Fixture {

var displayLinkWrapper: TestDiplayLinkWrapper
var displayLinkWrapper: TestDisplayLinkWrapper
var queue: DispatchQueue

init() {
displayLinkWrapper = TestDiplayLinkWrapper()
displayLinkWrapper = TestDisplayLinkWrapper()
queue = DispatchQueue(label: "SentryFramesTrackerTests", qos: .background, attributes: [.concurrent])
}

lazy var sut: SentryFramesTracker = {
return SentryFramesTracker(displayLinkWrapper: displayLinkWrapper)
}()
lazy var sut: SentryFramesTracker = SentryFramesTracker(displayLinkWrapper: displayLinkWrapper)
}

private var fixture: Fixture!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class SentryFramesTrackingIntegrationTests: XCTestCase {

private class Fixture {
let options = Options()
let displayLink = TestDiplayLinkWrapper()
let displayLink = TestDisplayLinkWrapper()

init() {
options.dsn = TestConstants.dsnAsString(username: "SentryFramesTrackingIntegrationTests")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
class TestDiplayLinkWrapper: SentryDisplayLinkWrapper {
class TestDisplayLinkWrapper: SentryDisplayLinkWrapper {

var target: AnyObject!
var selector: Selector!
Expand Down
4 changes: 2 additions & 2 deletions Tests/SentryTests/Performance/SentryTracerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class SentryTracerTests: XCTestCase {
let idleTimeout: TimeInterval = 1.0

#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
var displayLinkWrapper: TestDiplayLinkWrapper
var displayLinkWrapper: TestDisplayLinkWrapper
#endif

init() {
Expand All @@ -58,7 +58,7 @@ class SentryTracerTests: XCTestCase {
CurrentDate.setCurrentDateProvider(currentDateProvider)

#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
displayLinkWrapper = TestDiplayLinkWrapper()
displayLinkWrapper = TestDisplayLinkWrapper()

SentryFramesTracker.sharedInstance().setDisplayLinkWrapper(displayLinkWrapper)
SentryFramesTracker.sharedInstance().start()
Expand Down
2 changes: 1 addition & 1 deletion Tests/SentryTests/PrivateSentrySDKOnlyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class PrivateSentrySDKOnlyTests: XCTestCase {

func testGetFrames() {
let tracker = SentryFramesTracker.sharedInstance()
let displayLink = TestDiplayLinkWrapper()
let displayLink = TestDisplayLinkWrapper()

tracker.setDisplayLinkWrapper(displayLink)
tracker.start()
Expand Down
72 changes: 56 additions & 16 deletions Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@ class SentryProfilerSwiftTests: XCTestCase {
let message = "some message"
let transactionName = "Some Transaction"
let transactionOperation = "Some Operation"

lazy var systemWrapper = TestSentrySystemWrapper()
lazy var processInfoWrapper = TestSentryNSProcessInfoWrapper()
lazy var timerWrapper = TestSentryNSTimerWrapper()

#if !os(macOS)
lazy var displayLinkWrapper = TestDisplayLinkWrapper()
lazy var framesTracker = SentryFramesTracker(displayLinkWrapper: displayLinkWrapper)
#endif

func newTransaction() -> Span {
hub.startTransaction(name: transactionName, operation: transactionOperation)
}
Expand All @@ -45,7 +51,7 @@ class SentryProfilerSwiftTests: XCTestCase {
super.tearDown()
clearTestState()
SentryTracer.resetAppStartMeasurementRead()
#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
#if !os(macOS)
SentryFramesTracker.sharedInstance().resetFrames()
SentryFramesTracker.sharedInstance().stop()
#endif
Expand All @@ -58,6 +64,9 @@ class SentryProfilerSwiftTests: XCTestCase {
SentryProfiler.useSystemWrapper(fixture.systemWrapper)
SentryProfiler.useProcessInfoWrapper(fixture.processInfoWrapper)
SentryProfiler.useTimerWrapper(fixture.timerWrapper)
#if !os(macOS)
SentryProfiler.useFramesTracker(fixture.framesTracker)
#endif

// mock cpu usage
let cpuUsages = [12.4, 63.5, 1.4, 4.6]
Expand All @@ -76,13 +85,30 @@ class SentryProfilerSwiftTests: XCTestCase {
self.fixture.timerWrapper.fire()
}

#if !os(macOS)
// gather mock GPU frame render timestamps
fixture.framesTracker.start()
fixture.displayLinkWrapper.call() // call once directly to capture previous frame timestamp for comparison with later ones
fixture.displayLinkWrapper.slowFrame()
fixture.displayLinkWrapper.normalFrame()
fixture.displayLinkWrapper.almostFrozenFrame()
fixture.displayLinkWrapper.normalFrame()
fixture.displayLinkWrapper.frozenFrame()
fixture.framesTracker.stop()
#endif

// mock errors gathering cpu usage and memory footprint to ensure they don't add more information to the payload
fixture.systemWrapper.overrides.cpuUsageError = NSError(domain: "test-error", code: 0)
fixture.systemWrapper.overrides.memoryFootprintError = NSError(domain: "test-error", code: 1)
self.fixture.timerWrapper.fire()

// finish profile
let exp = expectation(description: "Receives profile payload")
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
span.finish()

do {
try self.assertMetricsPayload(expectedCPUUsages: cpuUsages, cpuReadings: 2)
try self.assertMetricsPayload(expectedCPUUsages: cpuUsages, usageReadings: 2, expectedMemoryFootprint: memoryFootprint, expectedSlowFrameCount: 2, expectedFrozenFrameCount: 1, expectedFrameRateCount: 1)
exp.fulfill()
} catch {
XCTFail("Encountered error: \(error)")
Expand Down Expand Up @@ -241,8 +267,8 @@ private extension SentryProfilerSwiftTests {
case noEnvelopeCaptured
case noProfileEnvelopeItem
case malformedMetricValueEntry
case noCPUUsageEvents
case noCPUUsageReported
case noMetricsReported
case noMetricValuesFound
}

func getProfileData() throws -> Data {
Expand Down Expand Up @@ -290,7 +316,7 @@ private extension SentryProfilerSwiftTests {
self.assertValidProfileData(data: profileData, transactionEnvironment: transactionEnvironment, numberOfTransactions: numberOfTransactions, shouldTimeout: shouldTimeOut)
}

func assertMetricsPayload(expectedCPUUsages: [Double], cpuReadings: Int) throws {
func assertMetricsPayload(expectedCPUUsages: [Double], usageReadings: Int, expectedMemoryFootprint: SentryRAMBytes, expectedSlowFrameCount: Int, expectedFrozenFrameCount: Int, expectedFrameRateCount: Int) throws {
let profileData = try self.getProfileData()
guard let profile = try JSONSerialization.jsonObject(with: profileData) as? [String: Any] else {
throw TestError.unexpectedProfileDeserializationType
Expand All @@ -301,18 +327,32 @@ private extension SentryProfilerSwiftTests {

for (i, expectedUsage) in expectedCPUUsages.enumerated() {
let key = NSString(format: kSentryMetricProfilerSerializationKeyCPUUsageFormat as NSString, i) as String
guard let cpuUsage = measurements[key] as? [String: Any] else {
throw TestError.noCPUUsageEvents
}
guard let values = cpuUsage["values"] as? [[String: Any]] else {
throw TestError.malformedMetricValueEntry
}
XCTAssertEqual(values.count, cpuReadings)
guard let firstReport = values[0]["value"] as? Double else {
throw TestError.noCPUUsageReported
}
try assertMetricValue(measurements: measurements, key: key, numberOfReadings: usageReadings, expectedValue: expectedUsage)
}

try assertMetricValue(measurements: measurements, key: kSentryMetricProfilerSerializationKeyMemoryFootprint, numberOfReadings: usageReadings, expectedValue: expectedMemoryFootprint)

XCTAssertEqual(firstReport, expectedUsage)
#if !os(macOS)
let dummyValue: UInt64? = nil
try assertMetricValue(measurements: measurements, key: kSentryProfilerSerializationKeySlowFrameRenders, numberOfReadings: expectedSlowFrameCount, expectedValue: dummyValue)
try assertMetricValue(measurements: measurements, key: kSentryProfilerSerializationKeyFrozenFrameRenders, numberOfReadings: expectedFrozenFrameCount, expectedValue: dummyValue)
try assertMetricValue(measurements: measurements, key: kSentryProfilerSerializationKeyFrameRates, numberOfReadings: expectedFrameRateCount, expectedValue: dummyValue)
#endif
}

func assertMetricValue<T: Equatable>(measurements: [String: Any], key: String, numberOfReadings: Int, expectedValue: T?) throws {
guard let metricContainer = measurements[key] as? [String: Any] else {
throw TestError.noMetricsReported
}
guard let values = metricContainer["values"] as? [[String: Any]] else {
throw TestError.malformedMetricValueEntry
}
XCTAssertEqual(values.count, numberOfReadings, "Wrong number of values under \(key)")
if let expectedValue = expectedValue {
guard let memoryFootprintValue = values[0]["value"] as? T else {
throw TestError.noMetricValuesFound
}
XCTAssertEqual(memoryFootprintValue, expectedValue, "Wrong value for \(key)")
}
}

Expand Down
13 changes: 12 additions & 1 deletion Tests/SentryTests/Protocol/SentryNSErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class SentryNSErrorTests: XCTestCase {
XCTAssertEqual(actualUnderlyingError.domain, inputUnderlyingError.domain)
}

func testSerializeWithUnderlyingNSException_disabled() {
func testSerializeWithUnderlyingNSException() {
let inputExceptionName = NSExceptionName.decimalNumberDivideByZeroException
let inputExceptionReason = "test exception reason"
let inputUnderlyingException = NSException(name: inputExceptionName, reason: inputExceptionReason, userInfo: ["some userinfo key": "some userinfo value"])
Expand All @@ -42,4 +42,15 @@ class SentryNSErrorTests: XCTestCase {
XCTAssertEqual(actualDescription, "\(inputDescription) (\(inputExceptionReason))")
}

func testWithKernelError() {
let inputKernelErrorCode = KERN_NOT_RECEIVER
let inputDescription = "some test kernel error"
let actualError = NSErrorFromSentryErrorWithKernelError(SentryError.unknownError, inputDescription, inputKernelErrorCode)

guard let actualDescription = actualError?.localizedDescription else {
XCTFail("Expected a localizedDescription in the resulting error")
return
}
XCTAssertEqual(actualDescription, "\(inputDescription) (The task in question does not hold receive rights for the port argument.)")
}
}