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

ref(profiling): put back some unnecessarily prefixed functions #3882

Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
390a859
ref: move sliceGPUData to timeseries code unit; prefix C functions
armcknight Apr 23, 2024
9d0cb71
ref: move test helpers to separate code unit; prefix with _sentry
armcknight Apr 23, 2024
08e4e6a
ref: rename more functions to prefix with sentry_ or to remove leadin…
armcknight Apr 23, 2024
86bce30
ref(profiling): extract function to handle SDK startup tasks
armcknight Apr 23, 2024
cc1debd
ref(profiling): extract methods related to profile serialization
armcknight Apr 23, 2024
034fb4b
uncomment gates
armcknight Apr 23, 2024
e9147d5
remove unused imports
armcknight Apr 23, 2024
60e45a7
dead code
armcknight Apr 23, 2024
a129273
fix macos build
armcknight Apr 23, 2024
33639a8
fix xcframework build
armcknight Apr 23, 2024
e4893c2
Merge remote-tracking branch 'origin/main' into armcknight/feat/3555-…
armcknight Apr 23, 2024
2b9b637
Merge branch 'armcknight/feat/3555-continuous-profiling/4-refactoring…
armcknight Apr 23, 2024
ee1deb9
Merge branch 'armcknight/feat/3555-continuous-profiling/4-refactoring…
armcknight Apr 23, 2024
7c7aaf7
Merge remote-tracking branch 'origin/main' into armcknight/feat/3555-…
armcknight Apr 23, 2024
9a9d689
move to test util target
armcknight Apr 23, 2024
56b831e
put back some unnecessarily prefixed functions
armcknight Apr 23, 2024
e7a9688
fixup! put back some unnecessarily prefixed functions
armcknight Apr 23, 2024
0b3796f
Revert "move to test util target"
armcknight Apr 23, 2024
d2d5e03
Merge branch 'armcknight/feat/3555-continuous-profiling/4-refactoring…
armcknight Apr 23, 2024
4864579
move some C declarations to static scope
armcknight Apr 23, 2024
3ae3438
Merge remote-tracking branch 'origin/main' into armcknight/feat/3555-…
armcknight Apr 25, 2024
d4f8c74
Merge remote-tracking branch 'origin/main' into armcknight/feat/3555-…
armcknight Apr 26, 2024
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
10 changes: 10 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,8 @@
8454CF8D293EAF9A006AC140 /* SentryMetricProfiler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */; };
8459FCB72BD737AC0038E9C9 /* SentryProfilerTestHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 8459FCB62BD737AC0038E9C9 /* SentryProfilerTestHelpers.h */; };
8459FCB82BD738CE0038E9C9 /* SentryProfilerTestHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 8459FCB32BD737250038E9C9 /* SentryProfilerTestHelpers.m */; };
8459FCBE2BD73E820038E9C9 /* SentryProfilerSerialization.h in Headers */ = {isa = PBXBuildFile; fileRef = 8459FCBD2BD73E810038E9C9 /* SentryProfilerSerialization.h */; };
8459FCC02BD73EB20038E9C9 /* SentryProfilerSerialization.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8459FCBF2BD73EB20038E9C9 /* SentryProfilerSerialization.mm */; };
845C16D52A622A5B00EC9519 /* SentryTracer+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 845C16D42A622A5B00EC9519 /* SentryTracer+Private.h */; };
8489B8882A5F7905009A055A /* SentryThreadWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8489B8872A5F7905009A055A /* SentryThreadWrapperTests.swift */; };
848A45192BBF8D33006AAAEC /* SentryContinuousProfiler.m in Sources */ = {isa = PBXBuildFile; fileRef = 848A45182BBF8D33006AAAEC /* SentryContinuousProfiler.m */; };
Expand Down Expand Up @@ -1685,6 +1687,9 @@
8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = SentryMetricProfiler.mm; path = Sources/Sentry/SentryMetricProfiler.mm; sourceTree = SOURCE_ROOT; };
8459FCB32BD737250038E9C9 /* SentryProfilerTestHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryProfilerTestHelpers.m; sourceTree = "<group>"; };
8459FCB62BD737AC0038E9C9 /* SentryProfilerTestHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryProfilerTestHelpers.h; path = Sources/Sentry/include/SentryProfilerTestHelpers.h; sourceTree = SOURCE_ROOT; };
8459FCBD2BD73E810038E9C9 /* SentryProfilerSerialization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryProfilerSerialization.h; path = Sources/Sentry/include/SentryProfilerSerialization.h; sourceTree = SOURCE_ROOT; };
8459FCBF2BD73EB20038E9C9 /* SentryProfilerSerialization.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryProfilerSerialization.mm; sourceTree = "<group>"; };
8459FCC12BD73EEF0038E9C9 /* SentryProfilerSerialization+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryProfilerSerialization+Test.h"; sourceTree = "<group>"; };
845C16D42A622A5B00EC9519 /* SentryTracer+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryTracer+Private.h"; path = "include/SentryTracer+Private.h"; sourceTree = "<group>"; };
8489B8872A5F7905009A055A /* SentryThreadWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SentryThreadWrapperTests.swift; path = Helper/SentryThreadWrapperTests.swift; sourceTree = "<group>"; };
848A45172BBF8D33006AAAEC /* SentryContinuousProfiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryContinuousProfiler.h; path = ../include/SentryContinuousProfiler.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3318,6 +3323,9 @@
03F84D1B27DD414C008FE43F /* SentryMachLogging.hpp */,
844EDD6B2949387000C86F34 /* SentryMetricProfiler.h */,
8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */,
8459FCBD2BD73E810038E9C9 /* SentryProfilerSerialization.h */,
8459FCBF2BD73EB20038E9C9 /* SentryProfilerSerialization.mm */,
8459FCC12BD73EEF0038E9C9 /* SentryProfilerSerialization+Test.h */,
84AF45A429A7FFA500FBB177 /* SentryProfiledTracerConcurrency.h */,
84AF45A529A7FFA500FBB177 /* SentryProfiledTracerConcurrency.mm */,
840B7EF22BBF83DF008B8120 /* SentryProfiler+Private.h */,
Expand Down Expand Up @@ -3945,6 +3953,7 @@
7B85DC1E24EFAFCD007D01D2 /* SentryClient+Private.h in Headers */,
A8AFFCCF2906C03700967CD7 /* SentryRequest.h in Headers */,
7BD4BD4327EB29BA0071F4FF /* SentryClientReport.h in Headers */,
8459FCBE2BD73E820038E9C9 /* SentryProfilerSerialization.h in Headers */,
7B2BB0032966F55900A1E102 /* SentryOptions+HybridSDKs.h in Headers */,
7BF9EF762722B34700B5BBEF /* SentrySubClassFinder.h in Headers */,
7BC852332458802C005A70F0 /* SentryDataCategoryMapper.h in Headers */,
Expand Down Expand Up @@ -4386,6 +4395,7 @@
7BF9EF782722B35D00B5BBEF /* SentrySubClassFinder.m in Sources */,
D80CD8D32B751447002F710B /* SentryMXCallStackTree.swift in Sources */,
7BCFA71627D0BB50008C662C /* SentryANRTracker.m in Sources */,
8459FCC02BD73EB20038E9C9 /* SentryProfilerSerialization.mm in Sources */,
63EED6C02237923600E02400 /* SentryOptions.m in Sources */,
D8CB741B2947286500A5F964 /* SentryEnvelopeItemHeader.m in Sources */,
D8CB7417294724CC00A5F964 /* SentryEnvelopeAttachmentHeader.m in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion SentryTestUtils/ClearTestState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class TestCleanup: NSObject {

#if os(iOS) || os(macOS) || targetEnvironment(macCatalyst)
SentryProfiler.getCurrent().stop(for: .normal)
SentryProfiler.sentry_resetConcurrencyTracking()
SentryProfiler.resetConcurrencyTracking()
#endif // os(iOS) || os(macOS) || targetEnvironment(macCatalyst)

#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
Expand Down
8 changes: 3 additions & 5 deletions Sources/Sentry/PrivateSentrySDKOnly.mm
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#import "SentryOptions.h"
#import "SentryProfiledTracerConcurrency.h"
#import "SentryProfiler+Private.h"
#import "SentryProfilerSerialization.h"
#import "SentrySDK+Private.h"
#import "SentrySerialization.h"
#import "SentrySwift.h"
Expand Down Expand Up @@ -135,11 +136,8 @@ + (uint64_t)startProfilerForTrace:(SentryId *)traceId;
and:(uint64_t)endSystemTime
forTrace:(SentryId *)traceId;
{
NSMutableDictionary<NSString *, id> *payload =
[SentryProfiler collectProfileBetween:startSystemTime
and:endSystemTime
forTrace:traceId
onHub:[SentrySDK currentHub]];
NSMutableDictionary<NSString *, id> *payload = sentry_collectProfileData(
startSystemTime, endSystemTime, traceId, [SentrySDK currentHub]);

if (payload != nil) {
payload[@"platform"] = SentryPlatformName;
Expand Down
41 changes: 41 additions & 0 deletions Sources/Sentry/Profiling/SentryProfilerSerialization+Test.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#import "SentryProfilingConditionals.h"

#if SENTRY_TARGET_PROFILING_SUPPORTED

# if defined(TEST) || defined(TESTCI)

# import "SentryDefines.h"
# import "SentryProfiler+Private.h"
# import <Foundation/Foundation.h>

@class SentryDebugMeta;
@class SentryHub;

NS_ASSUME_NONNULL_BEGIN

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

SENTRY_EXTERN NSString *sentry_profilerTruncationReasonName(SentryProfilerTruncationReason reason);

/**
* An intermediate function that can serve requests from either the native SDK or hybrid SDKs; they
* will have different structures/objects available, these parameters are the common elements
* needed to construct the payload dictionary.
*/
SENTRY_EXTERN NSMutableDictionary<NSString *, id> *sentry_serializedProfileData(
NSDictionary<NSString *, id> *profileData, uint64_t startSystemTime, uint64_t endSystemTime,
NSString *truncationReason, NSDictionary<NSString *, id> *serializedMetrics,
NSArray<SentryDebugMeta *> *debugMeta, SentryHub *hub
# if SENTRY_HAS_UIKIT
,
SentryScreenFrames *gpuData
# endif // SENTRY_HAS_UIKIT
);

NS_ASSUME_NONNULL_END

# endif // defined(TEST) || defined(TESTCI)

#endif // SENTRY_TARGET_PROFILING_SUPPORTED
271 changes: 271 additions & 0 deletions Sources/Sentry/Profiling/SentryProfilerSerialization.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
#import "SentryProfilerSerialization.h"

#if SENTRY_TARGET_PROFILING_SUPPORTED

# import "SentryClient+Private.h"
# import "SentryDateUtils.h"
# import "SentryDebugImageProvider.h"
# import "SentryDebugMeta.h"
# import "SentryDependencyContainer.h"
# import "SentryDevice.h"
# import "SentryEnvelope.h"
# import "SentryEnvelopeItemHeader.h"
# import "SentryEnvelopeItemType.h"
# import "SentryEvent+Private.h"
# import "SentryFormatter.h"
# import "SentryHub.h"
# import "SentryLog.h"
# import "SentryMetricProfiler.h"
# import "SentryOptions.h"
# import "SentryProfileTimeseries.h"
# import "SentryProfiledTracerConcurrency.h"
# import "SentryProfiler+Private.h"
# import "SentryProfilerSerialization+Test.h"
# import "SentryProfilerSerialization.h"
# import "SentryProfilerState.h"
# import "SentryProfilerTestHelpers.h"
# import "SentrySample.h"
# import "SentryScope+Private.h"
# import "SentrySerialization.h"
# import "SentrySwift.h"
# import "SentryThread.h"
# import "SentryTime.h"
# import "SentryTracer+Private.h"
# import "SentryTransaction.h"
# import "SentryTransactionContext+Private.h"

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

# pragma mark - Private

namespace {

/**
* Given an array of samples with absolute timestamps, return the serialized JSON mapping with
* their data, with timestamps normalized relative to the provided transaction's start time.
* */
NSArray<NSDictionary *> *
_sentry_serializedSamplesWithRelativeTimestamps(
NSArray<SentrySample *> *samples, uint64_t startSystemTime)
{
const auto result = [NSMutableArray<NSDictionary *> array];
[samples enumerateObjectsUsingBlock:^(
SentrySample *_Nonnull sample, NSUInteger idx, BOOL *_Nonnull stop) {
// This shouldn't happen as we would've filtered out any such samples, but we should still
// guard against it before calling getDurationNs as a defensive measure
if (!orderedChronologically(startSystemTime, sample.absoluteTimestamp)) {
SENTRY_LOG_WARN(@"Filtered sample not chronological with transaction.");
return;
}
const auto dict = [NSMutableDictionary dictionaryWithDictionary:@ {
@"elapsed_since_start_ns" :
sentry_stringForUInt64(getDurationNs(startSystemTime, sample.absoluteTimestamp)),
@"thread_id" : sentry_stringForUInt64(sample.threadID),
@"stack_id" : sample.stackIndex,
}];
if (sample.queueAddress) {
dict[@"queue_address"] = sample.queueAddress;
}

[result addObject:dict];
}];
return result;
}

} // namespace

# pragma mark - Exported for tests

NSString *
sentry_profilerTruncationReasonName(SentryProfilerTruncationReason reason)
{
switch (reason) {
case SentryProfilerTruncationReasonNormal:
return @"normal";
case SentryProfilerTruncationReasonAppMovedToBackground:
return @"backgrounded";
case SentryProfilerTruncationReasonTimeout:
return @"timeout";
}
}

NSMutableDictionary<NSString *, id> *
sentry_serializedProfileData(
NSDictionary<NSString *, id> *profileData, uint64_t startSystemTime, uint64_t endSystemTime,
NSString *truncationReason, NSDictionary<NSString *, id> *serializedMetrics,
NSArray<SentryDebugMeta *> *debugMeta, SentryHub *hub
# if SENTRY_HAS_UIKIT
,
SentryScreenFrames *gpuData
# endif // SENTRY_HAS_UIKIT
)
{
NSMutableArray<SentrySample *> *const samples = profileData[@"profile"][@"samples"];
// We need at least two samples to be able to draw a stack frame for any given function: one
// sample for the start of the frame and another for the end. Otherwise we would only have a
// stack frame with 0 duration, which wouldn't make sense.
if ([samples count] < 2) {
SENTRY_LOG_DEBUG(@"Not enough samples in profile");
[hub.getClient recordLostEvent:kSentryDataCategoryProfile
reason:kSentryDiscardReasonEventProcessor];
return nil;
}

// slice the profile data to only include the samples/metrics within the transaction
const auto slicedSamples = sentry_slicedProfileSamples(samples, startSystemTime, endSystemTime);
if (slicedSamples.count < 2) {
SENTRY_LOG_DEBUG(@"Not enough samples in profile during the transaction");
[hub.getClient recordLostEvent:kSentryDataCategoryProfile
reason:kSentryDiscardReasonEventProcessor];
return nil;
}
const auto payload = [NSMutableDictionary<NSString *, id> dictionary];
NSMutableDictionary<NSString *, id> *const profile = [profileData[@"profile"] mutableCopy];
profile[@"samples"]
= _sentry_serializedSamplesWithRelativeTimestamps(slicedSamples, startSystemTime);
payload[@"profile"] = profile;

payload[@"version"] = @"1";
const auto debugImages = [NSMutableArray<NSDictionary<NSString *, id> *> new];
for (SentryDebugMeta *debugImage in debugMeta) {
[debugImages addObject:[debugImage serialize]];
}
if (debugImages.count > 0) {
payload[@"debug_meta"] = @ { @"images" : debugImages };
}

payload[@"os"] = @ {
@"name" : sentry_getOSName(),
@"version" : sentry_getOSVersion(),
@"build_number" : sentry_getOSBuildNumber()
};

const auto isEmulated = sentry_isSimulatorBuild();
payload[@"device"] = @{
@"architecture" : sentry_getCPUArchitecture(),
@"is_emulator" : @(isEmulated),
@"locale" : NSLocale.currentLocale.localeIdentifier,
@"manufacturer" : @"Apple",
@"model" : isEmulated ? sentry_getSimulatorDeviceModel() : sentry_getDeviceModel()
};

payload[@"profile_id"] = [[[SentryId alloc] init] sentryIdString];
payload[@"truncation_reason"] = truncationReason;
payload[@"environment"] = hub.scope.environmentString ?: hub.getClient.options.environment;
payload[@"release"] = hub.getClient.options.releaseName;

// add the gathered metrics
auto metrics = serializedMetrics;

# if SENTRY_HAS_UIKIT
const auto mutableMetrics =
[NSMutableDictionary<NSString *, id> dictionaryWithDictionary:metrics];
const auto slowFrames = sentry_sliceGPUData(gpuData.slowFrameTimestamps, startSystemTime,
endSystemTime, /*useMostRecentRecording */ NO);
if (slowFrames.count > 0) {
mutableMetrics[kSentryProfilerSerializationKeySlowFrameRenders] =
@ { @"unit" : @"nanosecond", @"values" : slowFrames };
}

const auto frozenFrames
= sentry_sliceGPUData(gpuData.frozenFrameTimestamps, startSystemTime, endSystemTime,
/*useMostRecentRecording */ NO);
if (frozenFrames.count > 0) {
mutableMetrics[kSentryProfilerSerializationKeyFrozenFrameRenders] =
@ { @"unit" : @"nanosecond", @"values" : frozenFrames };
}

if (slowFrames.count > 0 || frozenFrames.count > 0) {
const auto frameRates
= sentry_sliceGPUData(gpuData.frameRateTimestamps, startSystemTime, endSystemTime,
/*useMostRecentRecording */ YES);
if (frameRates.count > 0) {
mutableMetrics[kSentryProfilerSerializationKeyFrameRates] =
@ { @"unit" : @"hz", @"values" : frameRates };
}
}
metrics = mutableMetrics;
# endif // SENTRY_HAS_UIKIT

if (metrics.count > 0) {
payload[@"measurements"] = metrics;
}

return payload;
}

# pragma mark - Public

SentryEnvelopeItem *_Nullable sentry_profileEnvelopeItem(
SentryTransaction *transaction, NSDate *startTimestamp)
{
SENTRY_LOG_DEBUG(@"Creating profiling envelope item");
const auto profiler = sentry_profilerForFinishedTracer(transaction.trace.internalID);
if (!profiler) {
return nil;
}

const auto payload = sentry_serializedProfileData(
[profiler.state copyProfilingData], transaction.startSystemTime, transaction.endSystemTime,
sentry_profilerTruncationReasonName(profiler.truncationReason),
[profiler.metricProfiler serializeBetween:transaction.startSystemTime
and:transaction.endSystemTime],
[SentryDependencyContainer.sharedInstance.debugImageProvider getDebugImagesCrashed:NO],
transaction.trace.hub
# if SENTRY_HAS_UIKIT
,
profiler.screenFrameData
# endif // SENTRY_HAS_UIKIT
);

# if defined(TEST) || defined(TESTCI) || defined(DEBUG)
sentry_writeProfileFile(payload);
# endif // defined(TEST) || defined(TESTCI) || defined(DEBUG)

if (payload == nil) {
SENTRY_LOG_DEBUG(@"Payload was empty, will not create a profiling envelope item.");
return nil;
}

payload[@"platform"] = transaction.platform;
payload[@"transaction"] = @ {
@"id" : transaction.eventId.sentryIdString,
@"trace_id" : transaction.trace.traceId.sentryIdString,
@"name" : transaction.transaction,
@"active_thread_id" : [transaction.trace.transactionContext sentry_threadInfo].threadId
};
payload[@"timestamp"] = sentry_toIso8601String(startTimestamp);

const auto JSONData = [SentrySerialization dataWithJSONObject:payload];
if (JSONData == nil) {
SENTRY_LOG_DEBUG(@"Failed to encode profile to JSON.");
return nil;
}

const auto header = [[SentryEnvelopeItemHeader alloc] initWithType:SentryEnvelopeItemTypeProfile
length:JSONData.length];
return [[SentryEnvelopeItem alloc] initWithHeader:header data:JSONData];
}

NSMutableDictionary<NSString *, id> *_Nullable sentry_collectProfileData(
uint64_t startSystemTime, uint64_t endSystemTime, SentryId *traceId, SentryHub *hub)
{
const auto profiler = sentry_profilerForFinishedTracer(traceId);
if (!profiler) {
return nil;
}

return sentry_serializedProfileData([profiler.state copyProfilingData], startSystemTime,
endSystemTime, sentry_profilerTruncationReasonName(profiler.truncationReason),
[profiler.metricProfiler serializeBetween:startSystemTime and:endSystemTime],
[SentryDependencyContainer.sharedInstance.debugImageProvider getDebugImagesCrashed:NO], hub
# if SENTRY_HAS_UIKIT
,
profiler.screenFrameData
# endif // SENTRY_HAS_UIKIT
);
}

#endif // SENTRY_TARGET_PROFILING_SUPPORTED
Loading
Loading