SFAnalyticsMultiSampler.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 "SFAnalyticsMultiSampler+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 SFAnalyticsMultiSampler { 34 NSTimeInterval _samplingInterval; 35 dispatch_source_t _timer; 36 NSString* _name; 37 MultiSamplerDictionary (^_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 - (instancetype)initWithName:(NSString*)name interval:(NSTimeInterval)interval block:(MultiSamplerDictionary (^)(void))block clientClass:(Class)clientClass 49 { 50 if (self = [super init]) { 51 if (![clientClass isSubclassOfClass:[SFAnalytics class]]) { 52 secerror("SFAnalyticsSampler created without valid client class (%@)", clientClass); 53 return nil; 54 } 55 56 if (!name || (interval < 1.0f && interval != SFAnalyticsSamplerIntervalOncePerReport) || !block) { 57 secerror("SFAnalyticsSampler created without proper data"); 58 return nil; 59 } 60 61 _clientClass = clientClass; 62 _block = block; 63 _name = name; 64 _samplingInterval = interval; 65 [self newTimer]; 66 } 67 return self; 68 } 69 70 - (void)newTimer 71 { 72 if (_activeTimer) { 73 [self pauseSampling]; 74 } 75 76 _oncePerReport = (_samplingInterval == SFAnalyticsSamplerIntervalOncePerReport); 77 if (_oncePerReport) { 78 [self setupOnceTimer]; 79 } else { 80 [self setupPeriodicTimer]; 81 } 82 } 83 84 - (void)setupOnceTimer 85 { 86 __weak __typeof(self) weakSelf = self; 87 notify_register_dispatch(SFAnalyticsFireSamplersNotification, &_notificationToken, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(int token) { 88 __strong __typeof(self) strongSelf = weakSelf; 89 if (!strongSelf) { 90 secnotice("SFAnalyticsSampler", "sampler went away before we could run its once-per-report block"); 91 notify_cancel(token); 92 return; 93 } 94 95 MultiSamplerDictionary data = strongSelf->_block(); 96 [data enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSNumber * _Nonnull obj, BOOL * _Nonnull stop) { 97 [[strongSelf->_clientClass logger] logMetric:obj withName:key oncePerReport:strongSelf->_oncePerReport]; 98 }]; 99 }); 100 _activeTimer = YES; 101 } 102 103 - (void)setupPeriodicTimer 104 { 105 _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); 106 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 107 108 __weak __typeof(self) weakSelf = self; 109 dispatch_source_set_event_handler(_timer, ^{ 110 __strong __typeof(self) strongSelf = weakSelf; 111 if (!strongSelf) { 112 secnotice("SFAnalyticsSampler", "sampler went away before we could run its once-per-report block"); 113 return; 114 } 115 116 MultiSamplerDictionary data = strongSelf->_block(); 117 [data enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSNumber * _Nonnull obj, BOOL * _Nonnull stop) { 118 [[strongSelf->_clientClass logger] logMetric:obj withName:key oncePerReport:strongSelf->_oncePerReport]; 119 }]; 120 }); 121 dispatch_resume(_timer); 122 123 _activeTimer = YES; 124 } 125 126 - (void)setSamplingInterval:(NSTimeInterval)interval 127 { 128 if (interval < 1.0f && !(interval == SFAnalyticsSamplerIntervalOncePerReport)) { 129 secerror("SFAnalyticsSampler: interval %f is not supported", interval); 130 return; 131 } 132 133 _samplingInterval = interval; 134 [self newTimer]; 135 } 136 137 - (NSTimeInterval)samplingInterval { 138 return _samplingInterval; 139 } 140 141 - (MultiSamplerDictionary)sampleNow 142 { 143 MultiSamplerDictionary data = _block(); 144 [data enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSNumber * _Nonnull obj, BOOL * _Nonnull stop) { 145 [[self->_clientClass logger] logMetric:obj withName:key oncePerReport:self->_oncePerReport]; 146 }]; 147 return data; 148 } 149 150 - (void)pauseSampling 151 { 152 if (!_activeTimer) { 153 return; 154 } 155 156 if (_oncePerReport) { 157 notify_cancel(_notificationToken); 158 _notificationToken = 0; 159 } else { 160 dispatch_source_cancel(_timer); 161 } 162 _activeTimer = NO; 163 } 164 165 - (void)resumeSampling 166 { 167 [self newTimer]; 168 } 169 170 - (void)dealloc 171 { 172 [self pauseSampling]; 173 } 174 175 @end 176 177 #endif