Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
696ba61
todo
vaind Aug 17, 2023
4f85144
feat: expose cocoa profiling via method channels
vaind Aug 18, 2023
de6f244
feat: prepare profiler interfaces and hub integration
vaind Aug 22, 2023
7c73348
fix CI
vaind Aug 23, 2023
42af467
integrate dart & cocoa profiling
vaind Aug 24, 2023
162c2ba
fix API breakage
vaind Aug 26, 2023
31a92e5
fix tests
vaind Aug 28, 2023
b12978e
dart format
vaind Aug 28, 2023
3582adf
ci: fix pana
vaind Aug 29, 2023
1d74bac
update tests
vaind Aug 29, 2023
8d23d3f
analysis issues
vaind Aug 29, 2023
c9be10c
update to the latest cocoa SDK API
vaind Sep 5, 2023
32c6e5d
linter issue
vaind Sep 7, 2023
0ae79d4
fix import
vaind Sep 12, 2023
df9fb5b
refactor: SentryNative integration
vaind Sep 12, 2023
0fb649c
update cocoa native binding to use ffi startProfiler()
vaind Sep 12, 2023
dccb853
tmp: findPrimeNumber in example
vaind Sep 12, 2023
6119812
fix: make FFI dependency conditional (web/vm)
vaind Sep 18, 2023
5218efa
exclude generated binding code from code coverage
vaind Sep 18, 2023
4c8f2bf
test: profiler integration test
vaind Sep 18, 2023
e0bb3ab
workaround for the integration test issue
vaind Sep 18, 2023
8a4fed7
chore: formatting
vaind Sep 19, 2023
2d29cb6
Merge branch 'main' into feat/profiling
vaind Sep 19, 2023
4748a00
Merge branch 'main' into feat/profiling
vaind Sep 20, 2023
39d1187
chore: remove obsolete code
vaind Sep 25, 2023
50994d4
Update flutter/example/lib/main.dart
vaind Oct 3, 2023
6e599fb
renames
vaind Oct 3, 2023
e7582d7
Breadcrumbs for file I/O operations (#1649)
denrase Sep 25, 2023
36b052e
ci: don't run CI on markdown updates (#1651)
vaind Sep 25, 2023
813b947
fixup mock names after renames
vaind Oct 3, 2023
ce59509
Merge branch 'main' into feat/profiling
stefanosiano Oct 4, 2023
e13e7de
more renames (Sentry prefix)
vaind Oct 4, 2023
8e6bd13
chore: update changelog
vaind Oct 4, 2023
227b701
Merge branch 'main' into feat/profiling
vaind Oct 26, 2023
defdfa6
don't inline findPrimeNumber profiler-test function
vaind Oct 27, 2023
4074f01
fixup changelog
vaind Oct 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
update to the latest cocoa SDK API
  • Loading branch information
vaind committed Sep 18, 2023
commit c9be10c7122137cfcfb5f6c347efbdbb867bf75d
2 changes: 1 addition & 1 deletion dart/lib/src/hub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ class Hub {
Profiler? profiler;
if (_profilerFactory != null &&
_tracesSampler.sampleProfiling(samplingDecision)) {
profiler = _profilerFactory?.startProfiling(transactionContext);
profiler = _profilerFactory?.startProfiler(transactionContext);
}

final tracer = SentryTracer(
Expand Down
2 changes: 1 addition & 1 deletion dart/lib/src/profiling.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import '../sentry.dart';

@internal
abstract class ProfilerFactory {
Profiler? startProfiling(SentryTransactionContext context);
Profiler? startProfiler(SentryTransactionContext context);
}

@internal
Expand Down
2 changes: 1 addition & 1 deletion dart/lib/src/protocol/sentry_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class SentryEvent with SentryEventLike<SentryEvent> {
/// The ID Sentry.io assigned to the submitted event for future reference.
final SentryId eventId;

/// A timestamp representing when the breadcrumb occurred.
/// A timestamp representing when the event occurred.
final DateTime? timestamp;

/// A string representing the platform the SDK is submitting from. This will be used by the Sentry interface to customize various components in the interface.
Expand Down
8 changes: 4 additions & 4 deletions dart/test/hub_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ void main() {
test('profiler is started according to the sampling rate', () async {
final hub = fixture.getSut();
final factory = MockProfilerFactory();
when(factory.startProfiling(fixture._context)).thenReturn(MockProfiler());
when(factory.startProfiler(fixture._context)).thenReturn(MockProfiler());
hub.profilerFactory = factory;

var tr = hub.startTransactionWithContext(fixture._context);
Expand All @@ -397,15 +397,15 @@ void main() {
hub.options.profilesSampleRate = 1.0;
tr = hub.startTransactionWithContext(fixture._context);
expect((tr as SentryTracer).profiler, isNotNull);
verify(factory.startProfiling(fixture._context)).called(1);
verify(factory.startProfiler(fixture._context)).called(1);
});

test('profiler.finish() is called', () async {
final hub = fixture.getSut();
final factory = MockProfilerFactory();
final profiler = MockProfiler();
final expected = MockProfileInfo();
when(factory.startProfiling(fixture._context)).thenReturn(profiler);
when(factory.startProfiler(fixture._context)).thenReturn(profiler);
when(profiler.finishFor(any)).thenAnswer((_) async => expected);

hub.profilerFactory = factory;
Expand All @@ -421,7 +421,7 @@ void main() {
final factory = MockProfilerFactory();
final profiler = MockProfiler();
final expected = MockProfileInfo();
when(factory.startProfiling(fixture._context)).thenReturn(profiler);
when(factory.startProfiler(fixture._context)).thenReturn(profiler);
when(profiler.finishFor(any)).thenAnswer((_) async => expected);

hub.profilerFactory = factory;
Expand Down
4 changes: 2 additions & 2 deletions dart/test/mocks.mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ class MockProfilerFactory extends _i1.Mock implements _i3.ProfilerFactory {
}

@override
_i3.Profiler? startProfiling(_i2.SentryTransactionContext? context) =>
_i3.Profiler? startProfiler(_i2.SentryTransactionContext? context) =>
(super.noSuchMethod(Invocation.method(
#startProfiling,
#startProfiler,
[context],
)) as _i3.Profiler?);
}
Expand Down
30 changes: 25 additions & 5 deletions flutter/ios/Classes/SentryFlutterPluginApple.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,11 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
removeTag(key: key, result: result)

#if !os(tvOS) && !os(watchOS)
case "startProfiling":
startProfiling(call, result)
case "startProfiler":
startProfiler(call, result)

case "discardProfiler":
discardProfiler(call, result)

case "collectProfile":
collectProfile(call, result)
Expand Down Expand Up @@ -559,14 +562,14 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
}
}

private func startProfiling(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
private func startProfiler(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@armcknight could you help review the bridge profiling into cocoa?

guard let traceId = call.arguments as? String else {
print("Cannot start profiling: trace ID missing")
result(FlutterError(code: "5", message: "Cannot start profiling: trace ID missing", details: nil))
return
}

let startTime = PrivateSentrySDKOnly.startProfiling(forTrace: SentryId(uuidString: traceId))
let startTime = PrivateSentrySDKOnly.startProfiler(forTrace: SentryId(uuidString: traceId))
result(startTime)
}

Expand All @@ -584,9 +587,26 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
return
}

let payload = PrivateSentrySDKOnly.collectProfile(forTrace: SentryId(uuidString: traceId), since: startTime)
guard let endTime = arguments["endTime"] as? UInt64 else {
print("Cannot collect profile: end time missing")
result(FlutterError(code: "8", message: "Cannot collect profile: end time missing", details: nil))
return
}

let payload = PrivateSentrySDKOnly.collectProfileBetween(startTime, and: endTime, forTrace: SentryId(uuidString: traceId))
result(payload)
}

private func discardProfiler(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
guard let traceId = call.arguments as? String else {
print("Cannot discard a profiler: trace ID missing")
result(FlutterError(code: "9", message: "Cannot discard a profiler: trace ID missing", details: nil))
return
}

PrivateSentrySDKOnly.discardProfiler(forTrace: SentryId(uuidString: traceId))
result(nil)
}
}

// swiftlint:enable function_body_length
12 changes: 8 additions & 4 deletions flutter/lib/src/native/sentry_native.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,17 @@ class SentryNative {
return await _nativeChannel?.removeTag(key);
}

Future<int?> startProfiling(SentryId traceId) async {
return _nativeChannel?.startProfiling(traceId);
Future<int?> startProfiler(SentryId traceId) async {
return _nativeChannel?.startProfiler(traceId);
}

Future<void> discardProfiler(SentryId traceId) async {
return _nativeChannel?.discardProfiler(traceId);
}

Future<Map<String, dynamic>?> collectProfile(
SentryId traceId, int startTimeNs) async {
return _nativeChannel?.collectProfile(traceId, startTimeNs);
SentryId traceId, int startTimeNs, int endTimeNs) async {
return _nativeChannel?.collectProfile(traceId, startTimeNs, endTimeNs);
}

/// Reset state
Expand Down
23 changes: 17 additions & 6 deletions flutter/lib/src/native/sentry_native_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -139,21 +139,32 @@ class SentryNativeChannel {
}
}

Future<int?> startProfiling(SentryId traceId) async {
Future<int?> startProfiler(SentryId traceId) async {
try {
return await _channel.invokeMethod('startProfiling', traceId.toString())
return await _channel.invokeMethod('startProfiler', traceId.toString())
as int?;
} catch (error, stackTrace) {
_logError('startProfiling', error, stackTrace);
_logError('startProfiler', error, stackTrace);
return null;
}
}

Future<void> discardProfiler(SentryId traceId) async {
try {
return await _channel.invokeMethod('discardProfiler', traceId.toString());
} catch (error, stackTrace) {
_logError('discardProfiler', error, stackTrace);
}
}

Future<Map<String, dynamic>?> collectProfile(
SentryId traceId, int startTimeNs) async {
SentryId traceId, int startTimeNs, int endTimeNs) async {
try {
return await _channel.invokeMapMethod<String, dynamic>('collectProfile',
{'traceId': traceId.toString(), 'startTime': startTimeNs});
return await _channel.invokeMapMethod<String, dynamic>('collectProfile', {
'traceId': traceId.toString(),
'startTime': startTimeNs,
'endTime': endTimeNs,
});
} catch (error, stackTrace) {
_logError('collectProfile', error, stackTrace);
return null;
Expand Down
49 changes: 32 additions & 17 deletions flutter/lib/src/profiling.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import 'sentry_native.dart';
// ignore: invalid_use_of_internal_member
class NativeProfilerFactory implements ProfilerFactory {
final SentryNative _native;
final ClockProvider _clock;

NativeProfilerFactory(this._native);
NativeProfilerFactory(this._native, this._clock);

static void attachTo(Hub hub) {
// ignore: invalid_use_of_internal_member
Expand All @@ -32,24 +33,26 @@ class NativeProfilerFactory implements ProfilerFactory {
if (options.platformChecker.platform.isMacOS ||
options.platformChecker.platform.isIOS) {
// ignore: invalid_use_of_internal_member
hub.profilerFactory = NativeProfilerFactory(SentryNative());
hub.profilerFactory =
// ignore: invalid_use_of_internal_member
NativeProfilerFactory(SentryNative(), options.clock);
}
}

@override
NativeProfiler? startProfiling(SentryTransactionContext context) {
NativeProfiler? startProfiler(SentryTransactionContext context) {
if (context.traceId == SentryId.empty()) {
return null;
}

final startTime = _native.startProfiling(context.traceId);
final startTime = _native.startProfiler(context.traceId);

// TODO we cannot await the future returned by a method channel because
// startTransaction() is synchronous. In order to make this code fully
// synchronous and actually start the profiler, we need synchronous FFI
// calls, see https://github.com/getsentry/sentry-dart/issues/1444
// For now, return immediately even though the profiler may not have started yet...
return NativeProfiler(_native, startTime, context.traceId);
return NativeProfiler(_native, startTime, context.traceId, _clock);
}
}

Expand All @@ -60,33 +63,45 @@ class NativeProfiler implements Profiler {
final SentryNative _native;
final Future<int?> _startTime;
final SentryId _traceId;
bool _finished = false;
final ClockProvider _clock;

NativeProfiler(this._native, this._startTime, this._traceId);
NativeProfiler(this._native, this._startTime, this._traceId, this._clock);

@override
void dispose() {
// TODO expose in the cocoa SDK
// _startTime.then((_) => _native.discardProfiling(this._traceId));
if (!_finished) {
_finished = true;
_startTime.then((_) => _native.discardProfiler(_traceId));
}
}

@override
Future<NativeProfileInfo?> finishFor(SentryTransaction transaction) async {
final starTime = await _startTime;
if (starTime == null) {
if (_finished) {
return null;
}
_finished = true;

final starTimeNs = await _startTime;
if (starTimeNs == null) {
return null;
}

final payload = await _native.collectProfile(_traceId, starTime);
// ignore: invalid_use_of_internal_member
final transactionEndTime = transaction.timestamp ?? _clock();
final duration = transactionEndTime.difference(transaction.startTimestamp);
final endTimeNs = starTimeNs + (duration.inMicroseconds * 1000);

final payload =
await _native.collectProfile(_traceId, starTimeNs, endTimeNs);
if (payload == null) {
return null;
}

payload["transaction"] = <String, String?>{
"id": transaction.eventId.toString(),
"trace_id": _traceId.toString(),
"name": transaction.transaction,
// "active_thread_id" : [transaction.trace.transactionContext sentry_threadInfo].threadId
};
payload["transaction"]["id"] = transaction.eventId.toString();
payload["transaction"]["trace_id"] = _traceId.toString();
payload["transaction"]["name"] = transaction.transaction;
payload["timestamp"] = transaction.startTimestamp.toIso8601String();
return NativeProfileInfo(payload);
}
Expand Down
31 changes: 23 additions & 8 deletions flutter/test/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ ISentrySpan startTransactionShim(
Transport,
// ignore: invalid_use_of_internal_member
SentryTracer,
SentryTransaction,
MethodChannel,
], customMocks: [
MockSpec<Hub>(fallbackGenerators: {#startTransaction: startTransactionShim})
Expand Down Expand Up @@ -189,7 +190,8 @@ class TestMockSentryNative implements SentryNative {
var numberOfSetTagCalls = 0;
SentryUser? sentryUser;
var numberOfSetUserCalls = 0;
var numberOfStartProfilingCalls = 0;
var numberOfStartProfilerCalls = 0;
var numberOfDiscardProfilerCalls = 0;
var numberOfCollectProfileCalls = 0;

@override
Expand Down Expand Up @@ -270,14 +272,20 @@ class TestMockSentryNative implements SentryNative {

@override
Future<Map<String, dynamic>?> collectProfile(
SentryId traceId, int startTimeNs) {
SentryId traceId, int startTimeNs, int endTimeNs) {
numberOfCollectProfileCalls++;
return Future.value(null);
}

@override
Future<int?> startProfiling(SentryId traceId) {
numberOfStartProfilingCalls++;
Future<int?> startProfiler(SentryId traceId) {
numberOfStartProfilerCalls++;
return Future.value(42);
}

@override
Future<void> discardProfiler(SentryId traceId) {
numberOfDiscardProfilerCalls++;
return Future.value(null);
}
}
Expand All @@ -299,7 +307,8 @@ class MockNativeChannel implements SentryNativeChannel {
int numberOfSetContextsCalls = 0;
int numberOfSetExtraCalls = 0;
int numberOfSetTagCalls = 0;
int numberOfStartProfilingCalls = 0;
int numberOfStartProfilerCalls = 0;
int numberOfDiscardProfilerCalls = 0;
int numberOfCollectProfileCalls = 0;

@override
Expand Down Expand Up @@ -364,14 +373,20 @@ class MockNativeChannel implements SentryNativeChannel {

@override
Future<Map<String, dynamic>?> collectProfile(
SentryId traceId, int startTimeNs) {
SentryId traceId, int startTimeNs, int endTimeNs) {
numberOfCollectProfileCalls++;
return Future.value(null);
}

@override
Future<int?> startProfiling(SentryId traceId) {
numberOfStartProfilingCalls++;
Future<int?> startProfiler(SentryId traceId) {
numberOfStartProfilerCalls++;
return Future.value(null);
}

@override
Future<int?> discardProfiler(SentryId traceId) {
numberOfDiscardProfilerCalls++;
return Future.value(null);
}
}
Expand Down
Loading