SFAnalyticsSampler.m
1 /* 2 * Copyright (c) 2017 Apple Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24 #if __OBJC2__ 25 26 #import "SFAnalyticsSampler+Internal.h" 27 #import "SFAnalytics+Internal.h" 28 #import "SFAnalyticsDefines.h" 29 #import "utilities/debugging.h" 30 #include <notify.h> 31 #include <dispatch/dispatch.h> 32 33 @implementation SFAnalyticsSampler { 34 NSTimeInterval _samplingInterval; 35 dispatch_source_t _timer; 36 NSString* _name; 37 NSNumber* (^_block)(void); 38 int _notificationToken; 39 Class _clientClass; 40 BOOL _oncePerReport; 41 BOOL _activeTimer; 42 } 43 44 @synthesize name = _name; 45 @synthesize samplingInterval = _samplingInterval; 46 @synthesize oncePerReport = _oncePerReport; 47 48 49 - (instancetype)initWithName:(NSString*)name interval:(NSTimeInterval)interval block:(NSNumber* (^)(void))block clientClass:(Class)clientClass 50 { 51 if (self = [super init]) { 52 if (![clientClass isSubclassOfClass:[SFAnalytics class]]) { 53 secerror("SFAnalyticsSampler created without valid client class (%@)", clientClass); 54 return nil; 55 } 56 57 if (!name || (interval < 1.0f && interval != SFAnalyticsSamplerIntervalOncePerReport) || !block) { 58 secerror("SFAnalyticsSampler created without proper data"); 59 return nil; 60 } 61 62 _clientClass = clientClass; 63 _block = block; 64 _name = name; 65 _samplingInterval = interval; 66 [self newTimer]; 67 } 68 return self; 69 } 70 71 - (void)newTimer 72 { 73 if (_activeTimer) { 74 [self pauseSampling]; 75 } 76 77 _oncePerReport = (_samplingInterval == SFAnalyticsSamplerIntervalOncePerReport); 78 if (_oncePerReport) { 79 [self setupOnceTimer]; 80 } else { 81 [self setupPeriodicTimer]; 82 } 83 } 84 85 - (void)setupOnceTimer 86 { 87 __weak __typeof(self) weakSelf = self; 88 notify_register_dispatch(SFAnalyticsFireSamplersNotification, &_notificationToken, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(int token) { 89 __strong __typeof(self) strongSelf = weakSelf; 90 if (!strongSelf) { 91 secnotice("SFAnalyticsSampler", "sampler went away before we could run its once-per-report block"); 92 notify_cancel(token); 93 return; 94 } 95 [[strongSelf->_clientClass logger] logMetric:strongSelf->_block() withName:strongSelf->_name oncePerReport:strongSelf->_oncePerReport]; 96 }); 97 _activeTimer = YES; 98 } 99 100 - (void)setupPeriodicTimer 101 { 102 _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); 103 dispatch_source_set_timer(_timer, dispatch_walltime(0, _samplingInterval * NSEC_PER_SEC), _samplingInterval * NSEC_PER_SEC, _samplingInterval * NSEC_PER_SEC / 50.0); // give 2% leeway on timer 104 105 __weak __typeof(self) weakSelf = self; 106 dispatch_source_set_event_handler(_timer, ^{ 107 __strong __typeof(self) strongSelf = weakSelf; 108 if (!strongSelf) { 109 // TODO: can we cancel this thing from here? 110 secnotice("SFAnalyticsSampler", "sampler went away before we could run its once-per-report block"); 111 return; 112 } 113 [[strongSelf->_clientClass logger] logMetric:strongSelf->_block() withName:strongSelf->_name oncePerReport:strongSelf->_oncePerReport]; 114 }); 115 dispatch_resume(_timer); 116 117 _activeTimer = YES; 118 } 119 120 - (void)setSamplingInterval:(NSTimeInterval)interval 121 { 122 if (interval < 1.0f && !(interval == SFAnalyticsSamplerIntervalOncePerReport)) { 123 secerror("SFAnalyticsSampler: interval %f is not supported", interval); 124 return; 125 } 126 127 _samplingInterval = interval; 128 [self newTimer]; 129 } 130 131 - (NSTimeInterval)samplingInterval { 132 return _samplingInterval; 133 } 134 135 - (NSNumber*)sampleNow 136 { 137 NSNumber* result = _block(); 138 [[_clientClass logger] logMetric:result withName:_name oncePerReport:_oncePerReport]; 139 return result; 140 } 141 142 - (void)pauseSampling 143 { 144 if (!_activeTimer) { 145 return; 146 } 147 148 if (_oncePerReport) { 149 notify_cancel(_notificationToken); 150 _notificationToken = 0; 151 } else { 152 dispatch_source_cancel(_timer); 153 } 154 _activeTimer = NO; 155 } 156 157 - (void)resumeSampling 158 { 159 [self newTimer]; 160 } 161 162 - (void)dealloc 163 { 164 [self pauseSampling]; 165 } 166 167 @end 168 169 #endif