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: Automatic nest new spans with the ui life cycle function #1959

Merged
merged 13 commits into from
Jul 14, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Features

- Add enableAutoBreadcrumbTracking option (#1958)
- Automatic nest new spans with the ui life cycle function (#1959)
brustolin marked this conversation as resolved.
Show resolved Hide resolved

### Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,4 @@ class LoremIpsumViewController: UIViewController {
}
}
}

}
13 changes: 12 additions & 1 deletion Sources/Sentry/SentryPerformanceTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
NS_ASSUME_NONNULL_BEGIN

@interface
SentryPerformanceTracker ()
SentryPerformanceTracker () <SentryTracerDelegate>

@property (nonatomic, strong) NSMutableDictionary<SentrySpanId *, id<SentrySpan>> *spans;
@property (nonatomic, strong) NSMutableArray<id<SentrySpan>> *activeSpanStack;
Expand Down Expand Up @@ -67,6 +67,10 @@ - (SentrySpanId *)startSpanWithName:(NSString *)name operation:(NSString *)opera
bindToScope:bindToScope
waitForChildren:YES
customSamplingContext:@ {}];

if ([newSpan isKindOfClass:[SentryTracer class]]) {
[(SentryTracer *)newSpan setDelegate:self];
}
}];
}

Expand Down Expand Up @@ -179,6 +183,13 @@ - (BOOL)isSpanAlive:(SentrySpanId *)spanId
}
}

- (nullable id<SentrySpan>)activeSpanForTracer:(SentryTracer *)tracer
{
@synchronized(self.activeSpanStack) {
return [self.activeSpanStack lastObject];
}
}

@end

NS_ASSUME_NONNULL_END
33 changes: 28 additions & 5 deletions Sources/Sentry/SentryTracer.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
SentryTracer ()

@property (nonatomic, strong) SentrySpan *rootSpan;
@property (nonatomic, strong) NSMutableArray<id<SentrySpan>> *children;
@property (nonatomic, strong) SentryHub *hub;
@property (nonatomic) SentrySpanStatus finishStatus;
@property (nonatomic) BOOL isWaitingForChildren;
Expand All @@ -52,6 +51,7 @@ @implementation SentryTracer {
NSMutableDictionary<NSString *, id> *_tags;
NSMutableDictionary<NSString *, id> *_data;
dispatch_block_t _idleTimeoutBlock;
NSMutableArray<id<SentrySpan>> *_children;

#if SENTRY_HAS_UIKIT
BOOL _startTimeChanged;
Expand Down Expand Up @@ -120,7 +120,7 @@ - (instancetype)initWithTransactionContext:(SentryTransactionContext *)transacti
if (self = [super init]) {
self.rootSpan = [[SentrySpan alloc] initWithTransaction:self context:transactionContext];
self.name = transactionContext.name;
self.children = [[NSMutableArray alloc] init];
_children = [[NSMutableArray alloc] init];
self.hub = hub;
self.isWaitingForChildren = NO;
_waitForChildren = waitForChildren;
Expand Down Expand Up @@ -194,15 +194,33 @@ - (void)cancelIdleTimeout
}
}

- (id<SentrySpan>)getActiveSpan
{
id<SentrySpan> span;

if (self.delegate) {
@synchronized(_children) {
span = [self.delegate activeSpanForTracer:self];
if (span == nil || span == self || ![_children containsObject:span]) {
span = _rootSpan;
}
}
} else {
span = _rootSpan;
}

return span;
}

- (id<SentrySpan>)startChildWithOperation:(NSString *)operation
{
return [_rootSpan startChildWithOperation:operation];
return [[self getActiveSpan] startChildWithOperation:operation];
}

- (id<SentrySpan>)startChildWithOperation:(NSString *)operation
description:(nullable NSString *)description
{
return [_rootSpan startChildWithOperation:operation description:description];
return [[self getActiveSpan] startChildWithOperation:operation description:description];
}

- (id<SentrySpan>)startChildWithParentId:(SentrySpanId *)parentId
Expand All @@ -220,7 +238,7 @@ - (void)cancelIdleTimeout
context.spanDescription = description;

SentrySpan *child = [[SentrySpan alloc] initWithTransaction:self context:context];
@synchronized(self.children) {
@synchronized(_children) {
[_children addObject:child];
}

Expand Down Expand Up @@ -298,6 +316,11 @@ - (BOOL)isFinished
return self.rootSpan.isFinished;
}

- (NSArray<id<SentrySpan>> *)children
{
return [_children copy];
}

- (void)setDataValue:(nullable id)value forKey:(NSString *)key
{
@synchronized(_data) {
Expand Down
17 changes: 16 additions & 1 deletion Sources/Sentry/include/SentryTracer.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,20 @@
NS_ASSUME_NONNULL_BEGIN

@class SentryHub, SentryTransactionContext, SentryTraceHeader, SentryTraceContext,
SentryDispatchQueueWrapper;
SentryDispatchQueueWrapper, SentryTracer;

static NSTimeInterval const SentryTracerDefaultTimeout = 3.0;

@protocol SentryTracerDelegate

/**
* Return the active span of given tracer.
* This function is used to determine which span will be used to create a new child.
*/
- (nullable id<SentrySpan>)activeSpanForTracer:(SentryTracer *)tracer;

@end

@interface SentryTracer : NSObject <SentrySpan>

/**
Expand Down Expand Up @@ -59,6 +69,11 @@ static NSTimeInterval const SentryTracerDefaultTimeout = 3.0;
*/
@property (nonatomic, readonly) NSArray<id<SentrySpan>> *children;

/*
* A delegate that provides extra information for the transaction.
*/
@property (nullable, nonatomic, weak) id<SentryTracerDelegate> delegate;

/**
* Init a SentryTracer with given transaction context and hub and set other fields by default
*
Expand Down
56 changes: 56 additions & 0 deletions Tests/SentryTests/Performance/SentryTracerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import XCTest

class SentryTracerTests: XCTestCase {

private class TracerDelegate: SentryTracerDelegate {

var activeSpan: Span?

func activeSpan(for tracer: SentryTracer) -> Span? {
return activeSpan
}
}

private class Fixture {
let client: TestClient
let hub: TestHub
Expand Down Expand Up @@ -371,6 +380,53 @@ class SentryTracerTests: XCTestCase {
assertAppStartsSpanAdded(transaction: transaction, startType: "Cold Start", operation: fixture.appStartColdOperation, appStartMeasurement: appStartMeasurement)
}

func test_startChildWithDelegate() {
let delegate = TracerDelegate()

let sut = fixture.getSut()
sut.delegate = delegate

let child = sut.startChild(operation: fixture.transactionOperation)

delegate.activeSpan = child

let secondChild = sut.startChild(operation: fixture.transactionOperation)

XCTAssertEqual(secondChild.context.parentSpanId, child.context.spanId)
}

func test_startChildWithDelegate_ActiveNotChild() {
let delegate = TracerDelegate()

let sut = fixture.getSut()
sut.delegate = delegate

delegate.activeSpan = SentryTracer(transactionContext: TransactionContext(name: fixture.transactionName, operation: fixture.transactionOperation), hub: nil)

let child = sut.startChild(operation: fixture.transactionOperation)

let secondChild = sut.startChild(operation: fixture.transactionOperation)

XCTAssertEqual(secondChild.context.parentSpanId, sut.context.spanId)
XCTAssertEqual(secondChild.context.parentSpanId, child.context.parentSpanId)
}

func test_startChildWithDelegate_SelfIsActive() {
let delegate = TracerDelegate()

let sut = fixture.getSut()
sut.delegate = delegate

delegate.activeSpan = sut

let child = sut.startChild(operation: fixture.transactionOperation)

let secondChild = sut.startChild(operation: fixture.transactionOperation)

XCTAssertEqual(secondChild.context.parentSpanId, sut.context.spanId)
XCTAssertEqual(secondChild.context.parentSpanId, child.context.parentSpanId)
}

func testAddWarmAppStartMeasurement_PutOnNextAutoUITransaction() {
let appStartMeasurement = fixture.getAppStartMeasurement(type: .warm)
SentrySDK.setAppStartMeasurement(appStartMeasurement)
Expand Down