Skip to content

Commit

Permalink
feat: Add thread id and name to span data (#3359)
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 authored Oct 27, 2023
1 parent ae9c51b commit 2124551
Show file tree
Hide file tree
Showing 11 changed files with 111 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
17 changes: 17 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,20 @@ - (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];
if (threadName.length > 0) {
_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
67 changes: 58 additions & 9 deletions Tests/SentryTests/Transaction/SentrySpanTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,49 @@ 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 testInit_SetsThreadInfoAsSpanData_FromBackgroundThreadWithNoName() {
let expect = expectation(description: "Thread must be called.")

Thread.detachNewThread {
Thread.current.name = ""

let span = self.fixture.getSut()
XCTAssertNil(span.data["thread.name"])
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 +234,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 +280,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 +291,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 +314,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 +325,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 +374,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 +422,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 2124551

Please sign in to comment.