-
-
Notifications
You must be signed in to change notification settings - Fork 372
perf: gather profiler metrics gathering on a low-pri queue #2956
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
Changes from all commits
95a84b1
9e2110e
4355b03
b9e160e
763b65d
7fdce6d
5a8ae1f
c195a4e
adef4bc
b4014ee
a544511
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import Foundation | ||
| import Sentry | ||
|
|
||
| public class TestDispatchFactory: SentryDispatchFactory { | ||
| public var vendedSourceHandler: ((TestDispatchSourceWrapper) -> Void)? | ||
| public var vendedQueueHandler: ((TestSentryDispatchQueueWrapper) -> Void)? | ||
|
|
||
| public override func queue(withName name: UnsafePointer<CChar>, attributes: __OS_dispatch_queue_attr) -> SentryDispatchQueueWrapper { | ||
| let queue = TestSentryDispatchQueueWrapper(name: name, attributes: attributes) | ||
| vendedQueueHandler?(queue) | ||
| return queue | ||
| } | ||
|
|
||
| public override func source(withInterval interval: UInt64, leeway: UInt64, queueName: UnsafePointer<CChar>, attributes: __OS_dispatch_queue_attr, eventHandler: @escaping () -> Void) -> SentryDispatchSourceWrapper { | ||
| let source = TestDispatchSourceWrapper(eventHandler: eventHandler) | ||
| vendedSourceHandler?(source) | ||
| return source | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import Foundation | ||
| import Sentry | ||
|
|
||
| public class TestDispatchSourceWrapper: SentryDispatchSourceWrapper { | ||
| public struct Override { | ||
| public var eventHandler: (() -> Void)? | ||
| } | ||
| public var overrides = Override() | ||
|
|
||
| public init(eventHandler: @escaping () -> Void) { | ||
| self.overrides.eventHandler = eventHandler | ||
| super.init() | ||
| } | ||
|
|
||
| public override func cancel() { | ||
| // no-op | ||
| } | ||
|
|
||
| public func fire() { | ||
| self.overrides.eventHandler?() | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| #import "SentryDispatchFactory.h" | ||
| #import "SentryDispatchQueueWrapper.h" | ||
| #import "SentryDispatchSourceWrapper.h" | ||
|
|
||
| @implementation SentryDispatchFactory | ||
|
|
||
| - (SentryDispatchQueueWrapper *)queueWithName:(const char *)name | ||
| attributes:(dispatch_queue_attr_t)attributes | ||
| { | ||
| return [[SentryDispatchQueueWrapper alloc] initWithName:name attributes:attributes]; | ||
| } | ||
|
|
||
| - (SentryDispatchSourceWrapper *)sourceWithInterval:(uint64_t)interval | ||
| leeway:(uint64_t)leeway | ||
| queueName:(const char *)queueName | ||
| attributes:(dispatch_queue_attr_t)attributes | ||
| eventHandler:(void (^)(void))eventHandler | ||
| { | ||
| return [[SentryDispatchSourceWrapper alloc] | ||
| initTimerWithInterval:interval | ||
| leeway:leeway | ||
| queue:[self queueWithName:queueName attributes:attributes] | ||
| eventHandler:eventHandler]; | ||
| } | ||
|
|
||
| @end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| #import "SentryDispatchSourceWrapper.h" | ||
| #import "SentryDispatchQueueWrapper.h" | ||
|
|
||
| @implementation SentryDispatchSourceWrapper { | ||
| SentryDispatchQueueWrapper *_queueWrapper; | ||
| dispatch_source_t _source; | ||
| } | ||
|
|
||
| - (instancetype)initTimerWithInterval:(uint64_t)interval | ||
| leeway:(uint64_t)leeway | ||
| queue:(SentryDispatchQueueWrapper *)queueWrapper | ||
| eventHandler:(void (^)(void))eventHandler | ||
| { | ||
| if (self = [super init]) { | ||
| _queueWrapper = queueWrapper; | ||
| _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queueWrapper.queue); | ||
| dispatch_source_set_event_handler(_source, eventHandler); | ||
| dispatch_source_set_timer( | ||
| _source, dispatch_time(DISPATCH_TIME_NOW, interval), interval, leeway); | ||
| dispatch_resume(_source); | ||
| } | ||
| return self; | ||
| } | ||
|
|
||
| - (void)cancel | ||
| { | ||
| dispatch_cancel(_source); | ||
| } | ||
|
|
||
| @end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,9 @@ | |
| #if SENTRY_TARGET_PROFILING_SUPPORTED | ||
|
|
||
| # import "SentryCurrentDate.h" | ||
| # import "SentryDispatchFactory.h" | ||
| # import "SentryDispatchQueueWrapper.h" | ||
| # import "SentryDispatchSourceWrapper.h" | ||
| # import "SentryEvent+Private.h" | ||
| # import "SentryFormatter.h" | ||
| # import "SentryLog.h" | ||
|
|
@@ -23,19 +26,18 @@ @interface SentryMetricReading : NSObject | |
| @implementation SentryMetricReading | ||
| @end | ||
|
|
||
| /** | ||
| * Currently set to 10 Hz as we don't anticipate much utility out of a higher resolution when | ||
| * sampling CPU usage and memory footprint, and we want to minimize the overhead of making the | ||
| * necessary system calls to gather that information. | ||
| */ | ||
| static const NSTimeInterval kSentryMetricProfilerTimeseriesInterval = 0.1; | ||
|
|
||
| NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint = @"memory_footprint"; | ||
| NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat = @"cpu_usage_%d"; | ||
|
|
||
| NSString *const kSentryMetricProfilerSerializationUnitBytes = @"byte"; | ||
| NSString *const kSentryMetricProfilerSerializationUnitPercentage = @"percent"; | ||
|
|
||
| // Currently set to 10 Hz as we don't anticipate much utility out of a higher resolution when | ||
| // sampling CPU usage and memory footprint, and we want to minimize the overhead of making the | ||
| // necessary system calls to gather that information. This is currently roughly 10% of the | ||
| // backtrace profiler's resolution. | ||
| static uint64_t frequencyHz = 10; | ||
|
|
||
| namespace { | ||
| /** | ||
| * @return a dictionary containing all the metric values recorded during the transaction, or @c nil | ||
|
|
@@ -74,11 +76,11 @@ @implementation SentryMetricReading | |
| } // namespace | ||
|
|
||
| @implementation SentryMetricProfiler { | ||
| NSTimer *_timer; | ||
| SentryDispatchSourceWrapper *_dispatchSource; | ||
|
|
||
| SentryNSProcessInfoWrapper *_processInfoWrapper; | ||
| SentrySystemWrapper *_systemWrapper; | ||
| SentryNSTimerWrapper *_timerWrapper; | ||
| SentryDispatchFactory *_dispatchFactory; | ||
|
|
||
| /// arrays of readings keyed on NSNumbers representing the core number for the set of readings | ||
| NSMutableDictionary<NSNumber *, NSMutableArray<SentryMetricReading *> *> *_cpuUsage; | ||
|
|
@@ -88,7 +90,7 @@ @implementation SentryMetricProfiler { | |
|
|
||
| - (instancetype)initWithProcessInfoWrapper:(SentryNSProcessInfoWrapper *)processInfoWrapper | ||
| systemWrapper:(SentrySystemWrapper *)systemWrapper | ||
| timerWrapper:(SentryNSTimerWrapper *)timerWrapper | ||
| dispatchFactory:(nonnull SentryDispatchFactory *)dispatchFactory | ||
| { | ||
| if (self = [super init]) { | ||
| _cpuUsage = | ||
|
|
@@ -102,7 +104,7 @@ - (instancetype)initWithProcessInfoWrapper:(SentryNSProcessInfoWrapper *)process | |
|
|
||
| _systemWrapper = systemWrapper; | ||
| _processInfoWrapper = processInfoWrapper; | ||
| _timerWrapper = timerWrapper; | ||
| _dispatchFactory = dispatchFactory; | ||
|
|
||
| _memoryFootprint = [NSMutableArray<SentryMetricReading *> array]; | ||
| } | ||
|
|
@@ -123,24 +125,28 @@ - (void)start | |
|
|
||
| - (void)stop | ||
| { | ||
| [_timer invalidate]; | ||
| [_dispatchSource cancel]; | ||
| } | ||
|
|
||
| - (NSMutableDictionary<NSString *, id> *)serializeForTransaction:(SentryTransaction *)transaction | ||
| { | ||
| NSMutableDictionary<NSString *, id> *dict; | ||
| NSArray<SentryMetricReading *> *memoryFootprint; | ||
| NSDictionary<NSNumber *, NSArray<SentryMetricReading *> *> *cpuUsage; | ||
| @synchronized(self) { | ||
| dict = [NSMutableDictionary<NSString *, id> dictionary]; | ||
| memoryFootprint = [NSArray<SentryMetricReading *> arrayWithArray:_memoryFootprint]; | ||
| cpuUsage = [NSDictionary<NSNumber *, NSArray<SentryMetricReading *> *> | ||
| dictionaryWithDictionary:_cpuUsage]; | ||
| } | ||
|
|
||
| if (_memoryFootprint.count > 0) { | ||
| const auto dict = [NSMutableDictionary<NSString *, id> dictionary]; | ||
| if (memoryFootprint.count > 0) { | ||
| dict[kSentryMetricProfilerSerializationKeyMemoryFootprint] | ||
| = serializeValuesWithNormalizedTime( | ||
| _memoryFootprint, kSentryMetricProfilerSerializationUnitBytes, transaction); | ||
| memoryFootprint, kSentryMetricProfilerSerializationUnitBytes, transaction); | ||
| } | ||
|
|
||
| [_cpuUsage enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull core, | ||
| NSMutableArray<SentryMetricReading *> *_Nonnull readings, BOOL *_Nonnull stop) { | ||
| [cpuUsage enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull core, | ||
| NSArray<SentryMetricReading *> *_Nonnull readings, BOOL *_Nonnull stop) { | ||
| if (readings.count > 0) { | ||
| dict[[NSString stringWithFormat:kSentryMetricProfilerSerializationKeyCPUUsageFormat, | ||
| core.intValue]] | ||
|
|
@@ -157,12 +163,18 @@ - (void)stop | |
| - (void)registerSampler | ||
| { | ||
| __weak auto weakSelf = self; | ||
| _timer = [_timerWrapper scheduledTimerWithTimeInterval:kSentryMetricProfilerTimeseriesInterval | ||
| repeats:YES | ||
| block:^(NSTimer *_Nonnull timer) { | ||
| [weakSelf recordCPUPercentagePerCore]; | ||
| [weakSelf recordMemoryFootprint]; | ||
| }]; | ||
| const auto intervalNs = (uint64_t)1e9 / frequencyHz; | ||
| const auto leewayNs = intervalNs / 2; | ||
| _dispatchSource = | ||
| [_dispatchFactory sourceWithInterval:intervalNs | ||
| leeway:leewayNs | ||
| queueName:"io.sentry.metric-profiler" | ||
| attributes:dispatch_queue_attr_make_with_qos_class( | ||
| DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_UTILITY, 0) | ||
| eventHandler:^{ | ||
| [weakSelf recordCPUPercentagePerCore]; | ||
| [weakSelf recordMemoryFootprint]; | ||
|
Comment on lines
+175
to
+176
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. Their calls to
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i improved the method that reads from the data structure as well in adef4bc |
||
| }]; | ||
| } | ||
|
|
||
| - (void)recordMemoryFootprint | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is no longer true since we bumped our min supported version. I believe we can also change this:
sentry-cocoa/Sources/Sentry/SentryDispatchQueueWrapper.m
Lines 16 to 17 in d257eb9
This is now exposed as a property in the public interface so it can be accessed when setting up a new
dispatch_source_tinSentryDispatchSourceWrapper[