Skip to content

Commit

Permalink
feat: Add thread id and name to span data
Browse files Browse the repository at this point in the history
Add current thread name and thread id to every span
when the SDK initializes the span.

Fixes GH-3355
  • Loading branch information
philipphofmann committed Oct 25, 2023
1 parent ae9c51b commit c1ec4a1
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 18 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

- Add thread id and name to span data (#3359)

## 8.14.2

### Features
Expand Down
2 changes: 1 addition & 1 deletion Sources/Sentry/SentryCoreDataTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ - (void)addExtraInfoToSpan:(SentrySpan *)span withContext:(NSManagedObjectContex
{
BOOL isMainThread = [NSThread isMainThread];

[span setDataValue:@(isMainThread) forKey:BLOCKED_MAIN_THREAD];
[span setDataValue:@(isMainThread) forKey:SPAN_DATA_BLOCKED_MAIN_THREAD];
NSMutableArray<NSString *> *systems = [NSMutableArray<NSString *> array];
NSMutableArray<NSString *> *names = [NSMutableArray<NSString *> array];
[context.persistentStoreCoordinator.persistentStores enumerateObjectsUsingBlock:^(
Expand Down
14 changes: 14 additions & 0 deletions Sources/Sentry/SentryDependencyContainer.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#import "SentryRandom.h"
#import "SentrySysctl.h"
#import "SentrySystemWrapper.h"
#import "SentryThreadInspector.h"
#import "SentryUIDeviceWrapper.h"
#import <SentryAppStateManager.h>
#import <SentryClient+Private.h>
Expand Down Expand Up @@ -132,6 +133,19 @@ - (SentrySysctl *)sysctlWrapper
return _sysctlWrapper;
}

- (SentryThreadInspector *)threadInspector
{
if (_threadInspector == nil) {
@synchronized(sentryDependencyContainerLock) {
if (_threadInspector == nil) {
SentryOptions *options = [[[SentrySDK currentHub] getClient] options];
_threadInspector = [[SentryThreadInspector alloc] initWithOptions:options];
}
}
}
return _threadInspector;
}

- (SentryExtraContextProvider *)extraContextProvider
{
if (_extraContextProvider == nil) {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Sentry/SentryNSDataTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ - (void)mainThreadExtraInfo:(id<SentrySpan>)span
{
BOOL isMainThread = [NSThread isMainThread];

[span setDataValue:@(isMainThread) forKey:BLOCKED_MAIN_THREAD];
[span setDataValue:@(isMainThread) forKey:SPAN_DATA_BLOCKED_MAIN_THREAD];

if (!isMainThread) {
return;
Expand All @@ -210,7 +210,7 @@ - (void)mainThreadExtraInfo:(id<SentrySpan>)span
// and only the 'main' frame remains in the stack
// therefore, there is nothing to do about it
// and we should not report it as an issue.
[span setDataValue:@(NO) forKey:BLOCKED_MAIN_THREAD];
[span setDataValue:@(NO) forKey:SPAN_DATA_BLOCKED_MAIN_THREAD];
} else {
[((SentrySpan *)span) setFrames:frames];
}
Expand Down
15 changes: 15 additions & 0 deletions Sources/Sentry/SentrySpan.m
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
#import "SentrySpan.h"
#import "NSDate+SentryExtras.h"
#import "NSDictionary+SentrySanitize.h"
#import "SentryCrashThread.h"
#import "SentryCurrentDateProvider.h"
#import "SentryDependencyContainer.h"
#import "SentryFrame.h"
#import "SentryId.h"
#import "SentryInternalDefines.h"
#import "SentryLog.h"
#import "SentryMeasurementValue.h"
#import "SentryNoOpSpan.h"
#import "SentrySampleDecision+Private.h"
#import "SentrySerializable.h"
#import "SentrySpanContext.h"
#import "SentrySpanId.h"
#import "SentryThreadInspector.h"
#import "SentryTime.h"
#import "SentryTraceHeader.h"
#import "SentryTracer.h"
Expand All @@ -33,6 +36,18 @@ - (instancetype)initWithContext:(SentrySpanContext *)context
if (self = [super init]) {
self.startTimestamp = [SentryDependencyContainer.sharedInstance.dateProvider date];
_data = [[NSMutableDictionary alloc] init];

SentryCrashThread currentThread = sentrycrashthread_self();
_data[SPAN_DATA_THREAD_ID] = @(currentThread);

if ([NSThread isMainThread]) {
_data[SPAN_DATA_THREAD_NAME] = @"main";
} else {
NSString *threadName = [SentryDependencyContainer.sharedInstance.threadInspector
getThreadName:currentThread];
_data[SPAN_DATA_THREAD_NAME] = threadName;
}

_tags = [[NSMutableDictionary alloc] init];
_isFinished = NO;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
@class SentrySysctl;
@class SentrySystemWrapper;
@class SentryThreadWrapper;
@class SentryThreadInspector;
@protocol SentryRandom;

#if SENTRY_HAS_METRIC_KIT
Expand Down Expand Up @@ -68,6 +69,7 @@ SENTRY_NO_INIT
@property (nonatomic, strong) SentryBinaryImageCache *binaryImageCache;
@property (nonatomic, strong) SentryExtraContextProvider *extraContextProvider;
@property (nonatomic, strong) SentrySysctl *sysctlWrapper;
@property (nonatomic, strong) SentryThreadInspector *threadInspector;

#if SENTRY_UIKIT_AVAILABLE
@property (nonatomic, strong) SentryFramesTracker *framesTracker;
Expand Down
4 changes: 3 additions & 1 deletion Sources/Sentry/include/SentryInternalDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,6 @@ static NSString *const SentryPlatformName = @"cocoa";
(__cond_result); \
})

#define BLOCKED_MAIN_THREAD @"blocked_main_thread"
#define SPAN_DATA_BLOCKED_MAIN_THREAD @"blocked_main_thread"
#define SPAN_DATA_THREAD_ID @"thread.id"
#define SPAN_DATA_THREAD_NAME @"thread.name"
2 changes: 2 additions & 0 deletions Sources/Sentry/include/SentryThreadInspector.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ SENTRY_NO_INIT
*/
- (NSArray<SentryThread *> *)getCurrentThreadsWithStackTrace;

- (NSString *)getThreadName:(SentryCrashThread)thread;

@end

NS_ASSUME_NONNULL_END
50 changes: 41 additions & 9 deletions Tests/SentryTests/Transaction/SentrySpanTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@ class SentrySpanTests: XCTestCase {
XCTAssertFalse(span.isFinished)
}

func testInit_SetsMainThreadInfoAsSpanData() {
let span = fixture.getSut()
XCTAssertEqual("main", span.data["thread.name"] as! String)

let threadId = sentrycrashthread_self()
XCTAssertEqual(NSNumber(value: threadId), span.data["thread.id"] as! NSNumber)
}

func testInit_SetsThreadInfoAsSpanData_FromBackGroundThread() {
let expect = expectation(description: "Thread must be called.")

Thread.detachNewThread {
let threadName = "test-thread-name"
Thread.current.name = threadName

let span = self.fixture.getSut()
XCTAssertEqual(threadName, span.data["thread.name"] as! String)
let threadId = sentrycrashthread_self()
XCTAssertEqual(NSNumber(value: threadId), span.data["thread.id"] as! NSNumber)

expect.fulfill()
}

wait(for: [expect], timeout: 0.1)
}

func testFinish() {
let client = TestClient(options: fixture.options)!
let span = fixture.getSut(client: client)
Expand Down Expand Up @@ -191,11 +217,11 @@ class SentrySpanTests: XCTestCase {

span.setData(value: fixture.extraValue, key: fixture.extraKey)

XCTAssertEqual(span.data.count, 1)
XCTAssertEqual(span.data.count, 3)
XCTAssertEqual(span.data[fixture.extraKey] as! String, fixture.extraValue)

span.removeData(key: fixture.extraKey)
XCTAssertEqual(span.data.count, 0)
XCTAssertEqual(span.data.count, 2, "Only expected thread.name and thread.id in data.")
XCTAssertNil(span.data[fixture.extraKey])
}

Expand Down Expand Up @@ -237,7 +263,9 @@ class SentrySpanTests: XCTestCase {
XCTAssertEqual(serialization["sampled"] as? NSNumber, true)
XCTAssertNotNil(serialization["data"])
XCTAssertNotNil(serialization["tags"])
XCTAssertEqual((serialization["data"] as! Dictionary)[fixture.extraKey], fixture.extraValue)

let data = serialization["data"] as? [String: Any]
XCTAssertEqual(data?[fixture.extraKey] as! String, fixture.extraValue)
XCTAssertEqual((serialization["tags"] as! Dictionary)[fixture.extraKey], fixture.extraValue)
XCTAssertEqual("manual", serialization["origin"] as? String)
}
Expand All @@ -246,7 +274,7 @@ class SentrySpanTests: XCTestCase {
let span = SentrySpan(tracer: fixture.tracer, context: SpanContext(operation: "test"))
let serialization = span.serialize()

XCTAssertNil(serialization["data"])
XCTAssertEqual(2, (serialization["data"] as? [String: Any])?.count, "Only expected thread.name and thread.id in data.")
}

func testSerialization_withFrames() {
Expand All @@ -269,7 +297,8 @@ class SentrySpanTests: XCTestCase {
span.finish()

let serialization = span.serialize()
XCTAssertEqual((serialization["data"] as! Dictionary)["date"], "1970-01-01T00:00:10.000Z")
let data = serialization["data"] as? [String: Any]
XCTAssertEqual(data?["date"] as? String, "1970-01-01T00:00:10.000Z")
}

func testSanitizeDataSpan() {
Expand All @@ -279,14 +308,15 @@ class SentrySpanTests: XCTestCase {
span.finish()

let serialization = span.serialize()
XCTAssertEqual((serialization["data"] as! Dictionary)["date"], "1970-01-01T00:00:10.000Z")
let data = serialization["data"] as? [String: Any]
XCTAssertEqual(data?["date"] as? String, "1970-01-01T00:00:10.000Z")
}

func testSerialization_WithNoDataAndTag() {
let span = fixture.getSut()

let serialization = span.serialize()
XCTAssertNil(serialization["data"])
XCTAssertEqual(2, (serialization["data"] as? [String: Any])?.count, "Only expected thread.name and thread.id in data.")
XCTAssertNil(serialization["tag"])
}

Expand Down Expand Up @@ -327,7 +357,8 @@ class SentrySpanTests: XCTestCase {
let sut = SentrySpan(tracer: fixture.tracer, context: SpanContext(operation: "test"))
sut.setExtra(value: 0, key: "key")

XCTAssertEqual(["key": 0], sut.data as! [String: Int])
let data = sut.data as [String: Any]
XCTAssertEqual(0, data["key"] as? Int)
}

func testSpanWithoutTracer_StartChild_ReturnsNoOpSpan() {
Expand Down Expand Up @@ -374,7 +405,8 @@ class SentrySpanTests: XCTestCase {

queue.activate()
group.wait()
XCTAssertEqual(span.data.count, outerLoop * innerLoop)
let threadDataItemCount = 2
XCTAssertEqual(span.data.count, outerLoop * innerLoop + threadDataItemCount)
}

func testSpanStatusNames() {
Expand Down
3 changes: 2 additions & 1 deletion Tests/SentryTests/Transaction/SentryTracerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1097,7 +1097,8 @@ class SentryTracerTests: XCTestCase {
let sut = fixture.getSut()
sut.setExtra(value: 0, key: "key")

XCTAssertEqual(["key": 0], sut.data as! [String: Int])
let data = sut.data as [String: Any]
XCTAssertEqual(0, data["key"] as? Int)
}

private func advanceTime(bySeconds: TimeInterval) {
Expand Down
8 changes: 4 additions & 4 deletions Tests/SentryTests/Transaction/SentryTransactionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ class SentryTransactionTests: XCTestCase {

// when
let serializedTransaction = sut.serialize()
let serializedTransactionExtra = try! XCTUnwrap(serializedTransaction["extra"] as? [String: String])
let serializedTransactionExtra = try! XCTUnwrap(serializedTransaction["extra"] as? [String: Any])

// then
XCTAssertEqual(serializedTransactionExtra, [fixture.testKey: fixture.testValue])
XCTAssertEqual(serializedTransactionExtra[fixture.testKey] as! String, fixture.testValue)
}

func testSerialize_shouldPreserveExtraFromScope() {
Expand All @@ -156,10 +156,10 @@ class SentryTransactionTests: XCTestCase {

// when
let serializedTransaction = sut.serialize()
let serializedTransactionExtra = try! XCTUnwrap(serializedTransaction["extra"] as? [String: String])
let serializedTransactionExtra = try! XCTUnwrap(serializedTransaction["extra"] as? [String: Any])

// then
XCTAssertEqual(serializedTransactionExtra, [fixture.testKey: fixture.testValue])
XCTAssertEqual(serializedTransactionExtra[fixture.testKey] as! String, fixture.testValue)
}

func testSerializeOrigin() throws {
Expand Down

0 comments on commit c1ec4a1

Please sign in to comment.