Skip to content

Commit

Permalink
feat: Add extra app start span (#1952)
Browse files Browse the repository at this point in the history
Add __mod_init_func hook which gets called before main. With
this info we can add one more app start span.
  • Loading branch information
philipphofmann committed Jul 12, 2022
1 parent db38c00 commit 252a8c2
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 79 deletions.
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))
}
}
20 changes: 19 additions & 1 deletion Sources/Sentry/Public/SentryAppStartMeasurement.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,20 @@ SENTRY_NO_INIT
appStartTimestamp:(NSDate *)appStartTimestamp
duration:(NSTimeInterval)duration
runtimeInitTimestamp:(NSDate *)runtimeInitTimestamp
didFinishLaunchingTimestamp:(NSDate *)didFinishLaunchingTimestamp;
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
moduleInitializationTimestamp:(NSDate *)moduleInitializationTimestamp
didFinishLaunchingTimestamp:(NSDate *)didFinishLaunchingTimestamp;

/**
* The type of the app start.
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 *moduleInitializationTimestamp;

/**
* When OS posts UIApplicationDidFinishLaunchingNotification.
*/
Expand Down
17 changes: 17 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,12 +9,28 @@ - (instancetype)initWithType:(SentryAppStartType)type
duration:(NSTimeInterval)duration
runtimeInitTimestamp:(NSDate *)runtimeInitTimestamp
didFinishLaunchingTimestamp:(NSDate *)didFinishLaunchingTimestamp
{
return [self initWithType:type
appStartTimestamp:appStartTimestamp
duration:duration
runtimeInitTimestamp:runtimeInitTimestamp
moduleInitializationTimestamp:[NSDate dateWithTimeIntervalSince1970:0]
didFinishLaunchingTimestamp:didFinishLaunchingTimestamp];
}

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

Expand Down
13 changes: 7 additions & 6 deletions Sources/Sentry/SentryAppStartTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,13 @@ - (void)buildAppStartMeasurement
appStartDuration = 0;
}

SentryAppStartMeasurement *appStartMeasurement =
[[SentryAppStartMeasurement alloc] initWithType:appStartType
appStartTimestamp:self.sysctl.processStartTimestamp
duration:appStartDuration
runtimeInitTimestamp:runtimeInit
didFinishLaunchingTimestamp:self.didFinishLaunchingTimestamp];
SentryAppStartMeasurement *appStartMeasurement = [[SentryAppStartMeasurement alloc]
initWithType:appStartType
appStartTimestamp:self.sysctl.processStartTimestamp
duration:appStartDuration
runtimeInitTimestamp:runtimeInit
moduleInitializationTimestamp:self.sysctl.moduleInitializationTimestamp
didFinishLaunchingTimestamp:self.didFinishLaunchingTimestamp];

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

static NSDate *moduleInitializationTimestamp;
static NSDate *runtimeInit = nil;

void
sentryModuleInitializationHook(int argc, char **argv, char **envp)
{
moduleInitializationTimestamp = [NSDate date];
}
/**
* Module initialization functions. The C++ compiler places static constructors here. For more info
* visit:
* https://github.com/aidansteele/osx-abi-macho-file-format-reference#table-2-the-sections-of-a__datasegment
*/
__attribute__((section("__DATA,__mod_init_func"))) typeof(sentryModuleInitializationHook) *__init
= sentryModuleInitializationHook;

@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 +44,9 @@ - (NSDate *)processStartTimestamp
return [NSDate dateWithTimeIntervalSince1970:startTime.tv_sec + startTime.tv_usec / 1E6];
}

- (NSDate *)moduleInitializationTimestamp
{
return moduleInitializationTimestamp;
}

@end
16 changes: 11 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 Pre Main Initializers"];
[runtimeInitSpan setStartTimestamp:appStartMeasurement.runtimeInitTimestamp];
[runtimeInitSpan setTimestamp:appStartMeasurement.moduleInitializationTimestamp];

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

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

[appStartSpan setTimestamp:appStartEndTimestamp];

return @[ appStartSpan, runtimeInitSpan, appInitSpan, frameRenderSpan ];
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 *moduleInitializationTimestamp;

@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.moduleInitializationTimestamp)

XCTAssertGreaterThan(distance, 0)
}

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

XCTAssertGreaterThan(distance, 0)
}

func testMainTimestamp_IsBiggerThan_RuntimeInitTimestamp() {
let distance = sut.moduleInitializationTimestamp.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.moduleInitializationTimestamp, appStartMeasurement.moduleInitializationTimestamp)
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

0 comments on commit 252a8c2

Please sign in to comment.