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: Measure slow and frozen frames #1123

Merged
merged 102 commits into from
Jun 11, 2021
Merged
Show file tree
Hide file tree
Changes from 101 commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
3c18e93
feat: Measure app start time
philipphofmann May 12, 2021
ea33558
Add changelog entry
philipphofmann May 12, 2021
551c638
Merge branch 'master' into feat/app-start-measurement
philipphofmann May 12, 2021
5199e6c
Send measurement with first transaction
philipphofmann May 14, 2021
b1060df
Remove sending transaction for app start
philipphofmann May 14, 2021
9020a69
Cleanup
philipphofmann May 14, 2021
f6f003e
Merge branch 'master' into feat/app-start-measurement
philipphofmann May 17, 2021
76e0659
backup
philipphofmann May 17, 2021
e3772bd
Backup
philipphofmann May 18, 2021
4b45426
Comments and check if has UI Kit
philipphofmann May 18, 2021
8df915f
Double-checked lock for SentryTracer
philipphofmann May 18, 2021
cedd656
Remove sync for measurements in transaction
philipphofmann May 18, 2021
a2fc344
Merge branch 'master' into feat/app-start-measurement
philipphofmann May 19, 2021
4402cc0
Use process start time as app start
philipphofmann May 19, 2021
ce244e9
Add tests for SentrySysctl
philipphofmann May 19, 2021
5c8b123
Fix reuse of len
philipphofmann May 19, 2021
9838bc3
Fix SentryTracerTests
philipphofmann May 19, 2021
17304c7
Merge branch 'master' into feat/app-start-measurement
philipphofmann May 19, 2021
39713b8
Add comment about process start time
philipphofmann May 19, 2021
828c20c
Merge branch 'master' into feat/fps
philipphofmann May 20, 2021
be78b66
Merge branch 'feat/app-start-measurement' into feat/fps
philipphofmann May 20, 2021
f5136b7
Add frame measurements to transaction
philipphofmann May 20, 2021
1a212e2
Fix off by 1 error in SentryFramesTracker
philipphofmann May 20, 2021
734b16d
Minor cleanup
philipphofmann May 20, 2021
06cf2e5
Changelog Entry
philipphofmann May 20, 2021
9a7ea15
Align measurements with contract
philipphofmann May 20, 2021
dd0b20d
Import unistd.h in SentryCrashSysCtl
philipphofmann May 20, 2021
692e9c7
Merge branch 'feat/app-start-measurement' into feat/fps
philipphofmann May 20, 2021
bddc723
Update Changelog
philipphofmann May 20, 2021
cd9357c
Fix build for MacOS
philipphofmann May 20, 2021
016e942
Fix tests
philipphofmann May 20, 2021
06cb504
Fix tests for tvOS
philipphofmann May 20, 2021
a0da1b9
Fix build failure for macOS tests
philipphofmann May 21, 2021
030dc37
Fix tests again
philipphofmann May 21, 2021
fda0f75
Add option enableAppStartMeasuring
philipphofmann May 21, 2021
1bcf8dc
Merge branch 'master' into feat/app-start-measurement
philipphofmann May 21, 2021
acd1dbc
Merge branch 'feat/app-start-measurement' into feat/fps
philipphofmann May 21, 2021
6f2b1df
Add enableRenderFrameMeasuring to options
philipphofmann May 21, 2021
af57ec1
Fix SentryAppStartTrackingIntegrationTests
philipphofmann May 21, 2021
43d3bcc
Merge branch 'feat/app-start-measurement' into feat/fps
philipphofmann May 21, 2021
350343a
Merge branch 'master' into feat/app-start-measurement
philipphofmann May 25, 2021
d597807
Set default of option to NO
philipphofmann May 25, 2021
efff92f
Merge branch 'feat/app-start-measurement' into feat/fps
philipphofmann May 25, 2021
d0266c0
Set default option to NO
philipphofmann May 25, 2021
b2cfcea
Fix tests
philipphofmann May 25, 2021
24fc1d1
Merge branch 'feat/app-start-measurement' into feat/fps
philipphofmann May 25, 2021
baa40b5
Check tracesSampleRate
philipphofmann May 25, 2021
98cf834
Comment about internal use
philipphofmann May 25, 2021
f3f0ee4
Add lock for appStartMeasurement
philipphofmann May 25, 2021
12b0bd1
Add enum for SentryAppStartType
philipphofmann May 25, 2021
e564b8c
backup
philipphofmann May 26, 2021
ad62300
feat: Auto UI Performance Instrumentation (#1105)
brustolin May 27, 2021
c775f1f
Merge branch 'feat/auto-performance-instrumentation' into feat/app-st…
philipphofmann May 27, 2021
6f48f37
Merge branch 'feat/auto-performance-instrumentation' into feat/app-st…
philipphofmann May 27, 2021
074d99d
Format code
getsentry-bot May 27, 2021
b0baa73
Fix test build error
philipphofmann May 27, 2021
db925b6
Format code
getsentry-bot May 27, 2021
02a75bb
feat: Auto UI Performance Instrumentation (#1105)
brustolin May 27, 2021
c88ce82
ref: Rename waitChildren to waitForChildren (#1138)
philipphofmann May 27, 2021
6ae80ef
fix: Logic for canBeFinished in SentryTracer
brustolin May 28, 2021
6d02e65
fix: Capturing Child Spans (#1139)
brustolin May 28, 2021
6c38bd7
Merge branch 'feat/auto-performance-instrumentation' into feat/app-st…
philipphofmann May 28, 2021
7b61f96
Add start up as spans
philipphofmann May 31, 2021
df9cd14
Format code
getsentry-bot May 31, 2021
0727b57
Fix tests
philipphofmann May 31, 2021
e895770
Add tests for app start spans
philipphofmann May 31, 2021
9fe8a69
fix: Set description for performance tracker spans (#1142)
philipphofmann May 31, 2021
a0b98ca
Fix SentryAppStartTrackingIntegrationTests
philipphofmann Jun 1, 2021
4d9c230
Change default of enableAppStartMeasuring to YES
philipphofmann Jun 1, 2021
7d3114e
Merge branch 'feat/auto-performance-instrumentation' into feat/app-st…
philipphofmann Jun 1, 2021
a63833e
Only enable integration when auto UI performance is on
philipphofmann Jun 1, 2021
6676f42
Safety check in currentProcessStartTime
philipphofmann Jun 1, 2021
4e7306f
Minor cleanup in SentryAppStartTracker
philipphofmann Jun 1, 2021
1515471
Fix testFinishAsync
philipphofmann Jun 1, 2021
da2b671
Check if transaction is auto generated
philipphofmann Jun 1, 2021
26b80f5
Remove double serialization of spanDescription
philipphofmann Jun 1, 2021
f27cb68
Undo changes in iOS-Swift.xcscheme
philipphofmann Jun 2, 2021
ca6bb07
Cleanup
philipphofmann Jun 2, 2021
c6d5d0a
Improve SentryAppStartTrackerTests
philipphofmann Jun 2, 2021
59a35fe
Check for tracesSampler
philipphofmann Jun 2, 2021
3c577b0
feat: Auto UI Performance Instrumentation (#1105)
brustolin May 27, 2021
2681026
ref: Rename waitChildren to waitForChildren (#1138)
philipphofmann May 27, 2021
4b88bf7
fix: Logic for canBeFinished in SentryTracer
brustolin May 28, 2021
1d070e8
fix: Capturing Child Spans (#1139)
brustolin May 28, 2021
36db1e2
fix: Set description for performance tracker spans (#1142)
philipphofmann May 31, 2021
ee400f2
Merge branch 'feat/auto-performance-instrumentation' into feat/app-st…
philipphofmann Jun 2, 2021
bb2da5b
Merge branch 'feat/app-start-measurement' into feat/fps
philipphofmann Jun 4, 2021
006de22
enableRenderFrameMeasuring per default
philipphofmann Jun 4, 2021
dcc196f
Fix SentryTracerTests for macOS
philipphofmann Jun 4, 2021
3093eb9
Only track frames if isRunning
philipphofmann Jun 4, 2021
cbcc2dd
Don't add frames when start time changed
philipphofmann Jun 4, 2021
f5df465
Merge branch 'master' into feat/fps
philipphofmann Jun 8, 2021
16a2275
Fix changelog
philipphofmann Jun 8, 2021
3a772f5
Rename Option to enableFrameRenderMeasuring
philipphofmann Jun 8, 2021
07f7333
Fix tests for macOS
philipphofmann Jun 8, 2021
fd018ce
Merge branch 'master' into feat/fps
philipphofmann Jun 10, 2021
503f964
Merge branch 'master' into feat/fps
philipphofmann Jun 11, 2021
4f310f0
Log if no UIKit
philipphofmann Jun 11, 2021
2abb57d
Check if frames are a positive number
philipphofmann Jun 11, 2021
967a449
Use fixed date in test
philipphofmann Jun 11, 2021
2541380
Fix Fixture in SentryFramesTrackingIntegrationTests
philipphofmann Jun 11, 2021
5128a7a
Don't send if all frames zero
philipphofmann Jun 11, 2021
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ fix: Operation names for auto instrumentation (#1164)

## 7.2.0-beta.0

- feat: Measure slow and frozen frames (#1123)
- feat: Measure app start time (#1111)
- feat: Auto UI Performance Instrumentation (#1105, #1150, #1136, #1139, #1042)

Expand Down
38 changes: 38 additions & 0 deletions Sentry.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@
<dict>
<key>classNames</key>
<dict>
<key>SentryFramesTrackerTests</key>
<dict>
<key>testPerformanceOfTrackingFrames()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.0013329</real>
<key>baselineIntegrationDisplayName</key>
<string>Local Baseline</string>
</dict>
</dict>
</dict>
<key>SentryHttpTransportTests</key>
<dict>
<key>testPerformanceOfSending()</key>
Expand Down
5 changes: 5 additions & 0 deletions Sources/Sentry/Public/SentryOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ NS_SWIFT_NAME(Options)
*/
@property (nonatomic, assign) BOOL enableAppStartMeasuring;

/**
* Whether to enable measuring the rendering of frames of the UI or not. Default is YES.
*/
@property (nonatomic, assign) BOOL enableFrameRenderMeasuring;

/**
* The interval to end a session if the App goes to the background.
*/
Expand Down
140 changes: 140 additions & 0 deletions Sources/Sentry/SentryFramesTracker.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#import "SentryFramesTracker.h"
#import "SentryDisplayLinkWrapper.h"
#import "SentryLog.h"
#import "SentryOptions.h"
#include <stdatomic.h>

#if SENTRY_HAS_UIKIT
# import <UIKit/UIKit.h>

static CFTimeInterval const SentryFrozenFrameThreshold = 0.7;
static CFTimeInterval const SentryPreviousFrameInitalValue = -1;

/**
* Relaxed memoring ordering is typical for incrementing counters. This operation only requires
* atomicity but not ordering or synchronization.
*/
static memory_order const SentryFramesMemoryOrder = memory_order_relaxed;

@interface
SentryFramesTracker ()

@property (nonatomic, strong, readonly) SentryDisplayLinkWrapper *displayLinkWrapper;
@property (nonatomic, assign, readonly) CFTimeInterval slowFrameThreshold;
@property (nonatomic, assign) CFTimeInterval previousFrameTimestamp;

@end

@implementation SentryFramesTracker {

/**
* With 32 bit we can track frames with 120 fps for around 414 days (2^32 / (120* 60 * 60 *
* 24)).
*/
atomic_uint_fast32_t _totalFrames;
atomic_uint_fast32_t _slowFrames;
atomic_uint_fast32_t _frozenFrames;
}

+ (instancetype)sharedInstance
{
static SentryFramesTracker *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance =
[[self alloc] initWithDisplayLinkWrapper:[[SentryDisplayLinkWrapper alloc] init]];
});
return sharedInstance;
}

/** Internal constructor for testing */
- (instancetype)initWithDisplayLinkWrapper:(SentryDisplayLinkWrapper *)displayLinkWrapper
{
if (self = [super init]) {
_isRunning = NO;
_displayLinkWrapper = displayLinkWrapper;

// If we can't get the frame rate we assume it is 60.
double maximumFramesPerSecond = 60.0;
if (@available(iOS 10.3, tvOS 10.3, macCatalyst 13.0, *)) {
maximumFramesPerSecond = (double)UIScreen.mainScreen.maximumFramesPerSecond;
}

// Most frames take just a few microseconds longer than the optimal caculated duration.
// Therefore we substract one, because otherwise almost all frames would be slow.
_slowFrameThreshold = 1 / (maximumFramesPerSecond - 1);

[self resetFrames];
}
return self;
}

/** Internal for testing */
- (void)setDisplayLinkWrapper:(SentryDisplayLinkWrapper *)displayLinkWrapper
{
_displayLinkWrapper = displayLinkWrapper;
}

/** Internal for testing */
- (void)resetFrames
{
atomic_store_explicit(&_totalFrames, 0, SentryFramesMemoryOrder);
atomic_store_explicit(&_frozenFrames, 0, SentryFramesMemoryOrder);
atomic_store_explicit(&_slowFrames, 0, SentryFramesMemoryOrder);

self.previousFrameTimestamp = SentryPreviousFrameInitalValue;
}

- (void)start
{
_isRunning = YES;
[_displayLinkWrapper linkWithTarget:self selector:@selector(displayLinkCallback)];
}

- (void)displayLinkCallback
{
CFTimeInterval lastFrameTimestamp = self.displayLinkWrapper.timestamp;

if (self.previousFrameTimestamp == SentryPreviousFrameInitalValue) {
self.previousFrameTimestamp = lastFrameTimestamp;
return;
}

CFTimeInterval frameDuration = lastFrameTimestamp - self.previousFrameTimestamp;
self.previousFrameTimestamp = lastFrameTimestamp;

if (frameDuration > self.slowFrameThreshold && frameDuration <= SentryFrozenFrameThreshold) {
atomic_fetch_add_explicit(&_slowFrames, 1, SentryFramesMemoryOrder);
}

if (frameDuration > SentryFrozenFrameThreshold) {
atomic_fetch_add_explicit(&_frozenFrames, 1, SentryFramesMemoryOrder);
}

atomic_fetch_add_explicit(&_totalFrames, 1, SentryFramesMemoryOrder);
}

- (NSUInteger)currentTotalFrames
{
return atomic_load_explicit(&_totalFrames, SentryFramesMemoryOrder);
}

- (NSUInteger)currentSlowFrames
{
return atomic_load_explicit(&_slowFrames, SentryFramesMemoryOrder);
}

- (NSUInteger)currentFrozenFrames
{
return atomic_load_explicit(&_frozenFrames, SentryFramesMemoryOrder);
}

- (void)stop
{
_isRunning = NO;
[self.displayLinkWrapper invalidate];
}

@end

#endif
51 changes: 51 additions & 0 deletions Sources/Sentry/SentryFramesTrackingIntegration.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#import "SentryFramesTrackingIntegration.h"
#import "SentryDisplayLinkWrapper.h"
#import "SentryFramesTracker.h"
#import "SentryLog.h"
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface
SentryFramesTrackingIntegration ()

#if SENTRY_HAS_UIKIT
@property (nonatomic, strong) SentryFramesTracker *tracker;
#endif

@end

@implementation SentryFramesTrackingIntegration

- (void)installWithOptions:(SentryOptions *)options
{
#if SENTRY_HAS_UIKIT
if (options.enableFrameRenderMeasuring) {
self.tracker = [SentryFramesTracker sharedInstance];
[self.tracker start];
}
philipphofmann marked this conversation as resolved.
Show resolved Hide resolved
#else
[SentryLog
logWithMessage:
@"NO UIKit -> SentryFramesTrackingIntegration will not track slow and frozen frames."
andLevel:kSentryLevelInfo];
#endif
}

- (void)uninstall
{
[self stop];
}

- (void)stop
{
#if SENTRY_HAS_UIKIT
if (nil != self.tracker) {
[self.tracker stop];
}
#endif
}

@end

NS_ASSUME_NONNULL_END
12 changes: 9 additions & 3 deletions Sources/Sentry/SentryOptions.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ @implementation SentryOptions
+ (NSArray<NSString *> *)defaultIntegrations
{
return @[
@"SentryCrashIntegration", @"SentryAutoBreadcrumbTrackingIntegration",
@"SentryAutoSessionTrackingIntegration", @"SentryAppStartTrackingIntegration",
@"SentryOutOfMemoryTrackingIntegration", @"SentryPerformanceTrackingIntegration"
@"SentryCrashIntegration", @"SentryFramesTrackingIntegration",
@"SentryAutoBreadcrumbTrackingIntegration", @"SentryAutoSessionTrackingIntegration",
@"SentryAppStartTrackingIntegration", @"SentryOutOfMemoryTrackingIntegration",
@"SentryPerformanceTrackingIntegration"
];
}

Expand All @@ -39,6 +40,7 @@ - (instancetype)init
self.enableAutoSessionTracking = YES;
self.enableOutOfMemoryTracking = YES;
self.enableAppStartMeasuring = YES;
self.enableFrameRenderMeasuring = YES;
self.sessionTrackingIntervalMillis = [@30000 unsignedIntValue];
self.attachStacktrace = YES;
self.maxAttachmentSize = 20 * 1024 * 1024;
Expand Down Expand Up @@ -192,6 +194,10 @@ - (void)validateOptions:(NSDictionary<NSString *, id> *)options
self.enableAppStartMeasuring = [options[@"enableAppStartMeasuring"] boolValue];
}

if (nil != options[@"enableFrameRenderMeasuring"]) {
self.enableFrameRenderMeasuring = [options[@"enableFrameRenderMeasuring"] boolValue];
}

if (nil != options[@"sessionTrackingIntervalMillis"]) {
self.sessionTrackingIntervalMillis =
[options[@"sessionTrackingIntervalMillis"] unsignedIntValue];
Expand Down
53 changes: 52 additions & 1 deletion Sources/Sentry/SentryTracer.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#import "SentryTracer.h"
#import "SentryAppStartMeasurement.h"
#import "SentryFramesTracker.h"
#import "SentryHub.h"
#import "SentryLog.h"
#import "SentrySDK+Private.h"
#import "SentryScope.h"
#import "SentrySpan.h"
Expand Down Expand Up @@ -32,6 +34,14 @@

@implementation SentryTracer {
BOOL _waitForChildren;

#if SENTRY_HAS_UIKIT
BOOL _startTimeChanged;

NSUInteger initTotalFrames;
NSUInteger initSlowFrames;
NSUInteger initFrozenFrames;
#endif
}

- (instancetype)initWithTransactionContext:(SentryTransactionContext *)transactionContext
Expand All @@ -52,6 +62,19 @@ - (instancetype)initWithTransactionContext:(SentryTransactionContext *)transacti
self.isWaitingForChildren = NO;
_waitForChildren = waitForChildren;
self.finishStatus = kSentrySpanStatusUndefined;

#if SENTRY_HAS_UIKIT
_startTimeChanged = NO;

// Store current amount of frames at the beginning to be able to calculate the amount of
// frames at the end of the transaction.
SentryFramesTracker *framesTracker = [SentryFramesTracker sharedInstance];
if (framesTracker.isRunning) {
initTotalFrames = framesTracker.currentTotalFrames;
initSlowFrames = framesTracker.currentSlowFrames;
initFrozenFrames = framesTracker.currentFrozenFrames;
}
#endif
}

return self;
Expand Down Expand Up @@ -139,6 +162,10 @@ - (NSDate *)startTimestamp
- (void)setStartTimestamp:(NSDate *)startTimestamp
{
self.rootSpan.startTimestamp = startTimestamp;

#if SENTRY_HAS_UIKIT
_startTimeChanged = YES;
#endif
}

- (NSDictionary<NSString *, id> *)data
Expand Down Expand Up @@ -313,6 +340,8 @@ - (nullable SentryAppStartMeasurement *)getAppStartMeasurement
- (void)addMeasurements:(SentryTransaction *)transaction
appStartMeasurement:(nullable SentryAppStartMeasurement *)appStartMeasurement
{
NSString *valueKey = @"value";

if (appStartMeasurement != nil && appStartMeasurement.type != SentryAppStartTypeUnknown) {
NSString *type = nil;
if (appStartMeasurement.type == SentryAppStartTypeCold) {
Expand All @@ -322,10 +351,32 @@ - (void)addMeasurements:(SentryTransaction *)transaction
}

if (type != nil) {
[transaction setMeasurementValue:@{ @"value" : @(appStartMeasurement.duration * 1000) }
[transaction setMeasurementValue:@{ valueKey : @(appStartMeasurement.duration * 1000) }
forKey:type];
}
}

#if SENTRY_HAS_UIKIT
// Frames
SentryFramesTracker *framesTracker = [SentryFramesTracker sharedInstance];
if (framesTracker.isRunning && !_startTimeChanged) {
NSInteger totalFrames = framesTracker.currentTotalFrames - initTotalFrames;
NSInteger slowFrames = framesTracker.currentSlowFrames - initSlowFrames;
NSInteger frozenFrames = framesTracker.currentFrozenFrames - initFrozenFrames;
philipphofmann marked this conversation as resolved.
Show resolved Hide resolved

if (totalFrames >= 0 && slowFrames >= 0 && frozenFrames >= 0) {
[transaction setMeasurementValue:@{ valueKey : @(totalFrames) } forKey:@"frames_total"];
[transaction setMeasurementValue:@{ valueKey : @(slowFrames) } forKey:@"frames_slow"];
[transaction setMeasurementValue:@{ valueKey : @(frozenFrames) }
forKey:@"frames_frozen"];

NSString *message = [NSString
stringWithFormat:@"Frames for transaction \"%@\" Total:%ld Slow:%ld Frozen:%ld",
self.context.operation, (long)totalFrames, (long)slowFrames, (long)frozenFrames];
[SentryLog logWithMessage:message andLevel:kSentryLevelDebug];
}
}
#endif
}

- (id<SentrySpan>)buildSpan:(SentrySpanId *)parentId
Expand Down
22 changes: 22 additions & 0 deletions Sources/Sentry/include/SentryDisplayLinkWrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#import "SentryDefines.h"

NS_ASSUME_NONNULL_BEGIN

#if SENTRY_HAS_UIKIT

/**
* A wrapper around DisplayLink for testability.
*/
@interface SentryDisplayLinkWrapper : NSObject

@property (readonly, nonatomic) CFTimeInterval timestamp;

- (void)linkWithTarget:(id)target selector:(SEL)sel;

- (void)invalidate;

@end

#endif

NS_ASSUME_NONNULL_END
Loading