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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add extra app start span #1952

Merged
merged 16 commits into from
Jul 12, 2022
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Features

- feat: Add extra app start span (#1952)
- Add enableAutoBreadcrumbTracking option (#1958)

### Fixes
Expand Down Expand Up @@ -40,7 +41,7 @@
## 7.18.0

### Features

- Replace tracestate header with baggage (#1867)

### Fixes
Expand Down
1 change: 1 addition & 0 deletions Samples/tvOS-Swift/tvOS-Swift/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ struct ContentView: View {
Button(action: captureMessageAction) {
Text("Capture Message")
}
.accessibility(identifier: "captureMessageButton")

Button(action: captureUserFeedbackAction) {
Text("Capture User Feedback")
Expand Down
20 changes: 18 additions & 2 deletions Samples/tvOS-Swift/tvOS-SwiftUITests/LaunchUITests.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import XCTest

class LaunchUITests: XCTestCase {

private let app: XCUIApplication = XCUIApplication()

override func setUpWithError() throws {
try super.setUpWithError()
continueAfterFailure = false

app.launch()
}

override func tearDown() {
app.terminate()
super.tearDown()
}

func testLaunch() throws {
let app = XCUIApplication()
app.launch()
XCTAssertTrue(app.buttons["captureMessageButton"].waitForExistence(), "Home Screen doesn't exist.")
}
}

extension XCUIElement {

@discardableResult
func waitForExistence() -> Bool {
self.waitForExistence(timeout: TimeInterval(10))
}
}
18 changes: 18 additions & 0 deletions Sources/Sentry/Public/SentryAppStartMeasurement.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ SENTRY_NO_INIT
appStartTimestamp:(NSDate *)appStartTimestamp
duration:(NSTimeInterval)duration
runtimeInitTimestamp:(NSDate *)runtimeInitTimestamp
didFinishLaunchingTimestamp:(NSDate *)didFinishLaunchingTimestamp
DEPRECATED_MSG_ATTRIBUTE("Use "
"initWithType:appStartTimestamp:duration:mainTimestamp:"
"runtimeInitTimestamp:didFinishLaunchingTimestamp instead.");

/**
* Initializes SentryAppStartMeasurement with the given parameters.
*/
- (instancetype)initWithType:(SentryAppStartType)type
appStartTimestamp:(NSDate *)appStartTimestamp
duration:(NSTimeInterval)duration
runtimeInitTimestamp:(NSDate *)runtimeInitTimestamp
mainTimestamp:(NSDate *)mainTimestamp
didFinishLaunchingTimestamp:(NSDate *)didFinishLaunchingTimestamp;

/**
Expand All @@ -41,6 +54,11 @@ SENTRY_NO_INIT
*/
@property (readonly, nonatomic, strong) NSDate *runtimeInitTimestamp;

/**
* When application main function is called.
*/
@property (readonly, nonatomic, strong) NSDate *mainTimestamp;

/**
* When OS posts UIApplicationDidFinishLaunchingNotification.
*/
Expand Down
47 changes: 47 additions & 0 deletions Sources/Sentry/SentryAppStartMeasurement.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import "SentryAppStartMeasurement.h"
#import "NSDate+SentryExtras.h"
#import <Foundation/Foundation.h>

@implementation SentryAppStartMeasurement
Expand All @@ -8,16 +9,62 @@ - (instancetype)initWithType:(SentryAppStartType)type
duration:(NSTimeInterval)duration
runtimeInitTimestamp:(NSDate *)runtimeInitTimestamp
didFinishLaunchingTimestamp:(NSDate *)didFinishLaunchingTimestamp
{
return [self initWithType:type
appStartTimestamp:appStartTimestamp
duration:duration
runtimeInitTimestamp:runtimeInitTimestamp
mainTimestamp:[NSDate dateWithTimeIntervalSince1970:0]
didFinishLaunchingTimestamp:didFinishLaunchingTimestamp];
}

- (instancetype)initWithType:(SentryAppStartType)type
appStartTimestamp:(NSDate *)appStartTimestamp
duration:(NSTimeInterval)duration
runtimeInitTimestamp:(NSDate *)runtimeInitTimestamp
mainTimestamp:(NSDate *)mainTimestamp
didFinishLaunchingTimestamp:(NSDate *)didFinishLaunchingTimestamp
{
if (self = [super init]) {
_type = type;
_appStartTimestamp = appStartTimestamp;
_duration = duration;
_runtimeInitTimestamp = runtimeInitTimestamp;
_mainTimestamp = mainTimestamp;
_didFinishLaunchingTimestamp = didFinishLaunchingTimestamp;
}

return self;
}

- (NSDictionary<NSString *, id> *)serialize
{
NSMutableDictionary *serializedData = [NSMutableDictionary new];

switch (self.type) {
case SentryAppStartTypeCold:
serializedData[@"type"] = @"Cold Start";
break;
case SentryAppStartTypeWarm:
serializedData[@"type"] = @"Warm Start";
break;
default:
serializedData[@"type"] = @"Unknown Start";
}

serializedData[@"appStartTimestamp"] = [self.appStartTimestamp sentry_toIso8601String];
serializedData[@"duration"] = @(self.duration);
serializedData[@"mainTimestamp"] = [self.mainTimestamp sentry_toIso8601String];
serializedData[@"runtimeInitTimestamp"] = [self.runtimeInitTimestamp sentry_toIso8601String];
serializedData[@"didFinishLaunchingTimestamp"] =
[self.didFinishLaunchingTimestamp sentry_toIso8601String];

return serializedData;
}

- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p, %@>", [self class], self, [self serialize]];
}

@end
1 change: 1 addition & 0 deletions Sources/Sentry/SentryAppStartTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ - (void)buildAppStartMeasurement
appStartTimestamp:self.sysctl.processStartTimestamp
duration:appStartDuration
runtimeInitTimestamp:runtimeInit
mainTimestamp:self.sysctl.mainTimestamp
didFinishLaunchingTimestamp:self.didFinishLaunchingTimestamp];

SentrySDK.appStartMeasurement = appStartMeasurement;
Expand Down
31 changes: 31 additions & 0 deletions Sources/Sentry/SentrySysctl.m
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
#import "SentrySysctl.h"
#import "SentryCrashSysCtl.h"
#include <stdio.h>
#include <time.h>

static double mod_init_time_in_seconds;
static NSDate *runtimeInit = nil;

void
sentry_mod_init_hook(int argc, char **argv, char **envp)
{
struct timeval value;
mod_init_time_in_seconds
= (gettimeofday(&value, NULL) == 0) ? (uint64_t)value.tv_sec + value.tv_usec / 1E6 : 0;
}
__attribute__((section("__DATA,__mod_init_func"))) typeof(sentry_mod_init_hook) *__init
= sentry_mod_init_hook;

@implementation SentrySysctl

+ (void)load
{
// Invoked whenever this class is added to the Objective-C runtime.
runtimeInit = [NSDate date];
}

- (NSDate *)runtimeInitTimestamp
{
return runtimeInit;
}

- (NSDate *)systemBootTimestamp
{
struct timeval value = sentrycrashsysctl_timeval(CTL_KERN, KERN_BOOTTIME);
Expand All @@ -15,4 +41,9 @@ - (NSDate *)processStartTimestamp
return [NSDate dateWithTimeIntervalSince1970:startTime.tv_sec + startTime.tv_usec / 1E6];
}

- (NSDate *)mainTimestamp
{
return [NSDate dateWithTimeIntervalSince1970:mod_init_time_in_seconds];
}

@end
19 changes: 14 additions & 5 deletions Sources/Sentry/SentryTracer.m
Original file line number Diff line number Diff line change
Expand Up @@ -588,16 +588,22 @@ - (nullable SentryAppStartMeasurement *)getAppStartMeasurement
description:type];
[appStartSpan setStartTimestamp:appStartMeasurement.appStartTimestamp];

SentrySpan *premainSpan = [self buildSpan:appStartSpan.context.spanId
operation:operation
description:@"Pre Runtime Init"];
[premainSpan setStartTimestamp:appStartMeasurement.appStartTimestamp];
[premainSpan setTimestamp:appStartMeasurement.runtimeInitTimestamp];

SentrySpan *runtimeInitSpan = [self buildSpan:appStartSpan.context.spanId
operation:operation
description:@"Pre main"];
[runtimeInitSpan setStartTimestamp:appStartMeasurement.appStartTimestamp];
[runtimeInitSpan setTimestamp:appStartMeasurement.runtimeInitTimestamp];
description:@"Runtime Init to Main"];
[runtimeInitSpan setStartTimestamp:appStartMeasurement.runtimeInitTimestamp];
[runtimeInitSpan setTimestamp:appStartMeasurement.mainTimestamp];

SentrySpan *appInitSpan = [self buildSpan:appStartSpan.context.spanId
operation:operation
description:@"UIKit and Application Init"];
[appInitSpan setStartTimestamp:appStartMeasurement.runtimeInitTimestamp];
[appInitSpan setStartTimestamp:appStartMeasurement.mainTimestamp];
[appInitSpan setTimestamp:appStartMeasurement.didFinishLaunchingTimestamp];

SentrySpan *frameRenderSpan = [self buildSpan:appStartSpan.context.spanId
Expand All @@ -608,7 +614,10 @@ - (nullable SentryAppStartMeasurement *)getAppStartMeasurement

[appStartSpan setTimestamp:appStartEndTimestamp];

return @[ appStartSpan, runtimeInitSpan, appInitSpan, frameRenderSpan ];
NSString *message = [NSString stringWithFormat:@"Phil: %@", appStartMeasurement.description];
[SentryLog logWithMessage:message andLevel:kSentryLevelInfo];
philipphofmann marked this conversation as resolved.
Show resolved Hide resolved

return @[ appStartSpan, premainSpan, runtimeInitSpan, appInitSpan, frameRenderSpan ];
}

- (void)addMeasurements:(SentryTransaction *)transaction
Expand Down
4 changes: 4 additions & 0 deletions Sources/Sentry/include/SentrySysctl.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ NS_ASSUME_NONNULL_BEGIN

@property (readonly) NSDate *processStartTimestamp;

@property (readonly) NSDate *runtimeInitTimestamp;

@property (readonly) NSDate *mainTimestamp;

@end

NS_ASSUME_NONNULL_END
30 changes: 30 additions & 0 deletions Tests/SentryTests/Helper/SentrySysctlTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,34 @@ class SentrySysctlTests: XCTestCase {

XCTAssertGreaterThan(distance, 0)
}

func testMainTimestamp_IsInThePast() {
let distance = Date().timeIntervalSince(sut.mainTimestamp)

XCTAssertGreaterThan(distance, 0)
}

func testMainTimestamp_IsBiggerThan_ProcessStartTime() {
let distance = sut.mainTimestamp.timeIntervalSince(sut.processStartTimestamp)

XCTAssertGreaterThan(distance, 0)
}

func testMainTimestamp_IsBiggerThan_RuntimeInitTimestamp() {
let distance = sut.mainTimestamp.timeIntervalSince(sut.runtimeInitTimestamp)

XCTAssertGreaterThan(distance, 0)
}

func testRuntimeInitTimestamp_IsBiggerThan_ProcessStartTimestamp() {
let distance = sut.runtimeInitTimestamp.timeIntervalSince(sut.processStartTimestamp)

XCTAssertGreaterThan(distance, 0)
}

func testRuntimeInitTimestamp_IsInThePast() {
let distance = Date().timeIntervalSince(sut.runtimeInitTimestamp)

XCTAssertGreaterThan(distance, 0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,15 @@ class SentryAppStartTrackerTests: XCTestCase {

startApp()

#if os(iOS)
#if os(iOS)
if #available(iOS 14.0, *) {
assertNoAppStartUp()
} else {
assertValidStart(type: .warm)
}
#else
#else
assertValidStart(type: .warm)
#endif
#endif
}

func testAppLaunches_WrongEnvValue_AppStartUp() {
Expand Down Expand Up @@ -317,20 +317,21 @@ class SentryAppStartTrackerTests: XCTestCase {
private func sendAppMeasurement() {
SentrySDK.setAppStartMeasurement(nil)
}

private func assertValidStart(type: SentryAppStartType, expectedDuration: TimeInterval? = nil) {
guard let appStartMeasurement = SentrySDK.getAppStartMeasurement() else {
XCTFail("AppStartMeasurement must not be nil")
return
}

XCTAssertEqual(type.rawValue, appStartMeasurement.type.rawValue)

let expectedAppStartDuration = expectedDuration ?? fixture.appStartDuration
let actualAppStartDuration = appStartMeasurement.duration
XCTAssertEqual(expectedAppStartDuration, actualAppStartDuration, accuracy: 0.000_1)

XCTAssertEqual(fixture.sysctl.processStartTimestamp, appStartMeasurement.appStartTimestamp)
XCTAssertEqual(fixture.sysctl.mainTimestamp, appStartMeasurement.mainTimestamp)
XCTAssertEqual(fixture.runtimeInitTimestamp, appStartMeasurement.runtimeInitTimestamp)
XCTAssertEqual(fixture.didFinishLaunchingTimestamp, appStartMeasurement.didFinishLaunchingTimestamp)
}
Expand All @@ -342,15 +343,15 @@ class SentryAppStartTrackerTests: XCTestCase {
}

XCTAssertEqual(type.rawValue, appStartMeasurement.type.rawValue)

let actualAppStartDuration = appStartMeasurement.duration
XCTAssertEqual(0.0, actualAppStartDuration, accuracy: 0.000_1)

XCTAssertEqual(fixture.sysctl.processStartTimestamp, appStartMeasurement.appStartTimestamp)
XCTAssertEqual(fixture.runtimeInitTimestamp, appStartMeasurement.runtimeInitTimestamp)
XCTAssertEqual(Date(timeIntervalSinceReferenceDate: 0), appStartMeasurement.didFinishLaunchingTimestamp)
}

private func assertNoAppStartUp() {
XCTAssertNil(SentrySDK.getAppStartMeasurement())
}
Expand Down