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