FPRConfigurations.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. // Copyright 2020 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import <UIKit/UIKit.h>
  15. #import "FirebasePerformance/Sources/Common/FPRConstants.h"
  16. #import "FirebasePerformance/Sources/Configurations/FPRConfigurations+Private.h"
  17. #import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
  18. #import "FirebasePerformance/Sources/Configurations/FPRRemoteConfigFlags+Private.h"
  19. #import "FirebasePerformance/Sources/Configurations/FPRRemoteConfigFlags.h"
  20. #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  21. FPRConfigName kFPRConfigDataCollectionEnabled = @"dataCollectionEnabled";
  22. FPRConfigName kFPRConfigInstrumentationEnabled = @"instrumentationEnabled";
  23. NSString *const kFPRConfigInstrumentationUserPreference =
  24. @"com.firebase.performanceInsrumentationEnabled";
  25. NSString *const kFPRConfigInstrumentationPlistKey = @"firebase_performance_instrumentation_enabled";
  26. NSString *const kFPRConfigCollectionUserPreference = @"com.firebase.performanceCollectionEnabled";
  27. NSString *const kFPRConfigCollectionPlistKey = @"firebase_performance_collection_enabled";
  28. NSString *const kFPRDiagnosticsUserPreference = @"FPRDiagnosticsLocal";
  29. NSString *const kFPRDiagnosticsEnabledPlistKey = @"FPRDiagnosticsLocal";
  30. NSString *const kFPRConfigCollectionDeactivationPlistKey =
  31. @"firebase_performance_collection_deactivated";
  32. NSString *const kFPRConfigLogSource = @"com.firebase.performanceLogSource";
  33. @implementation FPRConfigurations
  34. static dispatch_once_t gSharedInstanceToken;
  35. + (instancetype)sharedInstance {
  36. static FPRConfigurations *instance = nil;
  37. dispatch_once(&gSharedInstanceToken, ^{
  38. FPRConfigurationSource sources = FPRConfigurationSourceRemoteConfig;
  39. instance = [[FPRConfigurations alloc] initWithSources:sources];
  40. });
  41. return instance;
  42. }
  43. + (void)reset {
  44. // TODO(b/120032990): Reset the singletons that this singleton uses.
  45. gSharedInstanceToken = 0;
  46. [[NSUserDefaults standardUserDefaults]
  47. removeObjectForKey:kFPRConfigInstrumentationUserPreference];
  48. [[NSUserDefaults standardUserDefaults] removeObjectForKey:kFPRConfigCollectionUserPreference];
  49. }
  50. - (instancetype)initWithSources:(FPRConfigurationSource)source {
  51. self = [super init];
  52. if (self) {
  53. _sources = source;
  54. [self setupRemoteConfigFlags];
  55. // Register for notifications to update configs.
  56. [self registerForNotifications];
  57. self.FIRAppClass = [FIRApp class];
  58. self.userDefaults = [NSUserDefaults standardUserDefaults];
  59. self.infoDictionary = [NSBundle mainBundle].infoDictionary;
  60. self.mainBundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  61. self.updateQueue = dispatch_queue_create("com.google.perf.configUpdate", DISPATCH_QUEUE_SERIAL);
  62. }
  63. return self;
  64. }
  65. - (void)registerForNotifications {
  66. [[NSNotificationCenter defaultCenter] addObserver:self
  67. selector:@selector(update)
  68. name:UIApplicationDidBecomeActiveNotification
  69. object:nil];
  70. }
  71. /** Searches the main bundle and the bundle from bundleForClass: info dictionaries for the key and
  72. * returns the first result.
  73. *
  74. * @param key The key to search the info dictionaries for.
  75. * @return The first object found in the info dictionary of the main bundle and bundleForClass:.
  76. */
  77. - (nullable id)objectForInfoDictionaryKey:(NSString *)key {
  78. // If the config infoDictionary has been set to a new dictionary, only use the original dictionary
  79. // instead of the new dictionary.
  80. if (self.infoDictionary != [NSBundle mainBundle].infoDictionary) {
  81. return self.infoDictionary[key]; // nullable.
  82. }
  83. NSArray<NSBundle *> *bundles = @[ [NSBundle mainBundle], [NSBundle bundleForClass:[self class]] ];
  84. for (NSBundle *bundle in bundles) {
  85. id object = [bundle objectForInfoDictionaryKey:key];
  86. if (object) {
  87. return object; // nonnull.
  88. }
  89. }
  90. return nil;
  91. }
  92. - (void)update {
  93. dispatch_async(self.updateQueue, ^{
  94. if (!self.remoteConfigFlags) {
  95. [self setupRemoteConfigFlags];
  96. }
  97. [self.remoteConfigFlags update];
  98. });
  99. }
  100. /**
  101. * Sets up the remote config flags instance based on 3 different factors:
  102. * 1. Is the firebase app configured?
  103. * 2. Is the remote config source enabled?
  104. * 3. If the Remote Config flags instance exists already?
  105. */
  106. - (void)setupRemoteConfigFlags {
  107. if (!self.remoteConfigFlags && [self.FIRAppClass isDefaultAppConfigured] &&
  108. (self.sources & FPRConfigurationSourceRemoteConfig) == FPRConfigurationSourceRemoteConfig) {
  109. self.remoteConfigFlags = [FPRRemoteConfigFlags sharedInstance];
  110. }
  111. }
  112. #pragma mark - Overridden Properties
  113. - (void)setDataCollectionEnabled:(BOOL)dataCollectionEnabled {
  114. [self.userDefaults setBool:dataCollectionEnabled forKey:kFPRConfigCollectionUserPreference];
  115. }
  116. // The data collection flag is determined by this order:
  117. // 1. A plist flag for permanently disabling data collection
  118. // 2. The runtime flag (NSUserDefaults)
  119. // 3. A plist flag for enabling/disabling (overrideable)
  120. // 4. The global data collection switch from Core.
  121. - (BOOL)isDataCollectionEnabled {
  122. /**
  123. * Perf only works with the default app, so validate it exists then use the value from the global
  124. * data collection from the default app as the base value if no other values are set.
  125. */
  126. if (![self.FIRAppClass isDefaultAppConfigured]) {
  127. return NO;
  128. }
  129. BOOL dataCollectionPreference = [self.FIRAppClass defaultApp].isDataCollectionDefaultEnabled;
  130. // Check if data collection is permanently disabled by plist. If so, disable data collection.
  131. id dataCollectionDeactivationObject =
  132. [self objectForInfoDictionaryKey:kFPRConfigCollectionDeactivationPlistKey];
  133. if (dataCollectionDeactivationObject) {
  134. BOOL dataCollectionDeactivated = [dataCollectionDeactivationObject boolValue];
  135. if (dataCollectionDeactivated) {
  136. return NO;
  137. }
  138. }
  139. /**
  140. * Check if the performance collection preference key is available in NSUserDefaults.
  141. * If it exists - Just honor that and return that value.
  142. * If it does not exist - Check if firebase_performance_collection_enabled exists in Info.plist.
  143. * If it exists - honor that and return that value.
  144. * If not - return YES stating performance collection is enabled.
  145. */
  146. id dataCollectionPreferenceObject =
  147. [self.userDefaults objectForKey:kFPRConfigCollectionUserPreference];
  148. if (dataCollectionPreferenceObject) {
  149. dataCollectionPreference = [dataCollectionPreferenceObject boolValue];
  150. } else {
  151. dataCollectionPreferenceObject = [self objectForInfoDictionaryKey:kFPRConfigCollectionPlistKey];
  152. if (dataCollectionPreferenceObject) {
  153. dataCollectionPreference = [dataCollectionPreferenceObject boolValue];
  154. }
  155. }
  156. return dataCollectionPreference;
  157. }
  158. - (void)setInstrumentationEnabled:(BOOL)instrumentationEnabled {
  159. [self.userDefaults setBool:instrumentationEnabled forKey:kFPRConfigInstrumentationUserPreference];
  160. }
  161. - (BOOL)isInstrumentationEnabled {
  162. BOOL instrumentationPreference = YES;
  163. id instrumentationPreferenceObject =
  164. [self.userDefaults objectForKey:kFPRConfigInstrumentationUserPreference];
  165. /**
  166. * Check if the performance instrumentation preference key is available in NSUserDefaults.
  167. * If it exists - Just honor that and return that value.
  168. * If not - Check if firebase_performance_instrumentation_enabled exists in Info.plist.
  169. * If it exists - honor that and return that value.
  170. * If not - return YES stating performance instrumentation is enabled.
  171. */
  172. if (instrumentationPreferenceObject) {
  173. instrumentationPreference = [instrumentationPreferenceObject boolValue];
  174. } else {
  175. instrumentationPreferenceObject =
  176. [self objectForInfoDictionaryKey:kFPRConfigInstrumentationPlistKey];
  177. if (instrumentationPreferenceObject) {
  178. instrumentationPreference = [instrumentationPreferenceObject boolValue];
  179. }
  180. }
  181. return instrumentationPreference;
  182. }
  183. #pragma mark - Fireperf SDK configurations.
  184. - (BOOL)sdkEnabled {
  185. BOOL enabled = YES;
  186. if (self.remoteConfigFlags) {
  187. enabled = [self.remoteConfigFlags performanceSDKEnabledWithDefaultValue:enabled];
  188. }
  189. // Check if the current version is one of the disabled versions.
  190. if ([[self sdkDisabledVersions] containsObject:[NSString stringWithUTF8String:kFPRSDKVersion]]) {
  191. enabled = NO;
  192. }
  193. // If there is a plist override, honor that value.
  194. // NOTE: PList override should ideally be used only for tests and not for production.
  195. id plistObject = [self objectForInfoDictionaryKey:@"firebase_performance_sdk_enabled"];
  196. if (plistObject) {
  197. enabled = [plistObject boolValue];
  198. }
  199. return enabled;
  200. }
  201. - (BOOL)diagnosticsEnabled {
  202. BOOL enabled = NO;
  203. /**
  204. * Check if the diagnostics preference key is available in NSUserDefaults.
  205. * If it exists - Just honor that and return that value.
  206. * If not - Check if firebase_performance_instrumentation_enabled exists in Info.plist.
  207. * If it exists - honor that and return that value.
  208. * If not - return NO stating diagnostics is disabled.
  209. */
  210. id diagnosticsEnabledPreferenceObject =
  211. [self.userDefaults objectForKey:kFPRDiagnosticsUserPreference];
  212. if (diagnosticsEnabledPreferenceObject) {
  213. enabled = [diagnosticsEnabledPreferenceObject boolValue];
  214. } else {
  215. id diagnosticsEnabledObject = [self objectForInfoDictionaryKey:kFPRDiagnosticsEnabledPlistKey];
  216. if (diagnosticsEnabledObject) {
  217. enabled = [diagnosticsEnabledObject boolValue];
  218. }
  219. }
  220. return enabled;
  221. }
  222. - (NSSet<NSString *> *)sdkDisabledVersions {
  223. NSMutableSet<NSString *> *disabledVersions = [[NSMutableSet<NSString *> alloc] init];
  224. if (self.remoteConfigFlags) {
  225. NSSet<NSString *> *sdkDisabledVersions =
  226. [self.remoteConfigFlags sdkDisabledVersionsWithDefaultValue:[disabledVersions copy]];
  227. if (sdkDisabledVersions.count > 0) {
  228. [disabledVersions addObjectsFromArray:[sdkDisabledVersions allObjects]];
  229. }
  230. }
  231. return [disabledVersions copy];
  232. }
  233. - (int)logSource {
  234. /**
  235. * Order of preference of returning the log source.
  236. * If it is an autopush build (based on environment variable), always return
  237. * LogRequest_LogSource_FireperfAutopush (461). If there is a recent value of remote config fetch,
  238. * honor that value. If logSource cached value (NSUserDefaults value) exists, honor that. Fallback
  239. * to the default value LogRequest_LogSource_Fireperf (462).
  240. */
  241. int logSource = 462;
  242. NSDictionary<NSString *, NSString *> *environment = [NSProcessInfo processInfo].environment;
  243. if (environment[@"FPR_AUTOPUSH_ENV"] != nil &&
  244. [environment[@"FPR_AUTOPUSH_ENV"] isEqualToString:@"1"]) {
  245. logSource = 461;
  246. } else {
  247. if (self.remoteConfigFlags) {
  248. logSource = [self.remoteConfigFlags logSourceWithDefaultValue:462];
  249. }
  250. }
  251. return logSource;
  252. }
  253. #pragma mark - Log sampling configurations.
  254. - (float)logTraceSamplingRate {
  255. float samplingRate = 1.0f;
  256. if (self.remoteConfigFlags) {
  257. float rcSamplingRate = [self.remoteConfigFlags traceSamplingRateWithDefaultValue:samplingRate];
  258. if (rcSamplingRate >= 0) {
  259. samplingRate = rcSamplingRate;
  260. }
  261. }
  262. return samplingRate;
  263. }
  264. - (float)logNetworkSamplingRate {
  265. float samplingRate = 1.0f;
  266. if (self.remoteConfigFlags) {
  267. float rcSamplingRate =
  268. [self.remoteConfigFlags networkRequestSamplingRateWithDefaultValue:samplingRate];
  269. if (rcSamplingRate >= 0) {
  270. samplingRate = rcSamplingRate;
  271. }
  272. }
  273. return samplingRate;
  274. }
  275. #pragma mark - Traces rate limiting configurations.
  276. - (uint32_t)foregroundEventCount {
  277. uint32_t eventCount = 300;
  278. if (self.remoteConfigFlags) {
  279. eventCount =
  280. [self.remoteConfigFlags rateLimitTraceCountInForegroundWithDefaultValue:eventCount];
  281. }
  282. return eventCount;
  283. }
  284. - (uint32_t)foregroundEventTimeLimit {
  285. uint32_t timeLimit = 600;
  286. if (self.remoteConfigFlags) {
  287. timeLimit = [self.remoteConfigFlags rateLimitTimeDurationWithDefaultValue:timeLimit];
  288. }
  289. uint32_t timeLimitInMinutes = timeLimit / 60;
  290. return timeLimitInMinutes;
  291. }
  292. - (uint32_t)backgroundEventCount {
  293. uint32_t eventCount = 30;
  294. if (self.remoteConfigFlags) {
  295. eventCount =
  296. [self.remoteConfigFlags rateLimitTraceCountInBackgroundWithDefaultValue:eventCount];
  297. }
  298. return eventCount;
  299. }
  300. - (uint32_t)backgroundEventTimeLimit {
  301. uint32_t timeLimit = 600;
  302. if (self.remoteConfigFlags) {
  303. timeLimit = [self.remoteConfigFlags rateLimitTimeDurationWithDefaultValue:timeLimit];
  304. }
  305. uint32_t timeLimitInMinutes = timeLimit / 60;
  306. return timeLimitInMinutes;
  307. }
  308. #pragma mark - Network requests rate limiting configurations.
  309. - (uint32_t)foregroundNetworkEventCount {
  310. uint32_t eventCount = 700;
  311. if (self.remoteConfigFlags) {
  312. eventCount = [self.remoteConfigFlags
  313. rateLimitNetworkRequestCountInForegroundWithDefaultValue:eventCount];
  314. }
  315. return eventCount;
  316. }
  317. - (uint32_t)foregroundNetworkEventTimeLimit {
  318. uint32_t timeLimit = 600;
  319. if (self.remoteConfigFlags) {
  320. timeLimit = [self.remoteConfigFlags rateLimitTimeDurationWithDefaultValue:timeLimit];
  321. }
  322. uint32_t timeLimitInMinutes = timeLimit / 60;
  323. return timeLimitInMinutes;
  324. }
  325. - (uint32_t)backgroundNetworkEventCount {
  326. uint32_t eventCount = 70;
  327. if (self.remoteConfigFlags) {
  328. eventCount = [self.remoteConfigFlags
  329. rateLimitNetworkRequestCountInBackgroundWithDefaultValue:eventCount];
  330. }
  331. return eventCount;
  332. }
  333. - (uint32_t)backgroundNetworkEventTimeLimit {
  334. uint32_t timeLimit = 600;
  335. if (self.remoteConfigFlags) {
  336. timeLimit = [self.remoteConfigFlags rateLimitTimeDurationWithDefaultValue:timeLimit];
  337. }
  338. uint32_t timeLimitInMinutes = timeLimit / 60;
  339. return timeLimitInMinutes;
  340. }
  341. #pragma mark - Sessions feature related configurations.
  342. - (float_t)sessionsSamplingPercentage {
  343. float samplingPercentage = 1.0f; // One Percent.
  344. if (self.remoteConfigFlags) {
  345. float rcSamplingRate =
  346. [self.remoteConfigFlags sessionSamplingRateWithDefaultValue:(samplingPercentage / 100)];
  347. if (rcSamplingRate >= 0) {
  348. samplingPercentage = rcSamplingRate * 100;
  349. }
  350. }
  351. id plistObject = [self objectForInfoDictionaryKey:@"sessionsSamplingPercentage"];
  352. if (plistObject) {
  353. samplingPercentage = [plistObject floatValue];
  354. }
  355. return samplingPercentage;
  356. }
  357. - (uint32_t)maxSessionLengthInMinutes {
  358. uint32_t sessionLengthInMinutes = 240;
  359. if (self.remoteConfigFlags) {
  360. sessionLengthInMinutes =
  361. [self.remoteConfigFlags sessionMaxDurationWithDefaultValue:sessionLengthInMinutes];
  362. }
  363. // If the session max length gets set to 0, default it to 240 minutes.
  364. if (sessionLengthInMinutes == 0) {
  365. return 240;
  366. }
  367. return sessionLengthInMinutes;
  368. }
  369. - (uint32_t)cpuSamplingFrequencyInForegroundInMS {
  370. uint32_t samplingFrequency = 100;
  371. if (self.remoteConfigFlags) {
  372. samplingFrequency = [self.remoteConfigFlags
  373. sessionGaugeCPUCaptureFrequencyInForegroundWithDefaultValue:samplingFrequency];
  374. }
  375. return samplingFrequency;
  376. }
  377. - (uint32_t)cpuSamplingFrequencyInBackgroundInMS {
  378. uint32_t samplingFrequency = 0;
  379. if (self.remoteConfigFlags) {
  380. samplingFrequency = [self.remoteConfigFlags
  381. sessionGaugeCPUCaptureFrequencyInBackgroundWithDefaultValue:samplingFrequency];
  382. }
  383. return samplingFrequency;
  384. }
  385. - (uint32_t)memorySamplingFrequencyInForegroundInMS {
  386. uint32_t samplingFrequency = 100;
  387. if (self.remoteConfigFlags) {
  388. samplingFrequency = [self.remoteConfigFlags
  389. sessionGaugeMemoryCaptureFrequencyInForegroundWithDefaultValue:samplingFrequency];
  390. }
  391. return samplingFrequency;
  392. }
  393. - (uint32_t)memorySamplingFrequencyInBackgroundInMS {
  394. uint32_t samplingFrequency = 0;
  395. if (self.remoteConfigFlags) {
  396. samplingFrequency = [self.remoteConfigFlags
  397. sessionGaugeMemoryCaptureFrequencyInBackgroundWithDefaultValue:samplingFrequency];
  398. }
  399. return samplingFrequency;
  400. }
  401. #pragma mark - Google Data Transport related configurations.
  402. - (float_t)fllTransportPercentage {
  403. // Order of precedence is:
  404. //
  405. // Any RC config flags exists?
  406. // -> Yes
  407. // -> If Transport flag exists, honor the value (active rollout scenario)
  408. // -> Otherwise, send to Fll (deprecation scenario)
  409. // -> No
  410. // -> Send to clearcut (onboarding scenario)
  411. //
  412. // If a PList override also exists than that takes the priority
  413. // By default send to Clearcut
  414. float transportPercentage = 0.0f; // Range [0 - 100]
  415. if (self.remoteConfigFlags && [self.remoteConfigFlags containsRemoteConfigFlags]) {
  416. // If Transport flag exists, honor the value (active rollout scenario)
  417. // Otherwise, send to Fll (deprecation scenario)
  418. transportPercentage = [self.remoteConfigFlags fllTransportPercentageWithDefaultValue:100.0f];
  419. }
  420. // If a PList override also exists than that takes the priority
  421. id plistObject = [self objectForInfoDictionaryKey:@"fllTransportPercentage"];
  422. if (plistObject) {
  423. transportPercentage = [plistObject floatValue];
  424. }
  425. return transportPercentage;
  426. }
  427. @end