/ Analytics / SFAnalyticsSampler.m
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