AutorÃa | Ultima modificación | Ver Log |
// Copyright 2020 Google LLC//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.#import "FirebasePerformance/Sources/Configurations/FPRRemoteConfigFlags.h"#import "FirebasePerformance/Sources/Configurations/FPRConfigurations+Private.h"#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"#import "FirebasePerformance/Sources/Configurations/FPRRemoteConfigFlags+Private.h"#import "FirebasePerformance/Sources/FPRConsoleLogger.h"#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"#define ONE_DAY_SECONDS 24 * 60 * 60static NSDate *FPRAppStartTime = nil;typedef NS_ENUM(NSInteger, FPRConfigValueType) {// Config value type String.FPRConfigValueTypeString,// Config value type Bool.FPRConfigValueTypeBool,// Config value type Integer.FPRConfigValueTypeInteger,// Config value type Float.FPRConfigValueTypeFloat,};@interface FPRRemoteConfigFlags ()/** @brief Represents if a fetch is currently in progress. */@property(atomic) BOOL fetchInProgress;/** @brief Dictionary of different config keys and value types. */@property(nonatomic) NSDictionary<NSString *, NSNumber *> *configKeys;/** @brief Last time the configs were cached. */@property(nonatomic) NSDate *lastCachedTime;@end@implementation FPRRemoteConfigFlags+ (void)load {FPRAppStartTime = [NSDate date];}+ (nullable instancetype)sharedInstance {static FPRRemoteConfigFlags *instance = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{FIRRemoteConfig *rc = [FIRRemoteConfig remoteConfigWithFIRNamespace:@"fireperf"app:[FIRApp defaultApp]];instance = [[FPRRemoteConfigFlags alloc] initWithRemoteConfig:rc];});return instance;}- (instancetype)initWithRemoteConfig:(FIRRemoteConfig *)config {self = [super init];if (self) {_fprRemoteConfig = config;_userDefaults = [FPRConfigurations sharedInstance].userDefaults;self.fetchInProgress = NO;// Set the overall delay to 5+random(25) making the config fetch delay at a max of 30 secondsself.applicationStartTime = FPRAppStartTime;self.appStartConfigFetchDelayInSeconds =kFPRMinAppStartConfigFetchDelayInSeconds + arc4random_uniform(25);NSMutableDictionary<NSString *, NSNumber *> *keysToCache =[[NSMutableDictionary<NSString *, NSNumber *> alloc] init];[keysToCache setObject:@(FPRConfigValueTypeInteger) forKey:@"fpr_log_source"];[keysToCache setObject:@(FPRConfigValueTypeBool) forKey:@"fpr_enabled"];[keysToCache setObject:@(FPRConfigValueTypeString) forKey:@"fpr_disabled_ios_versions"];[keysToCache setObject:@(FPRConfigValueTypeInteger) forKey:@"fpr_rl_time_limit_sec"];[keysToCache setObject:@(FPRConfigValueTypeInteger) forKey:@"fpr_rl_trace_event_count_fg"];[keysToCache setObject:@(FPRConfigValueTypeInteger) forKey:@"fpr_rl_trace_event_count_bg"];[keysToCache setObject:@(FPRConfigValueTypeInteger)forKey:@"fpr_rl_network_request_event_count_fg"];[keysToCache setObject:@(FPRConfigValueTypeInteger)forKey:@"fpr_rl_network_request_event_count_bg"];[keysToCache setObject:@(FPRConfigValueTypeFloat) forKey:@"fpr_vc_trace_sampling_rate"];[keysToCache setObject:@(FPRConfigValueTypeFloat)forKey:@"fpr_vc_network_request_sampling_rate"];[keysToCache setObject:@(FPRConfigValueTypeFloat) forKey:@"fpr_vc_session_sampling_rate"];[keysToCache setObject:@(FPRConfigValueTypeInteger)forKey:@"fpr_session_gauge_cpu_capture_frequency_fg_ms"];[keysToCache setObject:@(FPRConfigValueTypeInteger)forKey:@"fpr_session_gauge_cpu_capture_frequency_bg_ms"];[keysToCache setObject:@(FPRConfigValueTypeInteger)forKey:@"fpr_session_gauge_memory_capture_frequency_fg_ms"];[keysToCache setObject:@(FPRConfigValueTypeInteger)forKey:@"fpr_session_gauge_memory_capture_frequency_bg_ms"];[keysToCache setObject:@(FPRConfigValueTypeInteger) forKey:@"fpr_session_max_duration_min"];[keysToCache setObject:@(FPRConfigValueTypeInteger) forKey:@"fpr_prewarm_detection"];self.configKeys = [keysToCache copy];[self update];}return self;}- (void)update {// If a fetch is already happening, do not attempt a fetch.if (self.fetchInProgress) {return;}NSTimeInterval timeIntervalSinceLastFetch =[self.fprRemoteConfig.lastFetchTime timeIntervalSinceNow];NSTimeInterval timeSinceAppStart = [self.applicationStartTime timeIntervalSinceNow];if ((ABS(timeSinceAppStart) > self.appStartConfigFetchDelayInSeconds) &&(!self.fprRemoteConfig.lastFetchTime ||ABS(timeIntervalSinceLastFetch) > kFPRConfigFetchIntervalInSeconds)) {self.fetchInProgress = YES;[self.fprRemoteConfigfetchAndActivateWithCompletionHandler:^(FIRRemoteConfigFetchAndActivateStatus status,NSError *_Nullable error) {self.lastFetchStatus = self.fprRemoteConfig.lastFetchStatus;if (status == FIRRemoteConfigFetchAndActivateStatusError) {FPRLogError(kFPRConfigurationFetchFailure, @"Unable to fetch configurations.");} else {self.lastFetchedTime = self.fprRemoteConfig.lastFetchTime;// If a fetch was successful,// 1. Clear the old cache[self resetCache];// 2. Cache the new config values[self cacheConfigValues];}self.fetchInProgress = NO;}];} else if (self.fprRemoteConfig.lastFetchTime) {// Update the last fetched time to know that remote config fetch has happened in the past.self.lastFetchedTime = self.fprRemoteConfig.lastFetchTime;}}#pragma mark - Util methods.- (void)resetCache {[self.configKeysenumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *valueType, BOOL *stop) {NSString *cacheKey = [NSString stringWithFormat:@"%@.%@", kFPRConfigPrefix, key];[self.userDefaults removeObjectForKey:cacheKey];}];}- (void)cacheConfigValues {[self.configKeysenumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *valueType, BOOL *stop) {FIRRemoteConfigValue *rcValue = [self.fprRemoteConfig configValueForKey:key];// Cache only values that comes from remote.if (rcValue != nil && rcValue.source == FIRRemoteConfigSourceRemote) {FPRConfigValueType configValueType = [valueType integerValue];NSString *cacheKey = [NSString stringWithFormat:@"%@.%@", kFPRConfigPrefix, key];if (configValueType == FPRConfigValueTypeInteger) {NSInteger integerValue = [[rcValue numberValue] integerValue];[self.userDefaults setInteger:integerValue forKey:cacheKey];} else if (configValueType == FPRConfigValueTypeFloat) {float floatValue = [[rcValue numberValue] floatValue];[self.userDefaults setFloat:floatValue forKey:cacheKey];} else if (configValueType == FPRConfigValueTypeBool) {BOOL boolValue = [rcValue boolValue];[self.userDefaults setBool:boolValue forKey:cacheKey];} else if (configValueType == FPRConfigValueTypeString) {NSString *stringValue = [rcValue stringValue];[self.userDefaults setObject:stringValue forKey:cacheKey];}self.lastCachedTime = [NSDate date];}}];}- (id)cachedValueForConfigFlag:(NSString *)configFlag {// If the cached value is too old, return nil.if (ABS([self.lastFetchedTime timeIntervalSinceNow]) > 7 * ONE_DAY_SECONDS) {return nil;}NSString *cacheKey = [NSString stringWithFormat:@"%@.%@", kFPRConfigPrefix, configFlag];id cachedValueObject = [self.userDefaults objectForKey:cacheKey];return cachedValueObject;}#pragma mark - Config value fetch methods.- (NSString *)getStringValueForFlag:(NSString *)flagName defaultValue:(NSString *)defaultValue {id cachedValueObject = [self cachedValueForConfigFlag:flagName];if ([cachedValueObject isKindOfClass:[NSString class]]) {return (NSString *)cachedValueObject;}return defaultValue;}- (int)getIntValueForFlag:(NSString *)flagName defaultValue:(int)defaultValue {id cachedValueObject = [self cachedValueForConfigFlag:flagName];if (cachedValueObject) {return [cachedValueObject intValue];}return defaultValue;}- (float)getFloatValueForFlag:(NSString *)flagName defaultValue:(float)defaultValue {id cachedValueObject = [self cachedValueForConfigFlag:flagName];if (cachedValueObject) {return [cachedValueObject floatValue];}return defaultValue;}- (BOOL)getBoolValueForFlag:(NSString *)flagName defaultValue:(BOOL)defaultValue {id cachedValueObject = [self cachedValueForConfigFlag:flagName];if (cachedValueObject) {return [cachedValueObject boolValue];}return defaultValue;}#pragma mark - Configuration methods.- (int)logSourceWithDefaultValue:(int)logSource {return [self getIntValueForFlag:@"fpr_log_source" defaultValue:logSource];}- (BOOL)performanceSDKEnabledWithDefaultValue:(BOOL)sdkEnabled {/* Order of preference:* 1. If remote config fetch was a failure, return NO.* 2. If the fetch was successful, but RC does not have the value (not a remote value),* return YES.* 3. Else, use the value from RC.*/if (self.lastFetchStatus == FIRRemoteConfigFetchStatusFailure) {return NO;}return [self getBoolValueForFlag:@"fpr_enabled" defaultValue:sdkEnabled];}- (NSSet<NSString *> *)sdkDisabledVersionsWithDefaultValue:(NSSet<NSString *> *)sdkVersions {NSMutableSet<NSString *> *disabledVersions = [[NSMutableSet<NSString *> alloc] init];NSString *sdkVersionsString = [[self getStringValueForFlag:@"fpr_disabled_ios_versions"defaultValue:@""]stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];if (sdkVersionsString.length > 0) {NSArray<NSString *> *sdkVersionStrings = [sdkVersionsString componentsSeparatedByString:@";"];for (NSString *sdkVersionString in sdkVersionStrings) {NSString *trimmedString = [sdkVersionStringstringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];if (trimmedString.length > 0) {[disabledVersions addObject:trimmedString];}}} else {return sdkVersions;}return [disabledVersions copy];}#pragma mark - Rate limiting flags- (int)rateLimitTimeDurationWithDefaultValue:(int)durationInSeconds {return [self getIntValueForFlag:@"fpr_rl_time_limit_sec" defaultValue:durationInSeconds];}- (int)rateLimitTraceCountInForegroundWithDefaultValue:(int)eventCount {return [self getIntValueForFlag:@"fpr_rl_trace_event_count_fg" defaultValue:eventCount];}- (int)rateLimitTraceCountInBackgroundWithDefaultValue:(int)eventCount {return [self getIntValueForFlag:@"fpr_rl_trace_event_count_bg" defaultValue:eventCount];}- (int)rateLimitNetworkRequestCountInForegroundWithDefaultValue:(int)eventCount {return [self getIntValueForFlag:@"fpr_rl_network_request_event_count_fg" defaultValue:eventCount];}- (int)rateLimitNetworkRequestCountInBackgroundWithDefaultValue:(int)eventCount {return [self getIntValueForFlag:@"fpr_rl_network_request_event_count_bg" defaultValue:eventCount];}#pragma mark - Sampling flags- (float)traceSamplingRateWithDefaultValue:(float)samplingRate {return [self getFloatValueForFlag:@"fpr_vc_trace_sampling_rate" defaultValue:samplingRate];}- (float)networkRequestSamplingRateWithDefaultValue:(float)samplingRate {return [self getFloatValueForFlag:@"fpr_vc_network_request_sampling_rate"defaultValue:samplingRate];}#pragma mark - Session flags- (float)sessionSamplingRateWithDefaultValue:(float)samplingRate {return [self getFloatValueForFlag:@"fpr_vc_session_sampling_rate" defaultValue:samplingRate];}- (int)sessionGaugeCPUCaptureFrequencyInForegroundWithDefaultValue:(int)defaultFrequency {return [self getIntValueForFlag:@"fpr_session_gauge_cpu_capture_frequency_fg_ms"defaultValue:defaultFrequency];}- (int)sessionGaugeCPUCaptureFrequencyInBackgroundWithDefaultValue:(int)defaultFrequency {return [self getIntValueForFlag:@"fpr_session_gauge_cpu_capture_frequency_bg_ms"defaultValue:defaultFrequency];}- (int)sessionGaugeMemoryCaptureFrequencyInForegroundWithDefaultValue:(int)defaultFrequency {return [self getIntValueForFlag:@"fpr_session_gauge_memory_capture_frequency_fg_ms"defaultValue:defaultFrequency];}- (int)sessionGaugeMemoryCaptureFrequencyInBackgroundWithDefaultValue:(int)defaultFrequency {return [self getIntValueForFlag:@"fpr_session_gauge_memory_capture_frequency_bg_ms"defaultValue:defaultFrequency];}- (int)sessionMaxDurationWithDefaultValue:(int)maxDurationInMinutes {return [self getIntValueForFlag:@"fpr_session_max_duration_min"defaultValue:maxDurationInMinutes];}@end