firebase log level

This commit is contained in:
oscarz
2024-08-29 18:25:13 +08:00
parent 8500300d18
commit 27c160beaf
1165 changed files with 122916 additions and 1 deletions

View File

@ -0,0 +1,156 @@
/*
* Copyright 2017 Google
*
* 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 <FirebaseCore/FIRApp.h>
@class FIRComponentContainer;
@class FIRHeartbeatLogger;
@protocol FIRLibrary;
/**
* The internal interface to `FirebaseApp`. This is meant for first-party integrators, who need to
* receive `FirebaseApp` notifications, log info about the success or failure of their
* configuration, and access other internal functionality of `FirebaseApp`.
*/
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, FIRConfigType) {
FIRConfigTypeCore = 1,
FIRConfigTypeSDK = 2,
};
extern NSString *const kFIRDefaultAppName;
extern NSString *const kFIRAppReadyToConfigureSDKNotification;
extern NSString *const kFIRAppDeleteNotification;
extern NSString *const kFIRAppIsDefaultAppKey;
extern NSString *const kFIRAppNameKey;
extern NSString *const kFIRGoogleAppIDKey;
extern NSString *const kFirebaseCoreErrorDomain;
/**
* The format string for the `UserDefaults` key used for storing the data collection enabled flag.
* This includes formatting to append the `FirebaseApp`'s name.
*/
extern NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat;
/**
* The plist key used for storing the data collection enabled flag.
*/
extern NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey;
/** @var FirebaseAuthStateDidChangeInternalNotification
@brief The name of the @c NotificationCenter notification which is posted when the auth state
changes (e.g. a new token has been produced, a user logs in or out). The object parameter of
the notification is a dictionary possibly containing the key:
@c FirebaseAuthStateDidChangeInternalNotificationTokenKey (the new access token.) If it does not
contain this key it indicates a sign-out event took place.
*/
extern NSString *const FIRAuthStateDidChangeInternalNotification;
/** @var FirebaseAuthStateDidChangeInternalNotificationTokenKey
@brief A key present in the dictionary object parameter of the
@c FirebaseAuthStateDidChangeInternalNotification notification. The value associated with this
key will contain the new access token.
*/
extern NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey;
/** @var FirebaseAuthStateDidChangeInternalNotificationAppKey
@brief A key present in the dictionary object parameter of the
@c FirebaseAuthStateDidChangeInternalNotification notification. The value associated with this
key will contain the FirebaseApp associated with the auth instance.
*/
extern NSString *const FIRAuthStateDidChangeInternalNotificationAppKey;
/** @var FirebaseAuthStateDidChangeInternalNotificationUIDKey
@brief A key present in the dictionary object parameter of the
@c FirebaseAuthStateDidChangeInternalNotification notification. The value associated with this
key will contain the new user's UID (or nil if there is no longer a user signed in).
*/
extern NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey;
@interface FIRApp ()
/**
* A flag indicating if this is the default app (has the default app name).
*/
@property(nonatomic, readonly) BOOL isDefaultApp;
/**
* The container of interop SDKs for this app.
*/
@property(nonatomic) FIRComponentContainer *container;
/**
* The heartbeat logger associated with this app.
*
* Firebase apps have a 1:1 relationship with heartbeat loggers.
*/
@property(readonly) FIRHeartbeatLogger *heartbeatLogger;
/**
* Checks if the default app is configured without trying to configure it.
*/
+ (BOOL)isDefaultAppConfigured;
/**
* Registers a given third-party library with the given version number to be reported for
* analytics.
*
* @param name Name of the library.
* @param version Version of the library.
*/
+ (void)registerLibrary:(nonnull NSString *)name withVersion:(nonnull NSString *)version;
/**
* Registers a given internal library to be reported for analytics.
*
* @param library Optional parameter for component registration.
* @param name Name of the library.
*/
+ (void)registerInternalLibrary:(nonnull Class<FIRLibrary>)library
withName:(nonnull NSString *)name;
/**
* Registers a given internal library with the given version number to be reported for
* analytics. This should only be used for non-Firebase libraries that have their own versioning
* scheme.
*
* @param library Optional parameter for component registration.
* @param name Name of the library.
* @param version Version of the library.
*/
+ (void)registerInternalLibrary:(nonnull Class<FIRLibrary>)library
withName:(nonnull NSString *)name
withVersion:(nonnull NSString *)version;
/**
* A concatenated string representing all the third-party libraries and version numbers.
*/
+ (NSString *)firebaseUserAgent;
/**
* Can be used by the unit tests in each SDK to reset `FirebaseApp`. This method is thread unsafe.
*/
+ (void)resetApps;
/**
* Can be used by the unit tests in each SDK to set customized options.
*/
- (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,84 @@
/*
* Copyright 2018 Google
*
* 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 <Foundation/Foundation.h>
@class FIRApp;
@class FIRComponentContainer;
NS_ASSUME_NONNULL_BEGIN
/// Provides a system to clean up cached instances returned from the component system.
NS_SWIFT_NAME(ComponentLifecycleMaintainer)
@protocol FIRComponentLifecycleMaintainer
/// The associated app will be deleted, clean up any resources as they are about to be deallocated.
- (void)appWillBeDeleted:(FIRApp *)app;
@end
typedef _Nullable id (^FIRComponentCreationBlock)(FIRComponentContainer *container,
BOOL *isCacheable)
NS_SWIFT_NAME(ComponentCreationBlock);
/// Describes the timing of instantiation. Note: new components should default to lazy unless there
/// is a strong reason to be eager.
typedef NS_ENUM(NSInteger, FIRInstantiationTiming) {
FIRInstantiationTimingLazy,
FIRInstantiationTimingAlwaysEager,
FIRInstantiationTimingEagerInDefaultApp
} NS_SWIFT_NAME(InstantiationTiming);
/// A component that can be used from other Firebase SDKs.
NS_SWIFT_NAME(Component)
@interface FIRComponent : NSObject
/// The protocol describing functionality provided from the `Component`.
@property(nonatomic, strong, readonly) Protocol *protocol;
/// The timing of instantiation.
@property(nonatomic, readonly) FIRInstantiationTiming instantiationTiming;
/// A block to instantiate an instance of the component with the appropriate dependencies.
@property(nonatomic, copy, readonly) FIRComponentCreationBlock creationBlock;
// There's an issue with long NS_SWIFT_NAMES that causes compilation to fail, disable clang-format
// for the next two methods.
// clang-format off
/// Creates a component with no dependencies that will be lazily initialized.
+ (instancetype)componentWithProtocol:(Protocol *)protocol
creationBlock:(FIRComponentCreationBlock)creationBlock
NS_SWIFT_NAME(init(_:creationBlock:));
/// Creates a component to be registered with the component container.
///
/// @param protocol - The protocol describing functionality provided by the component.
/// @param instantiationTiming - When the component should be initialized. Use .lazy unless there's
/// a good reason to be instantiated earlier.
/// @param creationBlock - A block to instantiate the component with a container, and if
/// @return A component that can be registered with the component container.
+ (instancetype)componentWithProtocol:(Protocol *)protocol
instantiationTiming:(FIRInstantiationTiming)instantiationTiming
creationBlock:(FIRComponentCreationBlock)creationBlock
NS_SWIFT_NAME(init(_:instantiationTiming:creationBlock:));
// clang-format on
/// Unavailable.
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,45 @@
/*
* Copyright 2018 Google
*
* 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// A type-safe macro to retrieve a component from a container. This should be used to retrieve
/// components instead of using the container directly.
#define FIR_COMPONENT(type, container) \
[FIRComponentType<id<type>> instanceForProtocol:@protocol(type) inContainer:container]
@class FIRApp;
/// A container that holds different components that are registered via the
/// `registerAsComponentRegistrant` call. These classes should conform to `ComponentRegistrant`
/// in order to properly register components for Core.
NS_SWIFT_NAME(FirebaseComponentContainer)
@interface FIRComponentContainer : NSObject
/// A weak reference to the app that an instance of the container belongs to.
@property(nonatomic, weak, readonly) FIRApp *app;
// TODO: See if we can get improved type safety here.
/// A Swift only API for fetching an instance since the top macro isn't available.
- (nullable id)__instanceForProtocol:(Protocol *)protocol NS_SWIFT_NAME(instance(for:));
/// Unavailable. Use the `container` property on `FirebaseApp`.
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,35 @@
/*
* Copyright 2018 Google
*
* 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 <Foundation/Foundation.h>
@class FIRComponentContainer;
NS_ASSUME_NONNULL_BEGIN
/// Do not use directly. A placeholder type in order to provide a macro that will warn users of
/// mis-matched protocols.
NS_SWIFT_NAME(ComponentType)
@interface FIRComponentType<__covariant T> : NSObject
/// Do not use directly. A factory method to retrieve an instance that provides a specific
/// functionality.
+ (nullable T)instanceForProtocol:(Protocol *)protocol
inContainer:(FIRComponentContainer *)container;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,90 @@
// Copyright 2021 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#ifndef FIREBASE_BUILD_CMAKE
@class FIRHeartbeatsPayload;
#endif // FIREBASE_BUILD_CMAKE
/// Enum representing different daily heartbeat codes.
/// This enum is only used by clients using platform logging V1. This is because
/// the V1 payload only supports a single daily heartbeat.
typedef NS_ENUM(NSInteger, FIRDailyHeartbeatCode) {
/// Represents the absence of a daily heartbeat.
FIRDailyHeartbeatCodeNone = 0,
/// Represents the presence of a daily heartbeat.
FIRDailyHeartbeatCodeSome = 2,
};
@protocol FIRHeartbeatLoggerProtocol <NSObject>
/// Asynchronously logs a heartbeat.
- (void)log;
#ifndef FIREBASE_BUILD_CMAKE
/// Return the headerValue for the HeartbeatLogger.
- (NSString *_Nullable)headerValue;
#endif // FIREBASE_BUILD_CMAKE
/// Gets the heartbeat code for today.
- (FIRDailyHeartbeatCode)heartbeatCodeForToday;
@end
#ifndef FIREBASE_BUILD_CMAKE
/// Returns a nullable string header value from a given heartbeats payload.
///
/// This API returns `nil` when the given heartbeats payload is considered empty.
///
/// @param heartbeatsPayload The heartbeats payload.
NSString *_Nullable FIRHeaderValueFromHeartbeatsPayload(FIRHeartbeatsPayload *heartbeatsPayload);
#endif // FIREBASE_BUILD_CMAKE
/// A thread safe, synchronized object that logs and flushes platform logging info.
@interface FIRHeartbeatLogger : NSObject <FIRHeartbeatLoggerProtocol>
/// Designated initializer.
///
/// @param appID The app ID that this heartbeat logger corresponds to.
- (instancetype)initWithAppID:(NSString *)appID;
/// Asynchronously logs a new heartbeat corresponding to the Firebase User Agent, if needed.
///
/// @note This API is thread-safe.
- (void)log;
#ifndef FIREBASE_BUILD_CMAKE
/// Flushes heartbeats from storage into a structured payload of heartbeats.
///
/// This API is for clients using platform logging V2.
///
/// @note This API is thread-safe.
/// @return A payload of heartbeats.
- (FIRHeartbeatsPayload *)flushHeartbeatsIntoPayload;
#endif // FIREBASE_BUILD_CMAKE
/// Gets today's corresponding heartbeat code.
///
/// This API is for clients using platform logging V1.
///
/// @note This API is thread-safe.
/// @return Heartbeat code indicating whether or not there is an unsent global heartbeat.
- (FIRDailyHeartbeatCode)heartbeatCodeForToday;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,39 @@
/*
* Copyright 2018 Google
*
* 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.
*/
#ifndef FIRLibrary_h
#define FIRLibrary_h
#import <Foundation/Foundation.h>
@class FIRApp;
@class FIRComponent;
NS_ASSUME_NONNULL_BEGIN
/// Provide an interface to register a library for userAgent logging and availability to others.
NS_SWIFT_NAME(Library)
@protocol FIRLibrary
/// Returns one or more Components that will be registered in
/// FirebaseApp and participate in dependency resolution and injection.
+ (NSArray<FIRComponent *> *)componentsToRegister;
@end
NS_ASSUME_NONNULL_END
#endif /* FIRLibrary_h */

View File

@ -0,0 +1,148 @@
/*
* Copyright 2017 Google
*
* 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 <Foundation/Foundation.h>
#import <FirebaseCore/FIRLoggerLevel.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The Firebase services used in Firebase logger.
*/
typedef NSString *const FIRLoggerService;
extern NSString *const kFIRLoggerAnalytics;
extern NSString *const kFIRLoggerCrash;
extern NSString *const kFIRLoggerCore;
extern NSString *const kFIRLoggerRemoteConfig;
/**
* The key used to store the logger's error count.
*/
extern NSString *const kFIRLoggerErrorCountKey;
/**
* The key used to store the logger's warning count.
*/
extern NSString *const kFIRLoggerWarningCountKey;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Enables or disables Analytics debug mode.
* If set to true, the logging level for Analytics will be set to FirebaseLoggerLevelDebug.
* Enabling the debug mode has no effect if the app is running from App Store.
* (required) analytics debug mode flag.
*/
void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode);
/**
* Gets the current FIRLoggerLevel.
*/
FIRLoggerLevel FIRGetLoggerLevel(void);
/**
* Changes the default logging level of FirebaseLoggerLevelNotice to a user-specified level.
* The default level cannot be set above FirebaseLoggerLevelNotice if the app is running from App
* Store. (required) log level (one of the FirebaseLoggerLevel enum values).
*/
void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel);
/**
* Checks if the specified logger level is loggable given the current settings.
* (required) log level (one of the FirebaseLoggerLevel enum values).
* (required) whether or not this function is called from the Analytics component.
*/
BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel, BOOL analyticsComponent);
/**
* Logs a message to the Xcode console and the device log. If running from AppStore, will
* not log any messages with a level higher than FirebaseLoggerLevelNotice to avoid log spamming.
* (required) log level (one of the FirebaseLoggerLevel enum values).
* (required) service name of type FirebaseLoggerService.
* (required) message code starting with "I-" which means iOS, followed by a capitalized
* three-character service identifier and a six digit integer message ID that is unique
* within the service.
* An example of the message code is @"I-COR000001".
* (required) message string which can be a format string.
* (optional) variable arguments list obtained from calling va_start, used when message is a format
* string.
*/
extern void FIRLogBasic(FIRLoggerLevel level,
NSString *category,
NSString *messageCode,
NSString *message,
// On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable
// See: http://stackoverflow.com/q/29095469
#if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX
va_list args_ptr
#else
va_list _Nullable args_ptr
#endif
);
/**
* The following functions accept the following parameters in order:
* (required) service name of type FirebaseLoggerService.
* (required) message code starting from "I-" which means iOS, followed by a capitalized
* three-character service identifier and a six digit integer message ID that is unique
* within the service.
* An example of the message code is @"I-COR000001".
* See go/firebase-log-proposal for details.
* (required) message string which can be a format string.
* (optional) the list of arguments to substitute into the format string.
* Example usage:
* FirebaseLogError(kFirebaseLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name);
*/
extern void FIRLogError(NSString *category, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
extern void FIRLogWarning(NSString *category, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
extern void FIRLogNotice(NSString *category, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
extern void FIRLogInfo(NSString *category, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
extern void FIRLogDebug(NSString *category, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
NS_SWIFT_NAME(FirebaseLogger)
@interface FIRLoggerWrapper : NSObject
/// Logs a given message at a given log level.
///
/// - Parameters:
/// - level: The log level to use (defined by `FirebaseLoggerLevel` enum values).
/// - service: The service name of type `FirebaseLoggerService`.
/// - code: The message code. Starting with "I-" which means iOS, followed by a capitalized
/// three-character service identifier and a six digit integer message ID that is unique within
/// the service. An example of the message code is @"I-COR000001".
/// - message: Formatted string to be used as the log's message.
+ (void)logWithLevel:(FIRLoggerLevel)level
service:(NSString *)category
code:(NSString *)code
message:(NSString *)message
__attribute__((__swift_name__("log(level:service:code:message:)")));
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,106 @@
/*
* Copyright 2017 Google
*
* 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 <FirebaseCore/FIROptions.h>
/**
* Keys for the strings in the plist file.
*/
extern NSString *const kFIRAPIKey;
extern NSString *const kFIRTrackingID;
extern NSString *const kFIRGoogleAppID;
extern NSString *const kFIRClientID;
extern NSString *const kFIRGCMSenderID;
extern NSString *const kFIRAndroidClientID;
extern NSString *const kFIRDatabaseURL;
extern NSString *const kFIRStorageBucket;
extern NSString *const kFIRBundleID;
extern NSString *const kFIRProjectID;
/**
* Keys for the plist file name
*/
extern NSString *const kServiceInfoFileName;
extern NSString *const kServiceInfoFileType;
/**
* This header file exposes the initialization of FirebaseOptions to internal use.
*/
@interface FIROptions ()
/**
* `resetDefaultOptions` and `initInternalWithOptionsDictionary` are exposed only for unit tests.
*/
+ (void)resetDefaultOptions;
/**
* Initializes the options with dictionary. The above strings are the keys of the dictionary.
* This is the designated initializer.
*/
- (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)serviceInfoDictionary
NS_DESIGNATED_INITIALIZER;
/**
* `defaultOptions` and `defaultOptionsDictionary` are exposed in order to be used in FirebaseApp
* and other first party services.
*/
+ (FIROptions *)defaultOptions;
+ (NSDictionary *)defaultOptionsDictionary;
/**
* Indicates whether or not Analytics collection was explicitly enabled via a plist flag or at
* runtime.
*/
@property(nonatomic, readonly) BOOL isAnalyticsCollectionExplicitlySet;
/**
* Whether or not Analytics Collection was enabled. Analytics Collection is enabled unless
* explicitly disabled in GoogleService-Info.plist.
*/
@property(nonatomic, readonly) BOOL isAnalyticsCollectionEnabled;
/**
* Whether or not Analytics Collection was completely disabled. If true, then
* isAnalyticsCollectionEnabled will be false.
*/
@property(nonatomic, readonly) BOOL isAnalyticsCollectionDeactivated;
/**
* The version ID of the client library, e.g. @"1100000".
*/
@property(nonatomic, readonly, copy) NSString *libraryVersionID;
/**
* The flag indicating whether this object was constructed with the values in the default plist
* file.
*/
@property(nonatomic) BOOL usingOptionsFromDefaultPlist;
/**
* Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in
* GoogleService-Info.plist.
*/
@property(nonatomic, readonly) BOOL isMeasurementEnabled;
/**
* Whether or not editing is locked. This should occur after `FirebaseOptions` has been set on a
* `FirebaseApp`.
*/
@property(nonatomic, getter=isEditingLocked) BOOL editingLocked;
@end

View File

@ -0,0 +1,24 @@
// 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 FirebaseCore;
#import "FIRAppInternal.h"
#import "FIRComponent.h"
#import "FIRComponentContainer.h"
#import "FIRComponentType.h"
#import "FIRHeartbeatLogger.h"
#import "FIRLibrary.h"
#import "FIRLogger.h"
#import "FIROptionsInternal.h"

View File

@ -0,0 +1,19 @@
// 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.
// An umbrella header, for any other libraries in this repo to access Firebase
// Installations Public headers. Any package manager complexity should be
// handled here.
#import <FirebaseInstallations/FirebaseInstallations.h>

View File

@ -0,0 +1,64 @@
// 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/Protogen/nanopb/perf_metric.nanopb.h"
#import "FirebasePerformance/Sources/Public/FirebasePerformance/FIRTrace.h"
FOUNDATION_EXTERN NSString *__nonnull const kFPRAppStartTraceName;
FOUNDATION_EXTERN NSString *__nonnull const kFPRAppStartStageNameTimeToUI;
FOUNDATION_EXTERN NSString *__nonnull const kFPRAppStartStageNameTimeToFirstDraw;
FOUNDATION_EXTERN NSString *__nonnull const kFPRAppStartStageNameTimeToUserInteraction;
FOUNDATION_EXTERN NSString *__nonnull const kFPRAppTraceNameForegroundSession;
FOUNDATION_EXTERN NSString *__nonnull const kFPRAppTraceNameBackgroundSession;
FOUNDATION_EXTERN NSString *__nonnull const kFPRAppCounterNameTraceEventsRateLimited;
FOUNDATION_EXTERN NSString *__nonnull const kFPRAppCounterNameNetworkTraceEventsRateLimited;
FOUNDATION_EXTERN NSString *__nonnull const kFPRAppCounterNameTraceNotStopped;
/** Different states of the current application. */
typedef NS_ENUM(NSInteger, FPRApplicationState) {
FPRApplicationStateUnknown,
/** Application in foreground. */
FPRApplicationStateForeground,
/** Application in background. */
FPRApplicationStateBackground,
};
/** This class is used to track the app activity and create internal traces to capture the
* performance metrics.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRAppActivityTracker : NSObject
/** The trace that tracks the currently active session of the app. *Do not stop this trace*. This is
* an active trace that needs to be running. Stopping this trace might impact the overall
* performance metrics captured for the active session. All other operations can be performed.
*/
@property(nonatomic, nullable, readonly) FIRTrace *activeTrace;
/** Current running state of the application. */
@property(nonatomic, readonly) FPRApplicationState applicationState;
/** Current network connection type of the application. */
@property(nonatomic, readonly) firebase_perf_v1_NetworkConnectionInfo_NetworkType networkType;
/** Accesses the singleton instance.
* @return Reference to the shared object if successful; <code>nil</code> if not.
*/
+ (nullable instancetype)sharedInstance;
- (nullable instancetype)init NS_UNAVAILABLE;
@end

View File

@ -0,0 +1,334 @@
// 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/AppActivity/FPRAppActivityTracker.h"
#import <Foundation/Foundation.h>
#import <Network/Network.h>
#import <UIKit/UIKit.h>
#import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
#import "FirebasePerformance/Sources/Gauges/CPU/FPRCPUGaugeCollector+Private.h"
#import "FirebasePerformance/Sources/Gauges/FPRGaugeManager.h"
#import "FirebasePerformance/Sources/Gauges/Memory/FPRMemoryGaugeCollector+Private.h"
#import "FirebasePerformance/Sources/Timer/FIRTrace+Internal.h"
#import "FirebasePerformance/Sources/Timer/FIRTrace+Private.h"
static NSDate *appStartTime = nil;
static NSDate *doubleDispatchTime = nil;
static NSDate *applicationDidFinishLaunchTime = nil;
static NSTimeInterval gAppStartMaxValidDuration = 60 * 60; // 60 minutes.
static FPRCPUGaugeData *gAppStartCPUGaugeData = nil;
static FPRMemoryGaugeData *gAppStartMemoryGaugeData = nil;
static BOOL isActivePrewarm = NO;
NSString *const kFPRAppStartTraceName = @"_as";
NSString *const kFPRAppStartStageNameTimeToUI = @"_astui";
NSString *const kFPRAppStartStageNameTimeToFirstDraw = @"_astfd";
NSString *const kFPRAppStartStageNameTimeToUserInteraction = @"_asti";
NSString *const kFPRAppTraceNameForegroundSession = @"_fs";
NSString *const kFPRAppTraceNameBackgroundSession = @"_bs";
NSString *const kFPRAppCounterNameTraceEventsRateLimited = @"_fstec";
NSString *const kFPRAppCounterNameNetworkTraceEventsRateLimited = @"_fsntc";
NSString *const kFPRAppCounterNameTraceNotStopped = @"_tsns";
NSString *const kFPRAppCounterNameActivePrewarm = @"_fsapc";
@interface FPRAppActivityTracker ()
/** The foreground session trace. Will be set only when the app is in the foreground. */
@property(nonatomic, readwrite) FIRTrace *foregroundSessionTrace;
/** The background session trace. Will be set only when the app is in the background. */
@property(nonatomic, readwrite) FIRTrace *backgroundSessionTrace;
/** Current running state of the application. */
@property(nonatomic, readwrite) FPRApplicationState applicationState;
/** Current network connection type of the application. */
@property(nonatomic, readwrite) firebase_perf_v1_NetworkConnectionInfo_NetworkType networkType;
/** Network monitor object to track network movements. */
@property(nonatomic, readwrite) nw_path_monitor_t monitor;
/** Queue used to track the network monitoring changes. */
@property(nonatomic, readwrite) dispatch_queue_t monitorQueue;
/** Trace to measure the app start performance. */
@property(nonatomic) FIRTrace *appStartTrace;
/** Tracks if the gauge metrics are dispatched. */
@property(nonatomic) BOOL appStartGaugeMetricDispatched;
/** Firebase Performance Configuration object */
@property(nonatomic) FPRConfigurations *configurations;
/** Starts tracking app active sessions. */
- (void)startAppActivityTracking;
@end
@implementation FPRAppActivityTracker
+ (void)load {
// This is an approximation of the app start time.
appStartTime = [NSDate date];
// When an app is prewarmed, Apple sets env variable ActivePrewarm to 1, then the env variable is
// deleted after didFinishLaunching
isActivePrewarm = [NSProcessInfo.processInfo.environment[@"ActivePrewarm"] isEqualToString:@"1"];
gAppStartCPUGaugeData = fprCollectCPUMetric();
gAppStartMemoryGaugeData = fprCollectMemoryMetric();
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowDidBecomeVisible:)
name:UIWindowDidBecomeVisibleNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidFinishLaunching:)
name:UIApplicationDidFinishLaunchingNotification
object:nil];
}
+ (void)windowDidBecomeVisible:(NSNotification *)notification {
FPRAppActivityTracker *activityTracker = [self sharedInstance];
[activityTracker startAppActivityTracking];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIWindowDidBecomeVisibleNotification
object:nil];
}
+ (void)applicationDidFinishLaunching:(NSNotification *)notification {
applicationDidFinishLaunchTime = [NSDate date];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidFinishLaunchingNotification
object:nil];
}
+ (instancetype)sharedInstance {
static FPRAppActivityTracker *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] initAppActivityTracker];
});
return instance;
}
/**
* Custom initializer to create an app activity tracker.
*/
- (instancetype)initAppActivityTracker {
self = [super init];
if (self != nil) {
_applicationState = FPRApplicationStateUnknown;
_appStartGaugeMetricDispatched = NO;
_configurations = [FPRConfigurations sharedInstance];
[self startTrackingNetwork];
}
return self;
}
- (void)startAppActivityTracking {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appDidBecomeActiveNotification:)
name:UIApplicationDidBecomeActiveNotification
object:[UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appWillResignActiveNotification:)
name:UIApplicationWillResignActiveNotification
object:[UIApplication sharedApplication]];
}
- (FIRTrace *)activeTrace {
if (self.foregroundSessionTrace) {
return self.foregroundSessionTrace;
}
return self.backgroundSessionTrace;
}
- (void)startTrackingNetwork {
self.networkType = firebase_perf_v1_NetworkConnectionInfo_NetworkType_NONE;
dispatch_queue_attr_t attrs = dispatch_queue_attr_make_with_qos_class(
DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, DISPATCH_QUEUE_PRIORITY_DEFAULT);
self.monitorQueue = dispatch_queue_create("com.google.firebase.perf.network.monitor", attrs);
self.monitor = nw_path_monitor_create();
nw_path_monitor_set_queue(self.monitor, self.monitorQueue);
__weak FPRAppActivityTracker *weakSelf = self;
nw_path_monitor_set_update_handler(self.monitor, ^(nw_path_t _Nonnull path) {
BOOL isWiFi = nw_path_uses_interface_type(path, nw_interface_type_wifi);
BOOL isCellular = nw_path_uses_interface_type(path, nw_interface_type_cellular);
BOOL isEthernet = nw_path_uses_interface_type(path, nw_interface_type_wired);
if (isWiFi) {
weakSelf.networkType = firebase_perf_v1_NetworkConnectionInfo_NetworkType_WIFI;
} else if (isCellular) {
weakSelf.networkType = firebase_perf_v1_NetworkConnectionInfo_NetworkType_MOBILE;
} else if (isEthernet) {
weakSelf.networkType = firebase_perf_v1_NetworkConnectionInfo_NetworkType_ETHERNET;
}
});
nw_path_monitor_start(self.monitor);
}
/**
* Checks if the prewarming feature is available on the current device.
*
* @return true if the OS could prewarm apps on the current device
*/
- (BOOL)isPrewarmAvailable {
return YES;
}
/**
RC flag for dropping all app start events
*/
- (BOOL)isAppStartEnabled {
return [self.configurations prewarmDetectionMode] != PrewarmDetectionModeKeepNone;
}
/**
RC flag for enabling prewarm-detection using ActivePrewarm environment variable
*/
- (BOOL)isActivePrewarmEnabled {
PrewarmDetectionMode mode = [self.configurations prewarmDetectionMode];
return (mode == PrewarmDetectionModeActivePrewarm);
}
/**
Checks if the current app start is a prewarmed app start
*/
- (BOOL)isApplicationPreWarmed {
if (![self isPrewarmAvailable]) {
return NO;
}
BOOL isPrewarmed = NO;
if (isActivePrewarm == YES) {
isPrewarmed = isPrewarmed || [self isActivePrewarmEnabled];
[self.activeTrace incrementMetric:kFPRAppCounterNameActivePrewarm byInt:1];
} else {
[self.activeTrace incrementMetric:kFPRAppCounterNameActivePrewarm byInt:0];
}
return isPrewarmed;
}
/**
* This gets called whenever the app becomes active. A new trace will be created to track the active
* foreground session. Any background session trace that was running in the past will be stopped.
*
* @param notification Notification received during app launch.
*/
- (void)appDidBecomeActiveNotification:(NSNotification *)notification {
self.applicationState = FPRApplicationStateForeground;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
self.appStartTrace = [[FIRTrace alloc] initInternalTraceWithName:kFPRAppStartTraceName];
[self.appStartTrace startWithStartTime:appStartTime];
[self.appStartTrace startStageNamed:kFPRAppStartStageNameTimeToUI startTime:appStartTime];
// Start measuring time to first draw on the App start trace.
[self.appStartTrace startStageNamed:kFPRAppStartStageNameTimeToFirstDraw];
});
// If ever the app start trace had it life in background stage, do not send the trace.
if (self.appStartTrace.backgroundTraceState != FPRTraceStateForegroundOnly) {
self.appStartTrace = nil;
}
// Stop the active background session trace.
[self.backgroundSessionTrace stop];
self.backgroundSessionTrace = nil;
// Start foreground session trace.
FIRTrace *appTrace =
[[FIRTrace alloc] initInternalTraceWithName:kFPRAppTraceNameForegroundSession];
[appTrace start];
self.foregroundSessionTrace = appTrace;
// Start measuring time to make the app interactive on the App start trace.
static BOOL TTIStageStarted = NO;
if (!TTIStageStarted) {
[self.appStartTrace startStageNamed:kFPRAppStartStageNameTimeToUserInteraction];
TTIStageStarted = YES;
// Assumption here is that - the app becomes interactive in the next runloop cycle.
// It is possible that the app does more things later, but for now we are not measuring that.
dispatch_async(dispatch_get_main_queue(), ^{
NSTimeInterval startTimeSinceEpoch = [self.appStartTrace startTimeSinceEpoch];
NSTimeInterval currentTimeSinceEpoch = [[NSDate date] timeIntervalSince1970];
// The below check is to account for 2 scenarios.
// 1. The app gets started in the background and might come to foreground a lot later.
// 2. The app is launched, but immediately backgrounded for some reason and the actual launch
// happens a lot later.
// Dropping the app start trace in such situations where the launch time is taking more than
// 60 minutes. This is an approximation, but a more agreeable timelimit for app start.
if ((currentTimeSinceEpoch - startTimeSinceEpoch < gAppStartMaxValidDuration) &&
[self isAppStartEnabled] && ![self isApplicationPreWarmed]) {
[self.appStartTrace stop];
} else {
[self.appStartTrace cancel];
}
});
}
}
/**
* This gets called whenever the app resigns its active status. The currently active foreground
* session trace will be stopped and a background session trace will be started.
*
* @param notification Notification received during app resigning active status.
*/
- (void)appWillResignActiveNotification:(NSNotification *)notification {
// Dispatch the collected gauge metrics.
if (!self.appStartGaugeMetricDispatched) {
[[FPRGaugeManager sharedInstance] dispatchMetric:gAppStartCPUGaugeData];
[[FPRGaugeManager sharedInstance] dispatchMetric:gAppStartMemoryGaugeData];
self.appStartGaugeMetricDispatched = YES;
}
self.applicationState = FPRApplicationStateBackground;
// Stop foreground session trace.
[self.foregroundSessionTrace stop];
self.foregroundSessionTrace = nil;
// Start background session trace.
self.backgroundSessionTrace =
[[FIRTrace alloc] initInternalTraceWithName:kFPRAppTraceNameBackgroundSession];
[self.backgroundSessionTrace start];
}
- (void)dealloc {
nw_path_monitor_cancel(self.monitor);
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidBecomeActiveNotification
object:[UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillResignActiveNotification
object:[UIApplication sharedApplication]];
}
@end

View File

@ -0,0 +1,117 @@
// 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/AppActivity/FPRScreenTraceTracker.h"
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#import <stdatomic.h>
#import "FirebasePerformance/Sources/Common/FPRConstants.h"
#import "FirebasePerformance/Sources/Timer/FIRTrace+Internal.h"
@class UIViewController;
NS_ASSUME_NONNULL_BEGIN
/** Prefix string for screen traces. */
FOUNDATION_EXTERN NSString *const kFPRPrefixForScreenTraceName;
/** Counter name for frozen frames. */
FOUNDATION_EXTERN NSString *const kFPRFrozenFrameCounterName;
/** Counter name for slow frames. */
FOUNDATION_EXTERN NSString *const kFPRSlowFrameCounterName;
/** Counter name for total frames. */
FOUNDATION_EXTERN NSString *const kFPRTotalFramesCounterName;
/** Slow frame threshold (for time difference between current and previous frame render time)
* in sec.
*/
FOUNDATION_EXTERN CFTimeInterval const kFPRSlowFrameThreshold;
/** Frozen frame threshold (for time difference between current and previous frame render time)
* in sec.
*/
FOUNDATION_EXTERN CFTimeInterval const kFPRFrozenFrameThreshold;
@interface FPRScreenTraceTracker ()
/** A map table of that has the viewControllers as the keys and their associated trace as the value.
* The key is weakly retained and the value is strongly retained.
*/
@property(nonatomic) NSMapTable<UIViewController *, FIRTrace *> *activeScreenTraces;
/** A list of all UIViewController instances that were visible before app was backgrounded. The
* viewControllers are reatined weakly.
*/
@property(nonatomic, nullable) NSPointerArray *previouslyVisibleViewControllers;
/** Serial queue on which all operations that need to be thread safe in this class take place. */
@property(nonatomic) dispatch_queue_t screenTraceTrackerSerialQueue;
/** The display link that provides us with the frame rate data. */
@property(nonatomic) CADisplayLink *displayLink;
/** Dispatch group which allows us to make this class testable. Instead of waiting an arbitrary
* amount of time for an asynchronous task to finish before asserting its behavior, we can wait
* on this dispatch group to finish executing before testing the behavior of any asynchronous
* task. Consequently, all asynchronous tasks in this class should use this dispatch group.
*/
@property(nonatomic) dispatch_group_t screenTraceTrackerDispatchGroup;
/** The frozen frames counter. */
@property(atomic) int_fast64_t frozenFramesCount;
/** The total frames counter. */
@property(atomic) int_fast64_t totalFramesCount;
/** The slow frames counter. */
@property(atomic) int_fast64_t slowFramesCount;
/** Handles the appDidBecomeActive notification. Restores the screen traces that were active before
* the app was backgrounded.
*
* @param notification The NSNotification object.
*/
- (void)appDidBecomeActiveNotification:(NSNotification *)notification;
/** Handles the appWillResignActive notification. Saves the names of the screen traces that are
* currently active and stops all of them.
*
* @param notification The NSNotification object.
*/
- (void)appWillResignActiveNotification:(NSNotification *)notification;
/** The method that is invoked by the CADisplayLink when a new frame is rendered. */
- (void)displayLinkStep;
/** Tells the screen trace tracker that the given viewController appeared. This should be called
* from the main thread.
*
* @param viewController The UIViewController instance that appeared.
*/
- (void)viewControllerDidAppear:(UIViewController *)viewController;
/** Tells the screen trace tracker that the given viewController disappeared. This should be called
* from the main thread.
*
* @param viewController The UIViewController instance that disappeared.
*/
- (void)viewControllerDidDisappear:(id)viewController;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,34 @@
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** This class manages all the screen traces. If initialized, it records the total frames, frozen
* frames and slow frames, and if it has been registered as a delegate of FIRAScreenViewReporter,
* it also automatically creates screen traces for each UIViewController.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRScreenTraceTracker : NSObject
/** Singleton instance of FPRScreenTraceTracker.
*
* @return The shared instance of FPRScreenTraceTracker.
*/
+ (instancetype)sharedInstance;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,403 @@
// 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/AppActivity/FPRScreenTraceTracker.h"
#import "FirebasePerformance/Sources/AppActivity/FPRScreenTraceTracker+Private.h"
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "FirebasePerformance/Sources/Common/FPRDiagnostics.h"
NSString *const kFPRPrefixForScreenTraceName = @"_st_";
NSString *const kFPRFrozenFrameCounterName = @"_fr_fzn";
NSString *const kFPRSlowFrameCounterName = @"_fr_slo";
NSString *const kFPRTotalFramesCounterName = @"_fr_tot";
// Note: This was previously 60 FPS, but that resulted in 90% + of all frames collected to be
// flagged as slow frames, and so the threshold for iOS is being changed to 59 FPS.
// TODO(b/73498642): Make these configurable.
CFTimeInterval const kFPRSlowFrameThreshold = 1.0 / 59.0; // Anything less than 59 FPS is slow.
CFTimeInterval const kFPRFrozenFrameThreshold = 700.0 / 1000.0;
/** Constant that indicates an invalid time. */
CFAbsoluteTime const kFPRInvalidTime = -1.0;
/** Returns the class name without the prefixed module name present in Swift classes
* (e.g. MyModule.MyViewController -> MyViewController).
*/
static NSString *FPRUnprefixedClassName(Class theClass) {
NSString *className = NSStringFromClass(theClass);
NSRange periodRange = [className rangeOfString:@"." options:NSBackwardsSearch];
if (periodRange.location == NSNotFound) {
return className;
}
return periodRange.location < className.length - 1
? [className substringFromIndex:periodRange.location + 1]
: className;
}
/** Returns the name for the screen trace for a given UIViewController. It does the following:
* - Removes module name from swift classes - (e.g. MyModule.MyViewController -> MyViewController)
* - Prepends "_st_" to the class name
* - Truncates the length if it exceeds the maximum trace length.
*
* @param viewController The view controller whose screen trace name we want. Cannot be nil.
* @return An NSString containing the trace name, or a string containing an error if the
* class was nil.
*/
static NSString *FPRScreenTraceNameForViewController(UIViewController *viewController) {
NSString *unprefixedClassName = FPRUnprefixedClassName([viewController class]);
if (unprefixedClassName.length != 0) {
NSString *traceName =
[NSString stringWithFormat:@"%@%@", kFPRPrefixForScreenTraceName, unprefixedClassName];
return traceName.length > kFPRMaxNameLength ? [traceName substringToIndex:kFPRMaxNameLength]
: traceName;
} else {
// This is unlikely, but might happen if there's a regression on iOS where the class name
// returned for a non-nil class is nil or empty.
return @"_st_ERROR_NIL_CLASS_NAME";
}
}
@implementation FPRScreenTraceTracker {
/** Instance variable storing the total frames observed so far. */
atomic_int_fast64_t _totalFramesCount;
/** Instance variable storing the slow frames observed so far. */
atomic_int_fast64_t _slowFramesCount;
/** Instance variable storing the frozen frames observed so far. */
atomic_int_fast64_t _frozenFramesCount;
}
@dynamic totalFramesCount;
@dynamic frozenFramesCount;
@dynamic slowFramesCount;
+ (instancetype)sharedInstance {
static FPRScreenTraceTracker *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
// Weakly retain viewController, use pointer hashing.
NSMapTableOptions keyOptions = NSMapTableWeakMemory | NSMapTableObjectPointerPersonality;
// Strongly retain the FIRTrace.
NSMapTableOptions valueOptions = NSMapTableStrongMemory;
_activeScreenTraces = [NSMapTable mapTableWithKeyOptions:keyOptions valueOptions:valueOptions];
_previouslyVisibleViewControllers = nil; // Will be set when there is data.
_screenTraceTrackerSerialQueue =
dispatch_queue_create("com.google.FPRScreenTraceTracker", DISPATCH_QUEUE_SERIAL);
_screenTraceTrackerDispatchGroup = dispatch_group_create();
atomic_store_explicit(&_totalFramesCount, 0, memory_order_relaxed);
atomic_store_explicit(&_frozenFramesCount, 0, memory_order_relaxed);
atomic_store_explicit(&_slowFramesCount, 0, memory_order_relaxed);
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkStep)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
// We don't receive background and foreground events from analytics and so we have to listen to
// them ourselves.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appDidBecomeActiveNotification:)
name:UIApplicationDidBecomeActiveNotification
object:[UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appWillResignActiveNotification:)
name:UIApplicationWillResignActiveNotification
object:[UIApplication sharedApplication]];
}
return self;
}
- (void)dealloc {
[_displayLink invalidate];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidBecomeActiveNotification
object:[UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillResignActiveNotification
object:[UIApplication sharedApplication]];
}
- (void)appDidBecomeActiveNotification:(NSNotification *)notification {
// To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
// soon as we're notified of an event.
int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
int64_t currentFrozenFrames = atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
int64_t currentSlowFrames = atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
dispatch_group_async(self.screenTraceTrackerDispatchGroup, self.screenTraceTrackerSerialQueue, ^{
for (id viewController in self.previouslyVisibleViewControllers) {
[self startScreenTraceForViewController:viewController
currentTotalFrames:currentTotalFrames
currentFrozenFrames:currentFrozenFrames
currentSlowFrames:currentSlowFrames];
}
self.previouslyVisibleViewControllers = nil;
});
}
- (void)appWillResignActiveNotification:(NSNotification *)notification {
// To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
// soon as we're notified of an event.
int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
int64_t currentFrozenFrames = atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
int64_t currentSlowFrames = atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
dispatch_group_async(self.screenTraceTrackerDispatchGroup, self.screenTraceTrackerSerialQueue, ^{
self.previouslyVisibleViewControllers = [NSPointerArray weakObjectsPointerArray];
id visibleViewControllersEnumerator = [self.activeScreenTraces keyEnumerator];
id visibleViewController = nil;
while (visibleViewController = [visibleViewControllersEnumerator nextObject]) {
[self.previouslyVisibleViewControllers addPointer:(__bridge void *)(visibleViewController)];
}
for (id visibleViewController in self.previouslyVisibleViewControllers) {
[self stopScreenTraceForViewController:visibleViewController
currentTotalFrames:currentTotalFrames
currentFrozenFrames:currentFrozenFrames
currentSlowFrames:currentSlowFrames];
}
});
}
#pragma mark - Frozen, slow and good frames
- (void)displayLinkStep {
static CFAbsoluteTime previousTimestamp = kFPRInvalidTime;
CFAbsoluteTime currentTimestamp = self.displayLink.timestamp;
RecordFrameType(currentTimestamp, previousTimestamp, &_slowFramesCount, &_frozenFramesCount,
&_totalFramesCount);
previousTimestamp = currentTimestamp;
}
/** This function increments the relevant frame counters based on the current and previous
* timestamp provided by the displayLink.
*
* @param currentTimestamp The current timestamp of the displayLink.
* @param previousTimestamp The previous timestamp of the displayLink.
* @param slowFramesCounter The value of the slowFramesCount before this function was called.
* @param frozenFramesCounter The value of the frozenFramesCount before this function was called.
* @param totalFramesCounter The value of the totalFramesCount before this function was called.
*/
FOUNDATION_STATIC_INLINE
void RecordFrameType(CFAbsoluteTime currentTimestamp,
CFAbsoluteTime previousTimestamp,
atomic_int_fast64_t *slowFramesCounter,
atomic_int_fast64_t *frozenFramesCounter,
atomic_int_fast64_t *totalFramesCounter) {
CFTimeInterval frameDuration = currentTimestamp - previousTimestamp;
if (previousTimestamp == kFPRInvalidTime) {
return;
}
if (frameDuration > kFPRSlowFrameThreshold) {
atomic_fetch_add_explicit(slowFramesCounter, 1, memory_order_relaxed);
}
if (frameDuration > kFPRFrozenFrameThreshold) {
atomic_fetch_add_explicit(frozenFramesCounter, 1, memory_order_relaxed);
}
atomic_fetch_add_explicit(totalFramesCounter, 1, memory_order_relaxed);
}
#pragma mark - Helper methods
/** Starts a screen trace for the given UIViewController instance if it doesn't exist. This method
* does NOT ensure thread safety - the caller is responsible for making sure that this is invoked
* in a thread safe manner.
*
* @param viewController The UIViewController instance for which the trace is to be started.
* @param currentTotalFrames The value of the totalFramesCount before this method was called.
* @param currentFrozenFrames The value of the frozenFramesCount before this method was called.
* @param currentSlowFrames The value of the slowFramesCount before this method was called.
*/
- (void)startScreenTraceForViewController:(UIViewController *)viewController
currentTotalFrames:(int64_t)currentTotalFrames
currentFrozenFrames:(int64_t)currentFrozenFrames
currentSlowFrames:(int64_t)currentSlowFrames {
if (![self shouldCreateScreenTraceForViewController:viewController]) {
return;
}
// If there's a trace for this viewController, don't do anything.
if (![self.activeScreenTraces objectForKey:viewController]) {
NSString *traceName = FPRScreenTraceNameForViewController(viewController);
FIRTrace *newTrace = [[FIRTrace alloc] initInternalTraceWithName:traceName];
[newTrace start];
[newTrace setIntValue:currentTotalFrames forMetric:kFPRTotalFramesCounterName];
[newTrace setIntValue:currentFrozenFrames forMetric:kFPRFrozenFrameCounterName];
[newTrace setIntValue:currentSlowFrames forMetric:kFPRSlowFrameCounterName];
[self.activeScreenTraces setObject:newTrace forKey:viewController];
}
}
/** Stops a screen trace for the given UIViewController instance if it exist. This method does NOT
* ensure thread safety - the caller is responsible for making sure that this is invoked in a
* thread safe manner.
*
* @param viewController The UIViewController instance for which the trace is to be stopped.
* @param currentTotalFrames The value of the totalFramesCount before this method was called.
* @param currentFrozenFrames The value of the frozenFramesCount before this method was called.
* @param currentSlowFrames The value of the slowFramesCount before this method was called.
*/
- (void)stopScreenTraceForViewController:(UIViewController *)viewController
currentTotalFrames:(int64_t)currentTotalFrames
currentFrozenFrames:(int64_t)currentFrozenFrames
currentSlowFrames:(int64_t)currentSlowFrames {
FIRTrace *previousScreenTrace = [self.activeScreenTraces objectForKey:viewController];
// Get a diff between the counters now and what they were at trace start.
int64_t actualTotalFrames =
currentTotalFrames - [previousScreenTrace valueForIntMetric:kFPRTotalFramesCounterName];
int64_t actualFrozenFrames =
currentFrozenFrames - [previousScreenTrace valueForIntMetric:kFPRFrozenFrameCounterName];
int64_t actualSlowFrames =
currentSlowFrames - [previousScreenTrace valueForIntMetric:kFPRSlowFrameCounterName];
// Update the values in the trace.
if (actualTotalFrames != 0) {
[previousScreenTrace setIntValue:actualTotalFrames forMetric:kFPRTotalFramesCounterName];
} else {
[previousScreenTrace deleteMetric:kFPRTotalFramesCounterName];
}
if (actualFrozenFrames != 0) {
[previousScreenTrace setIntValue:actualFrozenFrames forMetric:kFPRFrozenFrameCounterName];
} else {
[previousScreenTrace deleteMetric:kFPRFrozenFrameCounterName];
}
if (actualSlowFrames != 0) {
[previousScreenTrace setIntValue:actualSlowFrames forMetric:kFPRSlowFrameCounterName];
} else {
[previousScreenTrace deleteMetric:kFPRSlowFrameCounterName];
}
if (previousScreenTrace.numberOfCounters > 0) {
[previousScreenTrace stop];
} else {
// The trace did not collect any data. Don't log it.
[previousScreenTrace cancel];
}
[self.activeScreenTraces removeObjectForKey:viewController];
}
#pragma mark - Filtering for screen traces
/** Determines whether to create a screen trace for the given UIViewController instance.
*
* @param viewController The UIViewController instance.
* @return YES if a screen trace should be created for the given UIViewController instance,
NO otherwise.
*/
- (BOOL)shouldCreateScreenTraceForViewController:(UIViewController *)viewController {
if (viewController == nil) {
return NO;
}
// Ignore non-main bundle view controllers whose class or superclass is an internal iOS view
// controller. This is borrowed from the logic for tracking screens in Firebase Analytics.
NSBundle *bundle = [NSBundle bundleForClass:[viewController class]];
if (bundle != [NSBundle mainBundle]) {
NSString *className = FPRUnprefixedClassName([viewController class]);
if ([className hasPrefix:@"_"]) {
return NO;
}
NSString *superClassName = FPRUnprefixedClassName([viewController superclass]);
if ([superClassName hasPrefix:@"_"]) {
return NO;
}
}
// We are not creating screen traces for these view controllers because they're container view
// controllers. They always have a child view controller which will provide better context for a
// screen trace. We are however capturing traces if a developer subclasses these as there may be
// some context. Special case: We are not capturing screen traces for any input view
// controllers.
return !([viewController isMemberOfClass:[UINavigationController class]] ||
[viewController isMemberOfClass:[UITabBarController class]] ||
[viewController isMemberOfClass:[UISplitViewController class]] ||
[viewController isMemberOfClass:[UIPageViewController class]] ||
[viewController isKindOfClass:[UIInputViewController class]]);
}
#pragma mark - Screen Traces swizzling hooks
- (void)viewControllerDidAppear:(UIViewController *)viewController {
// To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
// soon as we're notified of an event.
int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
int64_t currentFrozenFrames = atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
int64_t currentSlowFrames = atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
dispatch_sync(self.screenTraceTrackerSerialQueue, ^{
[self startScreenTraceForViewController:viewController
currentTotalFrames:currentTotalFrames
currentFrozenFrames:currentFrozenFrames
currentSlowFrames:currentSlowFrames];
});
}
- (void)viewControllerDidDisappear:(id)viewController {
// To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
// soon as we're notified of an event.
int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
int64_t currentFrozenFrames = atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
int64_t currentSlowFrames = atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
dispatch_sync(self.screenTraceTrackerSerialQueue, ^{
[self stopScreenTraceForViewController:viewController
currentTotalFrames:currentTotalFrames
currentFrozenFrames:currentFrozenFrames
currentSlowFrames:currentSlowFrames];
});
}
#pragma mark - Test Helper Methods
- (int_fast64_t)totalFramesCount {
return atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
}
- (void)setTotalFramesCount:(int_fast64_t)count {
atomic_store_explicit(&_totalFramesCount, count, memory_order_relaxed);
}
- (int_fast64_t)slowFramesCount {
return atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
}
- (void)setSlowFramesCount:(int_fast64_t)count {
atomic_store_explicit(&_slowFramesCount, count, memory_order_relaxed);
}
- (int_fast64_t)frozenFramesCount {
return atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
}
- (void)setFrozenFramesCount:(int_fast64_t)count {
atomic_store_explicit(&_frozenFramesCount, count, memory_order_relaxed);
}
@end

View File

@ -0,0 +1,56 @@
// 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 <Foundation/Foundation.h>
/* List of options the session cares about. */
typedef NS_OPTIONS(NSUInteger, FPRSessionOptions) {
FPRSessionOptionsNone = 0,
FPRSessionOptionsGauges = (1 << 0),
FPRSessionOptionsEvents = (1 << 1),
};
/* Class that contains the details of a session including the sessionId and session options. */
@interface FPRSessionDetails : NSObject
/* The sessionId with which the session details is initialized with. */
@property(nonatomic, nonnull, readonly) NSString *sessionId;
/* List of options enabled for the session. */
@property(nonatomic, readonly) FPRSessionOptions options;
/* Length of the session in minutes. */
- (NSUInteger)sessionLengthInMinutesFromDate:(nonnull NSDate *)now;
/**
* Creates an instance of FPRSessionDetails with the provided sessionId and the list of available
* options.
*
* @param sessionId Session Id for which the object is created.
* @param options Options enabled for the session.
* @return Instance of the object FPRSessionDetails.
*/
- (nonnull instancetype)initWithSessionId:(nonnull NSString *)sessionId
options:(FPRSessionOptions)options NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)init NS_UNAVAILABLE;
/**
* Checks and returns if the session is verbose.
*
* @return Return YES if verbose, NO otherwise.
*/
- (BOOL)isVerbose;
@end

View File

@ -0,0 +1,59 @@
// 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/AppActivity/FPRSessionDetails.h"
@interface FPRSessionDetails ()
/** @brief Time at which the session was created. */
@property(nonatomic) NSDate *sessionCreationTime;
@end
@implementation FPRSessionDetails
- (instancetype)initWithSessionId:(NSString *)sessionId options:(FPRSessionOptions)options {
self = [super init];
if (self) {
_sessionId = sessionId;
_options = options;
_sessionCreationTime = [NSDate date];
}
return self;
}
- (FPRSessionDetails *)copyWithZone:(NSZone *)zone {
FPRSessionDetails *detailsCopy = [[[self class] allocWithZone:zone] initWithSessionId:_sessionId
options:_options];
detailsCopy.sessionCreationTime = _sessionCreationTime;
return detailsCopy;
}
- (NSUInteger)sessionLengthInMinutesFromDate:(NSDate *)now {
NSTimeInterval sessionLengthInSeconds = ABS([now timeIntervalSinceDate:self.sessionCreationTime]);
return (NSUInteger)(sessionLengthInSeconds / 60);
}
- (BOOL)isEqual:(FPRSessionDetails *)detailsObject {
if (self.sessionId == detailsObject.sessionId) {
return YES;
}
return NO;
}
- (BOOL)isVerbose {
return (self.options > FPRSessionOptionsNone);
}
@end

View File

@ -0,0 +1,49 @@
// 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 <Foundation/Foundation.h>
#import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
#import "FirebasePerformance/Sources/Gauges/FPRGaugeManager.h"
NS_ASSUME_NONNULL_BEGIN
/** This extension should only be used for testing. */
@interface FPRSessionManager ()
@property(nonatomic) FPRGaugeManager *gaugeManager;
/** The current active session managed by the session manager. Modifiable for unit tests */
@property(atomic, nullable, readwrite) FPRSessionDetails *sessionDetails;
/**
* Creates an instance of FPRSesssionManager with the notification center provided. All the
* notifications from the session manager will sent using this notification center.
*
* @param gaugeManager Gauge manager used by the session manager to work with gauges.
* @param notificationCenter Notification center with which the session manager with be initialized.
* @return Returns an instance of the session manager.
*/
- (FPRSessionManager *)initWithGaugeManager:(FPRGaugeManager *)gaugeManager
notificationCenter:(NSNotificationCenter *)notificationCenter;
/**
* Checks if the currently active session is beyond maximum allowed time for gauge-collection. If so
* stop gauges, else no-op.
*/
- (void)stopGaugesIfRunningTooLong;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,54 @@
// 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 <Foundation/Foundation.h>
#import "FirebasePerformance/Sources/AppActivity/FPRSessionDetails.h"
/* Notification name when the session Id gets updated. */
FOUNDATION_EXTERN NSString *_Nonnull const kFPRSessionIdUpdatedNotification;
/* Notification name when the session Id gets updated. */
FOUNDATION_EXTERN NSString *_Nonnull const kFPRSessionIdNotificationKey;
/** This class manages the current active sessionId of the application and provides mechanism for
* propagating the session Id.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRSessionManager : NSObject
/** The current active session managed by the session manager. */
@property(atomic, readonly, nonnull) FPRSessionDetails *sessionDetails;
/**
* The notification center managed by the session manager. All the notifications by the session
* manager will get broadcasted on this notification center.
*/
@property(nonatomic, readonly, nonnull) NSNotificationCenter *sessionNotificationCenter;
/**
* The shared instance of Session Manager.
*
* @return The singleton instance.
*/
+ (nonnull FPRSessionManager *)sharedInstance;
- (nullable instancetype)init NS_UNAVAILABLE;
- (void)updateSessionId:(nonnull NSString *)sessionIdString;
// Collects all the enabled gauge metrics once.
- (void)collectAllGaugesOnce;
@end

View File

@ -0,0 +1,113 @@
// 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/AppActivity/FPRSessionManager.h"
#import "FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
#import "FirebasePerformance/Sources/FPRConsoleLogger.h"
#import <UIKit/UIKit.h>
NSString *const kFPRSessionIdUpdatedNotification = @"kFPRSessionIdUpdatedNotification";
NSString *const kFPRSessionIdNotificationKey = @"kFPRSessionIdNotificationKey";
@interface FPRSessionManager ()
@property(nonatomic, readwrite) NSNotificationCenter *sessionNotificationCenter;
@end
@implementation FPRSessionManager
+ (FPRSessionManager *)sharedInstance {
static FPRSessionManager *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSNotificationCenter *notificationCenter = [[NSNotificationCenter alloc] init];
FPRGaugeManager *gaugeManager = [FPRGaugeManager sharedInstance];
instance = [[FPRSessionManager alloc] initWithGaugeManager:gaugeManager
notificationCenter:notificationCenter];
});
return instance;
}
- (FPRSessionManager *)initWithGaugeManager:(FPRGaugeManager *)gaugeManager
notificationCenter:(NSNotificationCenter *)notificationCenter {
self = [super init];
if (self) {
_gaugeManager = gaugeManager;
_sessionNotificationCenter = notificationCenter;
// Empty string is immediately replaced when FirebaseCore runs Fireperf's
// FIRComponentCreationBlock, because in the creation block we register Fireperf with Sessions,
// and the registration function immediately propagates real sessionId. This is at an early time
// in initialization that any trace is yet to be created.
_sessionDetails = [[FPRSessionDetails alloc] initWithSessionId:@""
options:FPRSessionOptionsNone];
}
return self;
}
- (void)stopGaugesIfRunningTooLong {
NSUInteger maxSessionLength = [[FPRConfigurations sharedInstance] maxSessionLengthInMinutes];
if ([self.sessionDetails sessionLengthInMinutesFromDate:[NSDate date]] >= maxSessionLength) {
[self.gaugeManager stopCollectingGauges:FPRGaugeCPU | FPRGaugeMemory];
}
}
/**
* Stops current session, and create a new session with new session id.
*
* @param sessionIdString New session id.
*/
- (void)updateSessionId:(NSString *)sessionIdString {
FPRSessionOptions sessionOptions = FPRSessionOptionsNone;
if ([self isGaugeCollectionEnabledForSessionId:sessionIdString]) {
[self.gaugeManager startCollectingGauges:FPRGaugeCPU | FPRGaugeMemory
forSessionId:sessionIdString];
sessionOptions = FPRSessionOptionsGauges;
} else {
[self.gaugeManager stopCollectingGauges:FPRGaugeCPU | FPRGaugeMemory];
}
FPRLogDebug(kFPRSessionId, @"Session Id changed - %@", sessionIdString);
FPRSessionDetails *sessionInfo = [[FPRSessionDetails alloc] initWithSessionId:sessionIdString
options:sessionOptions];
self.sessionDetails = sessionInfo;
NSMutableDictionary<NSString *, FPRSessionDetails *> *userInfo =
[[NSMutableDictionary alloc] init];
[userInfo setObject:sessionInfo forKey:kFPRSessionIdNotificationKey];
[self.sessionNotificationCenter postNotificationName:kFPRSessionIdUpdatedNotification
object:self
userInfo:[userInfo copy]];
}
- (void)collectAllGaugesOnce {
[self.gaugeManager collectAllGauges];
}
/**
* Checks if the provided sessionId can have gauge data collection enabled.
*
* @param sessionId Session Id for which the check is done.
* @return YES if gauge collection is enabled, NO otherwise.
*/
- (BOOL)isGaugeCollectionEnabledForSessionId:(NSString *)sessionId {
float_t sessionSamplePercentage = [[FPRConfigurations sharedInstance] sessionsSamplingPercentage];
double randomNumberBetween0And1 = ((double)arc4random() / UINT_MAX);
BOOL sessionsEnabled = randomNumberBetween0And1 * 100 < sessionSamplePercentage;
return sessionsEnabled;
}
@end

View File

@ -0,0 +1,42 @@
// 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 <Foundation/Foundation.h>
/** Different background states of a trace. */
typedef NS_ENUM(NSInteger, FPRTraceState) {
FPRTraceStateUnknown,
/** Background only trace. */
FPRTraceStateBackgroundOnly,
/** Foreground only trace. */
FPRTraceStateForegroundOnly,
/** Background and foreground trace. */
FPRTraceStateBackgroundAndForeground,
};
/**
* This class is used to track the app activity and track the background and foreground state of the
* object. This object will be used by a trace to determine its application state if the lifecycle
* of the trace is backgrounded, foregrounded or both.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRTraceBackgroundActivityTracker : NSObject
/** Background state of the tracker. */
@property(nonatomic, readonly) FPRTraceState traceBackgroundState;
@end

View File

@ -0,0 +1,81 @@
// 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/AppActivity/FPRTraceBackgroundActivityTracker.h"
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.h"
@interface FPRTraceBackgroundActivityTracker ()
@property(nonatomic, readwrite) FPRTraceState traceBackgroundState;
@end
@implementation FPRTraceBackgroundActivityTracker
- (instancetype)init {
self = [super init];
if (self) {
if ([FPRAppActivityTracker sharedInstance].applicationState == FPRApplicationStateBackground) {
_traceBackgroundState = FPRTraceStateBackgroundOnly;
} else {
_traceBackgroundState = FPRTraceStateForegroundOnly;
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:[UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:[UIApplication sharedApplication]];
}
return self;
}
- (void)dealloc {
// Remove all the notification observers registered.
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - UIApplicationDelegate events
/**
* This gets called whenever the app becomes active.
*
* @param notification Notification received during app launch.
*/
- (void)applicationDidBecomeActive:(NSNotification *)notification {
if (_traceBackgroundState == FPRTraceStateBackgroundOnly) {
_traceBackgroundState = FPRTraceStateBackgroundAndForeground;
}
}
/**
* This gets called whenever the app enters background.
*
* @param notification Notification received when the app enters background.
*/
- (void)applicationDidEnterBackground:(NSNotification *)notification {
if (_traceBackgroundState == FPRTraceStateForegroundOnly) {
_traceBackgroundState = FPRTraceStateBackgroundAndForeground;
}
}
@end

View File

@ -0,0 +1,51 @@
// Copyright 2021 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 <Foundation/Foundation.h>
/** This class generated the console URLs for a project or a metric.*/
@interface FPRConsoleURLGenerator : NSObject
/**
* Generates the console URL for the dashboard page of the project.
*
* @param projectID The Firebase project ID.
* @param bundleID The bundle ID of this project.
* @return The console URL for the dashboard page.
*/
+ (NSString *)generateDashboardURLWithProjectID:(NSString *)projectID bundleID:(NSString *)bundleID;
/**
* Generates the console URL for the custom trace page.
*
* @param projectID The Firebase project ID.
* @param bundleID The bundle ID of this project.
* @return The console URL for the custom trace page.
*/
+ (NSString *)generateCustomTraceURLWithProjectID:(NSString *)projectID
bundleID:(NSString *)bundleID
traceName:(NSString *)traceName;
/**
* Generates the console URL for the screen trace page.
*
* @param projectID The Firebase project ID.
* @param bundleID The bundle ID of this project.
* @return The console URL for the custom trace page.
*/
+ (NSString *)generateScreenTraceURLWithProjectID:(NSString *)projectID
bundleID:(NSString *)bundleID
traceName:(NSString *)traceName;
@end

View File

@ -0,0 +1,56 @@
// Copyright 2021 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/Common/FPRConsoleURLGenerator.h"
NSString *const URL_BASE_PATH = @"https://console.firebase.google.com";
NSString *const UTM_MEDIUM = @"ios-ide";
NSString *const UTM_SOURCE = @"perf-ios-sdk";
@implementation FPRConsoleURLGenerator
/** This is a class method to generate the console URL for the project's dashboard page.*/
+ (NSString *)generateDashboardURLWithProjectID:(NSString *)projectID
bundleID:(NSString *)bundleID {
NSString *rootUrl = [FPRConsoleURLGenerator getRootURLWithProjectID:projectID bundleID:bundleID];
return [NSString
stringWithFormat:@"%@/trends?utm_source=%@&utm_medium=%@", rootUrl, UTM_SOURCE, UTM_MEDIUM];
}
/** This is a class method to generate the console URL for the custom trace.*/
+ (NSString *)generateCustomTraceURLWithProjectID:(NSString *)projectID
bundleID:(NSString *)bundleID
traceName:(NSString *)traceName {
NSString *rootUrl = [FPRConsoleURLGenerator getRootURLWithProjectID:projectID bundleID:bundleID];
return [NSString stringWithFormat:@"%@/troubleshooting/trace/"
@"DURATION_TRACE/%@?utm_source=%@&utm_medium=%@",
rootUrl, traceName, UTM_SOURCE, UTM_MEDIUM];
}
/** This is a class method to generate the console URL for the screen trace.*/
+ (NSString *)generateScreenTraceURLWithProjectID:(NSString *)projectID
bundleID:(NSString *)bundleID
traceName:(NSString *)traceName {
NSString *rootUrl = [FPRConsoleURLGenerator getRootURLWithProjectID:projectID bundleID:bundleID];
return [NSString stringWithFormat:@"%@/troubleshooting/trace/"
@"SCREEN_TRACE/%@?utm_source=%@&utm_medium=%@",
rootUrl, traceName, UTM_SOURCE, UTM_MEDIUM];
}
/** This is a class method to get the root URL for the console .*/
+ (NSString *)getRootURLWithProjectID:(NSString *)projectID bundleID:(NSString *)bundleID {
return [NSString
stringWithFormat:@"%@/project/%@/performance/app/ios:%@", URL_BASE_PATH, projectID, bundleID];
}
@end

View File

@ -0,0 +1,39 @@
// 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 <Foundation/Foundation.h>
// SDK Version number.
FOUNDATION_EXTERN const char* const kFPRSDKVersion;
// Prefix for internal naming of objects
FOUNDATION_EXTERN NSString* const kFPRInternalNamePrefix;
// Max length for object names
FOUNDATION_EXTERN int const kFPRMaxNameLength;
// Max URL length
FOUNDATION_EXTERN int const kFPRMaxURLLength;
// Max length for attribute name.
FOUNDATION_EXTERN int const kFPRMaxAttributeNameLength;
// Max length for attribute value.
FOUNDATION_EXTERN int const kFPRMaxAttributeValueLength;
// Maximum number of global custom attributes.
FOUNDATION_EXTERN int const kFPRMaxGlobalCustomAttributesCount;
// Maximum number of trace custom attributes.
FOUNDATION_EXTERN int const kFPRMaxTraceCustomAttributesCount;

View File

@ -0,0 +1,43 @@
// 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/Common/FPRConstants.h"
// extract macro value into a C string
#define STR_FROM_MACRO(x) #x
#define STR(x) STR_FROM_MACRO(x)
// SDK Version number.
const char *const kFPRSDKVersion = (const char *const)STR(FIRPerformance_LIB_VERSION);
// Characters used prefix for internal naming of objects.
NSString *const kFPRInternalNamePrefix = @"_";
// Max length for object names
int const kFPRMaxNameLength = 100;
// Max URL length.
int const kFPRMaxURLLength = 2000;
// Max length for attribute name.
int const kFPRMaxAttributeNameLength = 40;
// Max length for attribute value.
int const kFPRMaxAttributeValueLength = 100;
// Maximum number of global custom attributes.
int const kFPRMaxGlobalCustomAttributesCount = 5;
// Maximum number of trace custom attributes.
int const kFPRMaxTraceCustomAttributesCount = 5;

View File

@ -0,0 +1,35 @@
// 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 <Foundation/Foundation.h>
/**
* Definition of a date that is used internally.
*/
@protocol FPRDate <NSObject>
/** Returns the current time. */
- (NSDate *)now;
/**
* Calculates the time difference between the provided date and returns the difference in seconds.
*
* @param date A date object used for reference.
* @return Difference in time between the current date and the provided date in seconds. If the
* current date is older than the provided date, the difference is returned in positive, else
* negative.
*/
- (NSTimeInterval)timeIntervalSinceDate:(NSDate *)date;
@end

View File

@ -0,0 +1,60 @@
// 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 <Foundation/Foundation.h>
#import "FirebasePerformance/Sources/FPRConsoleLogger.h"
/** Logs assert information. This shouldn't be called by anything except FPRAssert.
*
* @param object The object (or class) that is asserting.
* @param condition The condition that is being asserted to be true.
* @param func The value of the __func__ variable.
*/
FOUNDATION_EXTERN void __FPRAssert(id object, BOOL condition, const char *func);
/** This protocol defines the selectors that are invoked when a diagnostics event occurs. */
@protocol FPRDiagnosticsProtocol
@optional
/** Emits class-level diagnostic information. */
+ (void)emitDiagnostics;
/** Emits object-level diagnostic information. */
- (void)emitDiagnostics;
@end
// Use this define in implementations of +/-emitDiagnostics.
#define EMIT_DIAGNOSTIC(...) FPRLogNotice(kFPRDiagnosticLog, __VA_ARGS__)
// This assert adds additional functionality to the normal NSAssert, including printing out
// information when NSAsserts are stripped. A __builtin_expect is utilized to keep running speed
// as fast as possible.
#define FPRAssert(condition, ...) \
{ \
do { \
__FPRAssert(self, !!(condition), __func__); \
NSAssert(condition, __VA_ARGS__); \
} while (0); \
}
/** This class handles the control of diagnostics in the SDK. */
@interface FPRDiagnostics : NSObject
/** YES if diagnostics are enabled, NO otherwise. */
@property(class, nonatomic, readonly, getter=isEnabled) BOOL enabled;
@end

View File

@ -0,0 +1,83 @@
// 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/Common/FPRDiagnostics.h"
#import "FirebasePerformance/Sources/Common/FPRDiagnostics_Private.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
void __FPRAssert(id object, BOOL condition, const char *func) {
static BOOL diagnosticsEnabled = NO;
static dispatch_once_t onceToken;
NSDictionary<NSString *, NSString *> *environment = [NSProcessInfo processInfo].environment;
// Enable diagnostics when in test environment
if (environment[@"XCTestConfigurationFilePath"] != nil) {
diagnosticsEnabled = [FPRDiagnostics isEnabled];
} else {
dispatch_once(&onceToken, ^{
diagnosticsEnabled = [FPRDiagnostics isEnabled];
});
}
if (__builtin_expect(!condition && diagnosticsEnabled, NO)) {
FPRLogError(kFPRDiagnosticFailure, @"Failure in %s, information follows:", func);
FPRLogNotice(kFPRDiagnosticLog, @"Stack for failure in %s:\n%@", func,
[NSThread callStackSymbols]);
if ([[object class] respondsToSelector:@selector(emitDiagnostics)]) {
[[object class] performSelector:@selector(emitDiagnostics) withObject:nil];
}
if ([object respondsToSelector:@selector(emitDiagnostics)]) {
[object performSelector:@selector(emitDiagnostics) withObject:nil];
}
FPRLogNotice(kFPRDiagnosticLog, @"End of diagnostics for %s failure.", func);
}
}
@implementation FPRDiagnostics
static FPRConfigurations *_configuration;
+ (void)initialize {
_configuration = [FPRConfigurations sharedInstance];
}
+ (FPRConfigurations *)configuration {
return _configuration;
}
+ (void)setConfiguration:(FPRConfigurations *)config {
_configuration = config;
}
+ (BOOL)isEnabled {
// Check a soft-linked FIRCore class to see if this is running in the app store.
Class FIRAppEnvironmentUtil = NSClassFromString(@"FIRAppEnvironmentUtil");
SEL isFromAppStore = NSSelectorFromString(@"isFromAppStore");
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if (FIRAppEnvironmentUtil && [FIRAppEnvironmentUtil respondsToSelector:isFromAppStore] &&
[FIRAppEnvironmentUtil performSelector:isFromAppStore]) {
return NO;
}
#pragma clang diagnostic pop
BOOL enabled = [FPRDiagnostics.configuration diagnosticsEnabled];
if (enabled) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
FPRLogNotice(kFPRDiagnosticInfo, @"Firebase Performance Diagnostics have been enabled!");
});
}
return enabled;
}
@end

View File

@ -0,0 +1,27 @@
// 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/Common/FPRDiagnostics.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
/**
* Extension that is added on top of the class FPRDiagnostics to make the private properties
* visible between the implementation file and the unit tests.
*/
@interface FPRDiagnostics ()
/** FPRCongiguration to check if diagnostic is enabled. */
@property(class, nonatomic, readwrite) FPRConfigurations *configuration;
@end

View File

@ -0,0 +1,22 @@
// 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/Common/FPRDate.h"
/**
* FPRPerfDate creates a date object that does time calculations.
*/
@interface FPRPerfDate : NSObject <FPRDate>
@end

View File

@ -0,0 +1,27 @@
// 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/Common/FPRPerfDate.h"
@implementation FPRPerfDate
- (NSDate *)now {
return [NSDate date];
}
- (NSTimeInterval)timeIntervalSinceDate:(NSDate *)date {
return [self.now timeIntervalSinceDate:date];
}
@end

View File

@ -0,0 +1,76 @@
// 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/FPRConfigurations.h"
#import "FirebasePerformance/Sources/Configurations/FPRRemoteConfigFlags.h"
NS_ASSUME_NONNULL_BEGIN
@class GULUserDefaults;
/** List of gauges the gauge manager controls. */
typedef NS_OPTIONS(NSUInteger, FPRConfigurationSource) {
FPRConfigurationSourceNone = 0,
FPRConfigurationSourceRemoteConfig = (1 << 1),
};
/** This extension should only be used for testing. */
@interface FPRConfigurations ()
/** @brief Different configuration sources managed by the object. */
@property(nonatomic) FPRConfigurationSource sources;
/** @brief Instance of remote config flags. */
@property(nonatomic) FPRRemoteConfigFlags *remoteConfigFlags;
/** @brief The class to use when FIRApp is referenced. */
@property(nonatomic) Class FIRAppClass;
/** @brief User defaults used for user preference config fetches . */
@property(nonatomic) GULUserDefaults *userDefaults;
/** @brief The main bundle identifier used by config system. */
@property(nonatomic) NSString *mainBundleIdentifier;
/** @brief The infoDictionary provided by the main bundle. */
@property(nonatomic) NSDictionary<NSString *, id> *infoDictionary;
/** @brief Configurations update queue. */
@property(nonatomic) dispatch_queue_t updateQueue;
/**
* Creates an instance of the FPRConfigurations class with the specified sources.
*
* @param source Source that needs to be enabled for fetching configurations.
* @return Instance of FPRConfiguration.
*/
- (instancetype)initWithSources:(FPRConfigurationSource)source NS_DESIGNATED_INITIALIZER;
/**
* Returns the list of SDK versions that are disabled. SDK Versions are ';' separated. If no
* versions are disabled, an empty set is returned.
*
* @return The set of disabled SDK versions.
*/
- (nonnull NSSet<NSString *> *)sdkDisabledVersions;
/**
* Resets this class by changing the onceToken back to 0, allowing a new singleton to be created,
* while the old one is dealloc'd. This should only be used for testing.
*/
+ (void)reset;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,212 @@
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Different modes of prewarm-detection
* KeepNone = No app start events are allowed
* ActivePrewarm = Only detect prewarming using ActivePrewarm environment
* KeepAll = All app start events are allowed
*/
typedef NS_ENUM(NSInteger, PrewarmDetectionMode) {
PrewarmDetectionModeKeepNone = 0,
PrewarmDetectionModeActivePrewarm = 1,
PrewarmDetectionModeKeepAll = 2
};
/** A typedef for ensuring that config names are one of the below specified strings. */
typedef NSString* const FPRConfigName;
/**
* Class that manages the configurations used by firebase performance SDK. This class abstracts the
* configuration flags from different configuration sources.
*/
@interface FPRConfigurations : NSObject
/** Enables or disables performance data collection in the SDK. If the value is set to 'NO' none of
* the performance data will be sent to the server. Default is YES. */
@property(nonatomic, getter=isDataCollectionEnabled) BOOL dataCollectionEnabled;
/** The config KVC name string for the dataCollectionEnabled property. */
FOUNDATION_EXTERN FPRConfigName kFPRConfigDataCollectionEnabled;
/** Enables or disables instrumenting the app to collect performance data (like app start time,
* networking). Default is YES. */
@property(nonatomic, getter=isInstrumentationEnabled) BOOL instrumentationEnabled;
/** The config KVC name string for the instrumentationEnabled property. */
FOUNDATION_EXTERN FPRConfigName kFPRConfigInstrumentationEnabled;
/** Log source against which the Fireperf events are recorded. */
@property(nonatomic, readonly) int logSource;
/** Specifies if the SDK is enabled. */
@property(nonatomic, readonly) BOOL sdkEnabled;
/** Specifies if the diagnostic log messages should be enabled. */
@property(nonatomic, readonly) BOOL diagnosticsEnabled;
- (nullable instancetype)init NS_UNAVAILABLE;
/** Singleton instance of FPRConfigurations. */
+ (nullable instancetype)sharedInstance;
/**
* Updates all the configurations flags relevant to Firebase Performance.
*
* This call blocks until the update is done.
*/
- (void)update;
#pragma mark - Configuration fetcher methods.
/**
* Returns the mode that prewarm-detection should drop events in.
*
* @see PrewarmDetectionMode
* @return filter mode of app start traces prewarm-detection
*/
- (PrewarmDetectionMode)prewarmDetectionMode;
/**
* Returns the percentage of instances that would send trace events. Range [0-1].
*
* @return The percentage of instances that would send trace events.
*/
- (float)logTraceSamplingRate;
/**
* Returns the percentage of instances that would send network request events. Range [0-1].
*
* @return The percentage of instances that would send network request events.
*/
- (float)logNetworkSamplingRate;
/**
* Returns the foreground event count/burst size. This is the number of events that are allowed to
* flow in burst when the app is in foreground.
*
* @return The foreground event count as determined from configs.
*/
- (uint32_t)foregroundEventCount;
/**
* Returns the foreground time limit to allow the foreground event count. This is specified in
* number of minutes.
*
* @return The foreground event time limit as determined from configs.
*/
- (uint32_t)foregroundEventTimeLimit;
/**
* Returns the background event count/burst size. This is the number of events that are allowed to
* flow in burst when the app is in background.
*
* @return The background event count as determined from configs.
*/
- (uint32_t)backgroundEventCount;
/**
* Returns the background time limit to allow the background event count. This is specified in
* number of minutes.
*
* @return The background event time limit as determined from configs.
*/
- (uint32_t)backgroundEventTimeLimit;
/**
* Returns the foreground network event count/burst size. This is the number of network events that
* are allowed to flow in burst when the app is in foreground.
*
* @return The foreground network event count as determined from configs.
*/
- (uint32_t)foregroundNetworkEventCount;
/**
* Returns the foreground time limit to allow the foreground network event count. This is specified
* in number of minutes.
*
* @return The foreground network event time limit as determined from configs.
*/
- (uint32_t)foregroundNetworkEventTimeLimit;
/**
* Returns the background network event count/burst size. This is the number of network events that
* are allowed to flow in burst when the app is in background.
*
* @return The background network event count as determined from configs.
*/
- (uint32_t)backgroundNetworkEventCount;
/**
* Returns the background time limit to allow the background network event count. This is specified
* in number of minutes.
*
* @return The background network event time limit as determined from configs.
*/
- (uint32_t)backgroundNetworkEventTimeLimit;
/**
* Returns a float specifying the percentage of device instances on which session feature is
* enabled. Range [0-100].
*
* @return The percentage of devices on which session feature should be enabled.
*/
- (float_t)sessionsSamplingPercentage;
/**
* Returns the maximum length of a session in minutes. Default is 240 minutes.
*
* @return Maximum allowed length of the session in minutes.
*/
- (uint32_t)maxSessionLengthInMinutes;
/**
* Returns the frequency at which the CPU usage metrics are to be collected when the app is in the
* foreground. Frequency is specified in milliseconds. A value of '0' means do not capture.
*
* @return An integer value specifying the frequency of capture in milliseconds.
*/
- (uint32_t)cpuSamplingFrequencyInForegroundInMS;
/**
* Returns the frequency at which the CPU usage metrics are to be collected when the app is in the
* background. Frequency is specified in milliseconds. A value of '0' means do not capture.
*
* @return An integer value specifying the frequency of capture in milliseconds.
*/
- (uint32_t)cpuSamplingFrequencyInBackgroundInMS;
/**
* Returns the frequency at which the memory usage metrics are to be collected when the app is in
* the foreground. Frequency is specified in milliseconds. A value of '0' means do not capture.
*
* @return An integer value specifying the frequency of capture in milliseconds.
*/
- (uint32_t)memorySamplingFrequencyInForegroundInMS;
/**
* Returns the frequency at which the memory usage metrics are to be collected when the app is in
* the background. Frequency is specified in milliseconds. A value of '0' means do not capture.
*
* @return An integer value specifying the frequency of capture in milliseconds.
*/
- (uint32_t)memorySamplingFrequencyInBackgroundInMS;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,488 @@
// 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 <UIKit/UIKit.h>
#import <GoogleUtilities/GULUserDefaults.h>
#import "FirebasePerformance/Sources/Common/FPRConstants.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations+Private.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
#import "FirebasePerformance/Sources/Configurations/FPRRemoteConfigFlags+Private.h"
#import "FirebasePerformance/Sources/Configurations/FPRRemoteConfigFlags.h"
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
FPRConfigName kFPRConfigDataCollectionEnabled = @"dataCollectionEnabled";
FPRConfigName kFPRConfigInstrumentationEnabled = @"instrumentationEnabled";
NSString *const kFPRConfigInstrumentationUserPreference =
@"com.firebase.performanceInsrumentationEnabled";
NSString *const kFPRConfigInstrumentationPlistKey = @"firebase_performance_instrumentation_enabled";
NSString *const kFPRConfigCollectionUserPreference = @"com.firebase.performanceCollectionEnabled";
NSString *const kFPRConfigCollectionPlistKey = @"firebase_performance_collection_enabled";
NSString *const kFPRDiagnosticsUserPreference = @"FPRDiagnosticsLocal";
NSString *const kFPRDiagnosticsEnabledPlistKey = @"FPRDiagnosticsLocal";
NSString *const kFPRConfigCollectionDeactivationPlistKey =
@"firebase_performance_collection_deactivated";
NSString *const kFPRConfigLogSource = @"com.firebase.performanceLogSource";
@implementation FPRConfigurations
static dispatch_once_t gSharedInstanceToken;
+ (instancetype)sharedInstance {
static FPRConfigurations *instance = nil;
dispatch_once(&gSharedInstanceToken, ^{
FPRConfigurationSource sources = FPRConfigurationSourceRemoteConfig;
instance = [[FPRConfigurations alloc] initWithSources:sources];
});
return instance;
}
+ (void)reset {
// TODO(b/120032990): Reset the singletons that this singleton uses.
gSharedInstanceToken = 0;
[[GULUserDefaults standardUserDefaults]
removeObjectForKey:kFPRConfigInstrumentationUserPreference];
[[GULUserDefaults standardUserDefaults] removeObjectForKey:kFPRConfigCollectionUserPreference];
}
- (instancetype)initWithSources:(FPRConfigurationSource)source {
self = [super init];
if (self) {
_sources = source;
[self setupRemoteConfigFlags];
// Register for notifications to update configs.
[self registerForNotifications];
self.FIRAppClass = [FIRApp class];
self.userDefaults = [GULUserDefaults standardUserDefaults];
self.infoDictionary = [NSBundle mainBundle].infoDictionary;
self.mainBundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
self.updateQueue = dispatch_queue_create("com.google.perf.configUpdate", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)registerForNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(update)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
/** Searches the main bundle and the bundle from bundleForClass: info dictionaries for the key and
* returns the first result.
*
* @param key The key to search the info dictionaries for.
* @return The first object found in the info dictionary of the main bundle and bundleForClass:.
*/
- (nullable id)objectForInfoDictionaryKey:(NSString *)key {
// If the config infoDictionary has been set to a new dictionary, only use the original dictionary
// instead of the new dictionary.
if (self.infoDictionary != [NSBundle mainBundle].infoDictionary) {
return self.infoDictionary[key]; // nullable.
}
NSArray<NSBundle *> *bundles = @[ [NSBundle mainBundle], [NSBundle bundleForClass:[self class]] ];
for (NSBundle *bundle in bundles) {
id object = [bundle objectForInfoDictionaryKey:key];
if (object) {
return object; // nonnull.
}
}
return nil;
}
- (void)update {
dispatch_async(self.updateQueue, ^{
if (!self.remoteConfigFlags) {
[self setupRemoteConfigFlags];
}
[self.remoteConfigFlags update];
});
}
/**
* Sets up the remote config flags instance based on 3 different factors:
* 1. Is the firebase app configured?
* 2. Is the remote config source enabled?
* 3. If the Remote Config flags instance exists already?
*/
- (void)setupRemoteConfigFlags {
if (!self.remoteConfigFlags && [self.FIRAppClass isDefaultAppConfigured] &&
(self.sources & FPRConfigurationSourceRemoteConfig) == FPRConfigurationSourceRemoteConfig) {
self.remoteConfigFlags = [FPRRemoteConfigFlags sharedInstance];
}
}
#pragma mark - Overridden Properties
- (void)setDataCollectionEnabled:(BOOL)dataCollectionEnabled {
[self.userDefaults setBool:dataCollectionEnabled forKey:kFPRConfigCollectionUserPreference];
}
// The data collection flag is determined by this order:
// 1. A plist flag for permanently disabling data collection
// 2. The runtime flag (GULUserDefaults)
// 3. A plist flag for enabling/disabling (overridable)
// 4. The global data collection switch from Core.
- (BOOL)isDataCollectionEnabled {
/**
* Perf only works with the default app, so validate it exists then use the value from the global
* data collection from the default app as the base value if no other values are set.
*/
if (![self.FIRAppClass isDefaultAppConfigured]) {
return NO;
}
BOOL dataCollectionPreference = [self.FIRAppClass defaultApp].isDataCollectionDefaultEnabled;
// Check if data collection is permanently disabled by plist. If so, disable data collection.
id dataCollectionDeactivationObject =
[self objectForInfoDictionaryKey:kFPRConfigCollectionDeactivationPlistKey];
if (dataCollectionDeactivationObject) {
BOOL dataCollectionDeactivated = [dataCollectionDeactivationObject boolValue];
if (dataCollectionDeactivated) {
return NO;
}
}
/**
* Check if the performance collection preference key is available in GULUserDefaults.
* If it exists - Just honor that and return that value.
* If it does not exist - Check if firebase_performance_collection_enabled exists in Info.plist.
* If it exists - honor that and return that value.
* If not - return YES stating performance collection is enabled.
*/
id dataCollectionPreferenceObject =
[self.userDefaults objectForKey:kFPRConfigCollectionUserPreference];
if (dataCollectionPreferenceObject) {
dataCollectionPreference = [dataCollectionPreferenceObject boolValue];
} else {
dataCollectionPreferenceObject = [self objectForInfoDictionaryKey:kFPRConfigCollectionPlistKey];
if (dataCollectionPreferenceObject) {
dataCollectionPreference = [dataCollectionPreferenceObject boolValue];
}
}
return dataCollectionPreference;
}
- (void)setInstrumentationEnabled:(BOOL)instrumentationEnabled {
[self.userDefaults setBool:instrumentationEnabled forKey:kFPRConfigInstrumentationUserPreference];
}
- (BOOL)isInstrumentationEnabled {
BOOL instrumentationPreference = YES;
id instrumentationPreferenceObject =
[self.userDefaults objectForKey:kFPRConfigInstrumentationUserPreference];
/**
* Check if the performance instrumentation preference key is available in GULUserDefaults.
* If it exists - Just honor that and return that value.
* If not - Check if firebase_performance_instrumentation_enabled exists in Info.plist.
* If it exists - honor that and return that value.
* If not - return YES stating performance instrumentation is enabled.
*/
if (instrumentationPreferenceObject) {
instrumentationPreference = [instrumentationPreferenceObject boolValue];
} else {
instrumentationPreferenceObject =
[self objectForInfoDictionaryKey:kFPRConfigInstrumentationPlistKey];
if (instrumentationPreferenceObject) {
instrumentationPreference = [instrumentationPreferenceObject boolValue];
}
}
return instrumentationPreference;
}
#pragma mark - Fireperf SDK configurations.
- (BOOL)sdkEnabled {
BOOL enabled = YES;
if (self.remoteConfigFlags) {
enabled = [self.remoteConfigFlags performanceSDKEnabledWithDefaultValue:enabled];
}
// Check if the current version is one of the disabled versions.
if ([[self sdkDisabledVersions] containsObject:[NSString stringWithUTF8String:kFPRSDKVersion]]) {
enabled = NO;
}
// If there is a plist override, honor that value.
// NOTE: PList override should ideally be used only for tests and not for production.
id plistObject = [self objectForInfoDictionaryKey:@"firebase_performance_sdk_enabled"];
if (plistObject) {
enabled = [plistObject boolValue];
}
return enabled;
}
- (BOOL)diagnosticsEnabled {
BOOL enabled = NO;
/**
* Check if the diagnostics preference key is available in GULUserDefaults.
* If it exists - Just honor that and return that value.
* If not - Check if firebase_performance_instrumentation_enabled exists in Info.plist.
* If it exists - honor that and return that value.
* If not - return NO stating diagnostics is disabled.
*/
id diagnosticsEnabledPreferenceObject =
[self.userDefaults objectForKey:kFPRDiagnosticsUserPreference];
if (diagnosticsEnabledPreferenceObject) {
enabled = [diagnosticsEnabledPreferenceObject boolValue];
} else {
id diagnosticsEnabledObject = [self objectForInfoDictionaryKey:kFPRDiagnosticsEnabledPlistKey];
if (diagnosticsEnabledObject) {
enabled = [diagnosticsEnabledObject boolValue];
}
}
return enabled;
}
- (NSSet<NSString *> *)sdkDisabledVersions {
NSMutableSet<NSString *> *disabledVersions = [[NSMutableSet<NSString *> alloc] init];
if (self.remoteConfigFlags) {
NSSet<NSString *> *sdkDisabledVersions =
[self.remoteConfigFlags sdkDisabledVersionsWithDefaultValue:[disabledVersions copy]];
if (sdkDisabledVersions.count > 0) {
[disabledVersions addObjectsFromArray:[sdkDisabledVersions allObjects]];
}
}
return [disabledVersions copy];
}
- (int)logSource {
/**
* Order of preference of returning the log source.
* If it is an autopush build (based on environment variable), always return
* LogRequest_LogSource_FireperfAutopush (461). If there is a recent value of remote config fetch,
* honor that value. If logSource cached value (GULUserDefaults value) exists, honor that.
* Fallback to the default value LogRequest_LogSource_Fireperf (462).
*/
int logSource = 462;
NSDictionary<NSString *, NSString *> *environment = [NSProcessInfo processInfo].environment;
if (environment[@"FPR_AUTOPUSH_ENV"] != nil &&
[environment[@"FPR_AUTOPUSH_ENV"] isEqualToString:@"1"]) {
logSource = 461;
} else {
if (self.remoteConfigFlags) {
logSource = [self.remoteConfigFlags logSourceWithDefaultValue:462];
}
}
return logSource;
}
- (PrewarmDetectionMode)prewarmDetectionMode {
PrewarmDetectionMode mode = PrewarmDetectionModeActivePrewarm;
if (self.remoteConfigFlags) {
mode = [self.remoteConfigFlags getIntValueForFlag:@"fpr_prewarm_detection"
defaultValue:(int)mode];
}
return mode;
}
#pragma mark - Log sampling configurations.
- (float)logTraceSamplingRate {
float samplingRate = 1.0f;
if (self.remoteConfigFlags) {
float rcSamplingRate = [self.remoteConfigFlags traceSamplingRateWithDefaultValue:samplingRate];
if (rcSamplingRate >= 0) {
samplingRate = rcSamplingRate;
}
}
return samplingRate;
}
- (float)logNetworkSamplingRate {
float samplingRate = 1.0f;
if (self.remoteConfigFlags) {
float rcSamplingRate =
[self.remoteConfigFlags networkRequestSamplingRateWithDefaultValue:samplingRate];
if (rcSamplingRate >= 0) {
samplingRate = rcSamplingRate;
}
}
return samplingRate;
}
#pragma mark - Traces rate limiting configurations.
- (uint32_t)foregroundEventCount {
uint32_t eventCount = 300;
if (self.remoteConfigFlags) {
eventCount =
[self.remoteConfigFlags rateLimitTraceCountInForegroundWithDefaultValue:eventCount];
}
return eventCount;
}
- (uint32_t)foregroundEventTimeLimit {
uint32_t timeLimit = 600;
if (self.remoteConfigFlags) {
timeLimit = [self.remoteConfigFlags rateLimitTimeDurationWithDefaultValue:timeLimit];
}
uint32_t timeLimitInMinutes = timeLimit / 60;
return timeLimitInMinutes;
}
- (uint32_t)backgroundEventCount {
uint32_t eventCount = 30;
if (self.remoteConfigFlags) {
eventCount =
[self.remoteConfigFlags rateLimitTraceCountInBackgroundWithDefaultValue:eventCount];
}
return eventCount;
}
- (uint32_t)backgroundEventTimeLimit {
uint32_t timeLimit = 600;
if (self.remoteConfigFlags) {
timeLimit = [self.remoteConfigFlags rateLimitTimeDurationWithDefaultValue:timeLimit];
}
uint32_t timeLimitInMinutes = timeLimit / 60;
return timeLimitInMinutes;
}
#pragma mark - Network requests rate limiting configurations.
- (uint32_t)foregroundNetworkEventCount {
uint32_t eventCount = 700;
if (self.remoteConfigFlags) {
eventCount = [self.remoteConfigFlags
rateLimitNetworkRequestCountInForegroundWithDefaultValue:eventCount];
}
return eventCount;
}
- (uint32_t)foregroundNetworkEventTimeLimit {
uint32_t timeLimit = 600;
if (self.remoteConfigFlags) {
timeLimit = [self.remoteConfigFlags rateLimitTimeDurationWithDefaultValue:timeLimit];
}
uint32_t timeLimitInMinutes = timeLimit / 60;
return timeLimitInMinutes;
}
- (uint32_t)backgroundNetworkEventCount {
uint32_t eventCount = 70;
if (self.remoteConfigFlags) {
eventCount = [self.remoteConfigFlags
rateLimitNetworkRequestCountInBackgroundWithDefaultValue:eventCount];
}
return eventCount;
}
- (uint32_t)backgroundNetworkEventTimeLimit {
uint32_t timeLimit = 600;
if (self.remoteConfigFlags) {
timeLimit = [self.remoteConfigFlags rateLimitTimeDurationWithDefaultValue:timeLimit];
}
uint32_t timeLimitInMinutes = timeLimit / 60;
return timeLimitInMinutes;
}
#pragma mark - Sessions feature related configurations.
- (float_t)sessionsSamplingPercentage {
float samplingPercentage = 1.0f; // One Percent.
if (self.remoteConfigFlags) {
float rcSamplingRate =
[self.remoteConfigFlags sessionSamplingRateWithDefaultValue:(samplingPercentage / 100)];
if (rcSamplingRate >= 0) {
samplingPercentage = rcSamplingRate * 100;
}
}
id plistObject = [self objectForInfoDictionaryKey:@"sessionsSamplingPercentage"];
if (plistObject) {
samplingPercentage = [plistObject floatValue];
}
return samplingPercentage;
}
- (uint32_t)maxSessionLengthInMinutes {
uint32_t sessionLengthInMinutes = 240;
if (self.remoteConfigFlags) {
sessionLengthInMinutes =
[self.remoteConfigFlags sessionMaxDurationWithDefaultValue:sessionLengthInMinutes];
}
// If the session max length gets set to 0, default it to 240 minutes.
if (sessionLengthInMinutes == 0) {
return 240;
}
return sessionLengthInMinutes;
}
- (uint32_t)cpuSamplingFrequencyInForegroundInMS {
uint32_t samplingFrequency = 100;
if (self.remoteConfigFlags) {
samplingFrequency = [self.remoteConfigFlags
sessionGaugeCPUCaptureFrequencyInForegroundWithDefaultValue:samplingFrequency];
}
return samplingFrequency;
}
- (uint32_t)cpuSamplingFrequencyInBackgroundInMS {
uint32_t samplingFrequency = 0;
if (self.remoteConfigFlags) {
samplingFrequency = [self.remoteConfigFlags
sessionGaugeCPUCaptureFrequencyInBackgroundWithDefaultValue:samplingFrequency];
}
return samplingFrequency;
}
- (uint32_t)memorySamplingFrequencyInForegroundInMS {
uint32_t samplingFrequency = 100;
if (self.remoteConfigFlags) {
samplingFrequency = [self.remoteConfigFlags
sessionGaugeMemoryCaptureFrequencyInForegroundWithDefaultValue:samplingFrequency];
}
return samplingFrequency;
}
- (uint32_t)memorySamplingFrequencyInBackgroundInMS {
uint32_t samplingFrequency = 0;
if (self.remoteConfigFlags) {
samplingFrequency = [self.remoteConfigFlags
sessionGaugeMemoryCaptureFrequencyInBackgroundWithDefaultValue:samplingFrequency];
}
return samplingFrequency;
}
@end

View File

@ -0,0 +1,114 @@
// 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 "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h"
NS_ASSUME_NONNULL_BEGIN
@class GULUserDefaults;
static NSString *const kFPRConfigPrefix = @"com.fireperf";
/** Interval at which the configurations can be fetched. Specified in seconds. */
static NSInteger const kFPRConfigFetchIntervalInSeconds = 12 * 60 * 60;
/** Interval after which the configurations can be fetched. Specified in seconds. */
static NSInteger const kFPRMinAppStartConfigFetchDelayInSeconds = 5;
/** This extension should only be used for testing. */
@interface FPRRemoteConfigFlags ()
/** @brief Instance of remote config used for firebase performance namespace. */
@property(nonatomic) FIRRemoteConfig *fprRemoteConfig;
/** @brief Last activated time of the configurations. */
@property(atomic, nullable) NSDate *lastFetchedTime;
/** @brief User defaults used for caching. */
@property(nonatomic) GULUserDefaults *userDefaults;
/** @brief Last activated time of the configurations. */
@property(nonatomic) NSDate *applicationStartTime;
/** @brief Number of seconds delayed until the first config is made during app start. */
@property(nonatomic) NSTimeInterval appStartConfigFetchDelayInSeconds;
/** @brief Status of the last remote config fetch. */
@property(nonatomic) FIRRemoteConfigFetchStatus lastFetchStatus;
/**
* Creates an instance of FPRRemoteConfigFlags.
*
* @param config RemoteConfig object to be used for configuration management.
* @return Instance of remote config.
*/
- (instancetype)initWithRemoteConfig:(FIRRemoteConfig *)config NS_DESIGNATED_INITIALIZER;
#pragma mark - Config fetch methods
/**
* Gets and returns the string value for the provided remote config flag. If there are no values
* returned from remote config, default value will be returned.
* @param flagName Name of the flag for which the value needs to be fetched from RC.
* @param defaultValue Default value that will be returned if no value is fetched from RC.
*
* @return string value for the flag from RC if available. Default value, otherwise.
*/
- (NSString *)getStringValueForFlag:(NSString *)flagName defaultValue:(NSString *)defaultValue;
/**
* Gets and returns the int value for the provided remote config flag. If there are no values
* returned from remote config, default value will be returned.
* @param flagName Name of the flag for which the value needs to be fetched from RC.
* @param defaultValue Default value that will be returned if no value is fetched from RC.
*
* @return Int value for the flag from RC if available. Default value, otherwise.
*/
- (int)getIntValueForFlag:(NSString *)flagName defaultValue:(int)defaultValue;
/**
* Gets and returns the float value for the provided remote config flag. If there are no values
* returned from remote config, default value will be returned.
* @param flagName Name of the flag for which the value needs to be fetched from RC.
* @param defaultValue Default value that will be returned if no value is fetched from RC.
*
* @return Float value for the flag from RC if available. Default value, otherwise.
*/
- (float)getFloatValueForFlag:(NSString *)flagName defaultValue:(float)defaultValue;
/**
* Gets and returns the boolean value for the provided remote config flag. If there are no values
* returned from remote config, default value will be returned.
* @param flagName Name of the flag for which the value needs to be fetched from RC.
* @param defaultValue Default value that will be returned if no value is fetched from RC.
*
* @return Bool value for the flag from RC if available. Default value, otherwise.
*/
- (BOOL)getBoolValueForFlag:(NSString *)flagName defaultValue:(BOOL)defaultValue;
/**
* Caches the remote config values.
*/
- (void)cacheConfigValues;
/**
* Reset (Clears) all the remote config keys and values that were cached.
*/
- (void)resetCache;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,203 @@
// 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 <Foundation/Foundation.h>
/**
* Configuration flags retrieved from Firebase Remote Configuration.
*/
@interface FPRRemoteConfigFlags : NSObject
/**
* The name of the name space for which the remote config flags are fetched.
*/
@property(nonatomic, readonly, nonnull) NSString *remoteConfigNamespace;
#pragma mark - Instance methods
- (nullable instancetype)init NS_UNAVAILABLE;
/** Singleton instance of Firebase Remote Configuration flags. */
+ (nullable instancetype)sharedInstance;
/**
* Initiate a fetch of the flags from Firebase Remote Configuration and updates the configurations
* at the end of the fetch.
*
* @note This method is throttled to initiate a fetch once in 12 hours. So, calling this method does
* not guarantee a fetch from Firebase Remote Config.
*/
- (void)update;
#pragma mark - General configs.
/**
* Returns if performance SDK is enabled.
* Name in remote config: "fpr_enabled".
*
* @param sdkEnabled Default value to be returned if values does not exist in remote config.
* @return Specifies if SDK should be enabled or not.
*/
- (BOOL)performanceSDKEnabledWithDefaultValue:(BOOL)sdkEnabled;
/**
* Returns set of versions on which SDK is disabled.
* Name in remote config: "fpr_disabled_ios_versions".
*
* @param sdkVersions Default value to be returned if values does not exist in remote config.
* @return SDK versions list where the SDK has to be disabled.
*/
- (nullable NSSet<NSString *> *)sdkDisabledVersionsWithDefaultValue:
(nullable NSSet<NSString *> *)sdkVersions;
/**
* Returns the log source against which the events will be recorded.
* Name in remote config: "fpr_log_source"
*
* @param logSource Default value to be returned if values does not exist in remote config.
* @return Log source towards which the events would be logged.
*/
- (int)logSourceWithDefaultValue:(int)logSource;
#pragma mark - Rate limiting related configs.
/**
* Returns the time limit for which the event are measured against. Measured in seconds.
* Name in remote config: "fpr_rl_time_limit_sec"
*
* @param durationInSeconds Default value to be returned if values does not exist in remote config.
* @return Time limit used for rate limiting in seconds.
*/
- (int)rateLimitTimeDurationWithDefaultValue:(int)durationInSeconds;
/**
* Returns the number of trace events that are allowed when the app is in foreground.
* Name in remote config: "fpr_rl_trace_event_count_fg"
*
* @param eventCount Default value to be returned if values does not exist in remote config.
* @return Trace count limit when the app is in foreground.
*/
- (int)rateLimitTraceCountInForegroundWithDefaultValue:(int)eventCount;
/**
* Returns the number of trace events that are allowed when the app is in background.
* Name in remote config: "fpr_rl_trace_event_count_bg"
*
* @param eventCount Default value to be returned if values does not exist in remote config.
* @return Trace count limit when the app is in background.
*/
- (int)rateLimitTraceCountInBackgroundWithDefaultValue:(int)eventCount;
/**
* Returns the number of network trace events that are allowed when the app is in foreground.
* Name in remote config: "fpr_rl_network_request_event_count_fg"
*
* @param eventCount Default value to be returned if values does not exist in remote config.
* @return Network request count limit when the app is in foreground.
*/
- (int)rateLimitNetworkRequestCountInForegroundWithDefaultValue:(int)eventCount;
/**
* Returns the number of network trace events that are allowed when the app is in background.
* Name in remote config: "fpr_rl_network_request_event_count_bg"
*
* @param eventCount Default value to be returned if values does not exist in remote config.
* @return Network request count limit when the app is in background.
*/
- (int)rateLimitNetworkRequestCountInBackgroundWithDefaultValue:(int)eventCount;
#pragma mark - Sampling related configs.
/**
* Returns the sampling rate for traces. A value of 1 means all the events must be sent to the
* backend. A value of 0 means, no data must be sent. Range [0-1]. A value of -1 means the value is
* not found.
* Name in remote config: "fpr_vc_trace_sampling_rate"
*
* @param samplingRate Default value to be returned if values does not exist in remote config.
* @return Sampling rate used for the number of traces.
*/
- (float)traceSamplingRateWithDefaultValue:(float)samplingRate;
/**
* Returns the sampling rate for network requests. A value of 1 means all the events must be sent to
* the backend. A value of 0 means, no data must be sent. Range [0-1]. A value of -1 means the value
* is not found.
* Name in remote config: "fpr_vc_network_request_sampling_rate"
*
* @param samplingRate Default value to be returned if values does not exist in remote config.
* @return Sampling rate used for the number of network request traces.
*/
- (float)networkRequestSamplingRateWithDefaultValue:(float)samplingRate;
#pragma mark - Session related configs.
/**
* Returns the sampling rate for sessions. A value of 1 means all the events must be sent to the
* backend. A value of 0 means, no data must be sent. Range [0-1]. A value of -1 means the value is
* not found.
* Name in remote config: "fpr_vc_session_sampling_rate"
*
* @param samplingRate Default value to be returned if values does not exist in remote config.
* @return Session sampling rate used for the number of sessions generated.
*/
- (float)sessionSamplingRateWithDefaultValue:(float)samplingRate;
/**
* Returns the frequency at which CPU usage is measured when the app is in foreground. Measured in
* milliseconds. Name in remote config: "fpr_session_gauge_cpu_capture_frequency_fg_ms"
*
* @param defaultFrequency Default value to be returned if values does not exist in remote config.
* @return Frequency at which CPU information is captured when app is in foreground.
*/
- (int)sessionGaugeCPUCaptureFrequencyInForegroundWithDefaultValue:(int)defaultFrequency;
/**
* Returns the frequency at which CPU usage is measured when the app is in background. Measured in
* milliseconds. Name in remote config: "fpr_session_gauge_cpu_capture_frequency_bg_ms"
*
* @param defaultFrequency Default value to be returned if values does not exist in remote config.
* @return Frequency at which CPU information is captured when app is in background.
*/
- (int)sessionGaugeCPUCaptureFrequencyInBackgroundWithDefaultValue:(int)defaultFrequency;
/**
* Returns the frequency at which memory usage is measured when the app is in foreground. Measured
* in milliseconds. Name in remote config: "fpr_session_gauge_memory_capture_frequency_fg_ms"
*
* @param defaultFrequency Default value to be returned if values does not exist in remote config.
* @return Frequency at which memory information is captured when app is in foreground.
*/
- (int)sessionGaugeMemoryCaptureFrequencyInForegroundWithDefaultValue:(int)defaultFrequency;
/**
* Returns the frequency at which memory usage is measured when the app is in background. Measured
* in milliseconds. Name in remote config: "fpr_session_gauge_memory_capture_frequency_bg_ms"
*
* @param defaultFrequency Default value to be returned if values does not exist in remote config.
* @return Frequency at which memory information is captured when app is in background.
*/
- (int)sessionGaugeMemoryCaptureFrequencyInBackgroundWithDefaultValue:(int)defaultFrequency;
/**
* Returns the maximum allowed duration for the length of a session. Measured in minutes.
* Name in remote config: "fpr_session_max_duration_min"
*
* @param maxDurationInMinutes Default value to be returned if values does not exist in remote
* config.
* @return Duration for which a sessions can be active.
*/
- (int)sessionMaxDurationWithDefaultValue:(int)maxDurationInMinutes;
@end

View File

@ -0,0 +1,346 @@
// 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 <GoogleUtilities/GULUserDefaults.h>
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations+Private.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
#import "FirebasePerformance/Sources/Configurations/FPRRemoteConfigFlags+Private.h"
#import "FirebasePerformance/Sources/Configurations/FPRRemoteConfigFlags.h"
#import "FirebasePerformance/Sources/FPRConsoleLogger.h"
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
#define ONE_DAY_SECONDS 24 * 60 * 60
static 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 seconds
self.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.fprRemoteConfig
fetchAndActivateWithCompletionHandler:^(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.configKeys
enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *valueType, BOOL *stop) {
NSString *cacheKey = [NSString stringWithFormat:@"%@.%@", kFPRConfigPrefix, key];
[self.userDefaults removeObjectForKey:cacheKey];
}];
}
- (void)cacheConfigValues {
[self.configKeys
enumerateKeysAndObjectsUsingBlock:^(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 = [sdkVersionString
stringByTrimmingCharactersInSet:[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

View File

@ -0,0 +1,27 @@
// 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/Public/FirebasePerformance/FIRPerformance.h"
#import "FirebasePerformance/Sources/Public/FirebasePerformance/FIRPerformanceAttributable.h"
/**
* Extension that is added on top of the class FIRPerformance to make certain methods used
* internally within the SDK, but not public facing. A category could be ideal, but Firebase
* recommends not using categories as that mandates including -ObjC flag for build which is an extra
* step for the developer.
*/
@interface FIRPerformance (Attributable) <FIRPerformanceAttributable>
@end

View File

@ -0,0 +1,156 @@
// 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/Public/FirebasePerformance/FIRPerformance.h"
#import "FirebasePerformance/Sources/FIRPerformance+Internal.h"
#import "FirebasePerformance/Sources/FIRPerformance_Private.h"
#import "FirebasePerformance/Sources/Common/FPRConstants.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
#import "FirebasePerformance/Sources/FPRClient+Private.h"
#import "FirebasePerformance/Sources/FPRClient.h"
#import "FirebasePerformance/Sources/FPRConsoleLogger.h"
#import "FirebasePerformance/Sources/FPRDataUtils.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRInstrumentation.h"
#import "FirebasePerformance/Sources/Timer/FIRTrace+Internal.h"
static NSString *const kFirebasePerfErrorDomain = @"com.firebase.perf";
@implementation FIRPerformance
#pragma mark - Public methods
+ (instancetype)sharedInstance {
static FIRPerformance *firebasePerformance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
firebasePerformance = [[FIRPerformance alloc] init];
});
return firebasePerformance;
}
+ (FIRTrace *)startTraceWithName:(NSString *)name {
FIRTrace *trace = [[self sharedInstance] traceWithName:name];
[trace start];
return trace;
}
- (FIRTrace *)traceWithName:(NSString *)name {
if (![self isPerfConfigured]) {
FPRLogError(kFPRTraceNotCreated, @"Failed creating trace %@. Firebase is not configured.",
name);
[NSException raise:kFirebasePerfErrorDomain
format:@"The default Firebase app has not yet been configured. Add "
@"`FirebaseApp.configure()` to your application initialization."];
return nil;
}
FIRTrace *trace = [[FIRTrace alloc] initWithName:name];
return trace;
}
/**
* Checks if the SDK has been successfully configured.
*
* @return YES if SDK is configured successfully, otherwise NO.
*/
- (BOOL)isPerfConfigured {
return self.fprClient.isConfigured;
}
#pragma mark - Internal methods
- (instancetype)init {
self = [super init];
if (self) {
_customAttributes = [[NSMutableDictionary<NSString *, NSString *> alloc] init];
_customAttributesSerialQueue =
dispatch_queue_create("com.google.perf.customAttributes", DISPATCH_QUEUE_SERIAL);
_fprClient = [FPRClient sharedInstance];
}
return self;
}
- (BOOL)isDataCollectionEnabled {
return [FPRConfigurations sharedInstance].isDataCollectionEnabled;
}
- (void)setDataCollectionEnabled:(BOOL)dataCollectionEnabled {
[[FPRConfigurations sharedInstance] setDataCollectionEnabled:dataCollectionEnabled];
}
- (BOOL)isInstrumentationEnabled {
return self.fprClient.isSwizzled || [FPRConfigurations sharedInstance].isInstrumentationEnabled;
}
- (void)setInstrumentationEnabled:(BOOL)instrumentationEnabled {
[[FPRConfigurations sharedInstance] setInstrumentationEnabled:instrumentationEnabled];
if (instrumentationEnabled) {
[self.fprClient checkAndStartInstrumentation];
} else {
if (self.fprClient.isSwizzled) {
FPRLogError(kFPRInstrumentationDisabledAfterConfigure,
@"Failed to disable instrumentation because Firebase Performance has already "
@"been configured. It will be disabled when the app restarts.");
}
}
}
#pragma mark - Custom attributes related methods
- (NSDictionary<NSString *, NSString *> *)attributes {
return [self.customAttributes copy];
}
- (void)setValue:(NSString *)value forAttribute:(nonnull NSString *)attribute {
NSString *validatedName = FPRReservableAttributeName(attribute);
NSString *validatedValue = FPRValidatedAttributeValue(value);
BOOL canAddAttribute = YES;
if (validatedName == nil) {
FPRLogError(kFPRAttributeNoName,
@"Failed to initialize because of a nil or zero length attribute name.");
canAddAttribute = NO;
}
if (validatedValue == nil) {
FPRLogError(kFPRAttributeNoValue,
@"Failed to initialize because of a nil or zero length attribute value.");
canAddAttribute = NO;
}
if (self.customAttributes.allKeys.count >= kFPRMaxGlobalCustomAttributesCount) {
FPRLogError(kFPRMaxAttributesReached,
@"Only %d attributes allowed. Already reached maximum attribute count.",
kFPRMaxGlobalCustomAttributesCount);
canAddAttribute = NO;
}
if (canAddAttribute) {
// Ensure concurrency during update of attributes.
dispatch_sync(self.customAttributesSerialQueue, ^{
self.customAttributes[validatedName] = validatedValue;
});
}
}
- (NSString *)valueForAttribute:(NSString *)attribute {
// TODO(b/175053654): Should this be happening on the serial queue for thread safety?
return self.customAttributes[attribute];
}
- (void)removeAttribute:(NSString *)attribute {
[self.customAttributes removeObjectForKey:attribute];
}
@end

View File

@ -0,0 +1,33 @@
// 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/FPRClient.h"
#import "FirebasePerformance/Sources/Public/FirebasePerformance/FIRPerformance.h"
/**
* Extension that is added on top of the class FIRPerformances to make the private properties
* visible between the implementation file and the unit tests.
*/
@interface FIRPerformance ()
/** Custom attribute managed internally. */
@property(nonatomic) NSMutableDictionary<NSString *, NSString *> *customAttributes;
/** Serial queue to manage mutation of attributes. */
@property(nonatomic, readwrite) dispatch_queue_t customAttributesSerialQueue;
/** Client object used for checking the status of the performance SDK before generating events. */
@property(nonatomic, readwrite) FPRClient *fprClient;
@end

View File

@ -0,0 +1,75 @@
// 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/FPRClient.h"
#import "FirebasePerformance/Sources/Protogen/nanopb/perf_metric.nanopb.h"
@class FPRGDTLogger;
@class FPRConfigurations;
@class FIRInstallations;
/// Protocol to define the Firebase performance provider for the component framework.
@protocol FIRPerformanceProvider <NSObject>
@end
/**
* Extension that is added on top of the class FPRClient to make the private properties visible
* between the implementation file and the unit tests.
*/
@interface FPRClient ()
@property(nonatomic, getter=isConfigured, readwrite) BOOL configured;
/** GDT Logger to transmit Fireperf events to Google Data Transport. */
@property(nonatomic) FPRGDTLogger *gdtLogger;
/** The queue group all FPRClient work will run on. Used for testing only. */
@property(nonatomic, readonly) dispatch_group_t eventsQueueGroup;
/** Serial queue used for processing events. */
@property(nonatomic, readonly) dispatch_queue_t eventsQueue;
/** Firebase Remote Configuration object for FPRClient. */
@property(nonatomic) FPRConfigurations *configuration;
/** Firebase Installations object for FPRClient. */
@property(nonatomic) FIRInstallations *installations;
/** The Firebase Project ID of the project. */
@property(nonatomic, readonly) NSString *projectID;
/** The bundle ID of the project*/
@property(nonatomic, readonly) NSString *bundleID;
/**
* Determines the log directory path in the caches directory.
*
* @return The directory in which Clearcut logs are stored.
*/
+ (NSString *)logDirectoryPath;
/**
* Cleans up the log directory path in the cache directory created for Clearcut logs storage.
*
* @remark This method (cleanup logic) should stay for a while until all of our apps have migrated
* to a version which includes this logic.
*/
+ (void)cleanupClearcutCacheDirectory;
/** Performs post processing and logs a firebase_perf_v1_PerfMetric object to Google Data Transport.
* @param event Reference to a firebase_perf_v1_PerfMetric proto object.
*/
- (void)processAndLogEvent:(firebase_perf_v1_PerfMetric)event;
@end

View File

@ -0,0 +1,85 @@
// 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/Public/FirebasePerformance/FIRTrace.h"
#import "FirebasePerformance/Sources/FPRConfiguration.h"
#import "FirebasePerformance/Sources/Gauges/FPRGaugeManager.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.h"
/** NSError codes for FPRClient related errors */
typedef NS_ENUM(NSInteger, FPRClientErrorCode) {
// Generic Error.
FPRClientErrorCodeUnknown,
// Error starting the client.
FPRClientErrorCodeStartupError
};
/** This class is not exposed to the public and internally provides the primary entry point into
* the Firebase Performance module's functionality.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRClient : NSObject
/** YES if SDK is configured, otherwise NO. */
@property(nonatomic, getter=isConfigured, readonly) BOOL configured;
/** YES if methods have been swizzled, NO otherwise. */
@property(nonatomic, getter=isSwizzled) BOOL swizzled;
/** Accesses the singleton instance. All Firebase Performance methods should be managed via this
* shared instance.
* @return Reference to the shared object if successful; <code>nil</code> if not.
*/
+ (nonnull FPRClient *)sharedInstance;
/** Enables performance reporting. This installs auto instrumentation and configures metric
* uploading.
*
* @param config Configures perf reporting behavior.
* @param error Populated with an NSError instance on failure.
* @return <code>YES</code> if successful; <code>NO</code> if not.
*/
- (BOOL)startWithConfiguration:(nonnull FPRConfiguration *)config
error:(NSError *__autoreleasing _Nullable *_Nullable)error;
/** Logs a trace event.
*
* @param trace Trace event that needs to be logged to Google Data Transport.
*/
- (void)logTrace:(nonnull FIRTrace *)trace;
/** Logs a network trace event.
*
* @param trace Network trace event that needs to be logged to Google Data Transport.
*/
- (void)logNetworkTrace:(nonnull FPRNetworkTrace *)trace;
/** Logs a gauge metric event.
*
* @param gaugeData Gauge metric event that needs to be logged to Google Data Transport.
* @param sessionId SessionID with which the gauge data will be logged.
*/
- (void)logGaugeMetric:(nonnull NSArray *)gaugeData forSessionId:(nonnull NSString *)sessionId;
/** Checks if the instrumentation of the app is enabled. If enabled, setup the instrumentation. */
- (void)checkAndStartInstrumentation;
/** Unswizzles any existing methods that have been instrumented and stops automatic instrumentation
* for all future app starts unless explicitly enabled.
*/
- (void)disableInstrumentation;
@end

View File

@ -0,0 +1,370 @@
// 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/FPRClient.h"
#import "FirebasePerformance/Sources/FPRClient+Private.h"
#import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
#import "FirebasePerformance/Sources/AppActivity/FPRScreenTraceTracker+Private.h"
#import "FirebasePerformance/Sources/AppActivity/FPRScreenTraceTracker.h"
#import "FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h"
#import "FirebasePerformance/Sources/AppActivity/FPRTraceBackgroundActivityTracker.h"
#import "FirebasePerformance/Sources/Common/FPRConsoleURLGenerator.h"
#import "FirebasePerformance/Sources/Common/FPRConstants.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
#import "FirebasePerformance/Sources/Configurations/FPRRemoteConfigFlags.h"
#import "FirebasePerformance/Sources/FPRConsoleLogger.h"
#import "FirebasePerformance/Sources/FPRNanoPbUtils.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRInstrumentation.h"
#import "FirebasePerformance/Sources/Loggers/FPRGDTLogger.h"
#import "FirebasePerformance/Sources/Timer/FIRTrace+Internal.h"
#import "FirebasePerformance/Sources/Timer/FIRTrace+Private.h"
#import "FirebasePerformance/Sources/Public/FirebasePerformance/FIRPerformance.h"
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
@import FirebaseSessions;
@interface FPRClient () <FIRLibrary, FIRPerformanceProvider, FIRSessionsSubscriber>
/** The original configuration object used to initialize the client. */
@property(nonatomic, strong) FPRConfiguration *config;
/** The object that manages all automatic class instrumentation. */
@property(nonatomic) FPRInstrumentation *instrumentation;
@end
@implementation FPRClient
+ (void)load {
[FIRApp registerInternalLibrary:[FPRClient class]
withName:@"fire-perf"
withVersion:[NSString stringWithUTF8String:kFPRSDKVersion]];
[FIRSessionsDependencies addDependencyWithName:FIRSessionsSubscriberNamePerformance];
}
#pragma mark - Component registration system
+ (nonnull NSArray<FIRComponent *> *)componentsToRegister {
FIRComponentCreationBlock creationBlock =
^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
if (!container.app.isDefaultApp) {
return nil;
}
id<FIRSessionsProvider> sessions = FIR_COMPONENT(FIRSessionsProvider, container);
NSString *appName = container.app.name;
FIRApp *app = [FIRApp appNamed:appName];
FIROptions *options = app.options;
NSError *error = nil;
// Based on the environment variable SDK decides if events are dispatched to Autopush or Prod.
// By default, events are sent to Prod.
BOOL useAutoPush = NO;
NSDictionary<NSString *, NSString *> *environment = [NSProcessInfo processInfo].environment;
if (environment[@"FPR_AUTOPUSH_ENV"] != nil &&
[environment[@"FPR_AUTOPUSH_ENV"] isEqualToString:@"1"]) {
useAutoPush = YES;
}
FPRConfiguration *configuration = [FPRConfiguration configurationWithAppID:options.googleAppID
APIKey:options.APIKey
autoPush:useAutoPush];
if (![[self sharedInstance] startWithConfiguration:configuration error:&error]) {
FPRLogError(kFPRClientInitialize, @"Failed to initialize the client with error: %@.", error);
}
if (sessions) {
FPRLogDebug(kFPRClientInitialize, @"Registering Sessions SDK subscription for session data");
// Subscription should be made after the first call to [FPRClient sharedInstance] where
// _configuration is initialized so that the sessions SDK can immediately get the data
// collection state.
[sessions registerWithSubscriber:[self sharedInstance]];
}
*isCacheable = YES;
return [self sharedInstance];
};
FIRComponent *component =
[FIRComponent componentWithProtocol:@protocol(FIRPerformanceProvider)
instantiationTiming:FIRInstantiationTimingEagerInDefaultApp
creationBlock:creationBlock];
return @[ component ];
}
+ (FPRClient *)sharedInstance {
static FPRClient *sharedInstance = nil;
static dispatch_once_t token;
dispatch_once(&token, ^{
sharedInstance = [[FPRClient alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_instrumentation = [[FPRInstrumentation alloc] init];
_swizzled = NO;
_eventsQueue = dispatch_queue_create("com.google.perf.FPREventsQueue", DISPATCH_QUEUE_SERIAL);
_eventsQueueGroup = dispatch_group_create();
_configuration = [FPRConfigurations sharedInstance];
_projectID = [FIROptions defaultOptions].projectID;
_bundleID = [FIROptions defaultOptions].bundleID;
}
return self;
}
- (BOOL)startWithConfiguration:(FPRConfiguration *)config error:(NSError *__autoreleasing *)error {
self.config = config;
NSInteger logSource = [self.configuration logSource];
dispatch_group_async(self.eventsQueueGroup, self.eventsQueue, ^{
// Create the Logger for the Perf SDK events to be sent to Google Data Transport.
self.gdtLogger = [[FPRGDTLogger alloc] initWithLogSource:logSource];
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
// Create telephony network information object ahead of time to avoid runtime delays.
FPRNetworkInfo();
#endif
// Update the configuration flags.
[self.configuration update];
[FPRClient cleanupClearcutCacheDirectory];
});
// Set up instrumentation.
[self checkAndStartInstrumentation];
self.configured = YES;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
FPRLogInfo(kFPRClientInitialize,
@"Firebase Performance Monitoring is successfully initialized! In a minute, visit "
@"the Firebase console to view your data: %@",
[FPRConsoleURLGenerator generateDashboardURLWithProjectID:self.projectID
bundleID:self.bundleID]);
});
return YES;
}
- (void)checkAndStartInstrumentation {
BOOL instrumentationEnabled = self.configuration.isInstrumentationEnabled;
if (instrumentationEnabled && !self.isSwizzled) {
[self.instrumentation registerInstrumentGroup:kFPRInstrumentationGroupNetworkKey];
[self.instrumentation registerInstrumentGroup:kFPRInstrumentationGroupUIKitKey];
self.swizzled = YES;
}
}
#pragma mark - Public methods
- (void)logTrace:(FIRTrace *)trace {
if (self.configured == NO) {
FPRLogError(kFPRClientPerfNotConfigured, @"Dropping trace event %@. Perf SDK not configured.",
trace.name);
return;
}
if ([trace isCompleteAndValid]) {
dispatch_group_async(self.eventsQueueGroup, self.eventsQueue, ^{
firebase_perf_v1_PerfMetric metric = FPRGetPerfMetricMessage(self.config.appID);
FPRSetTraceMetric(&metric, FPRGetTraceMetric(trace));
FPRSetApplicationProcessState(&metric,
FPRApplicationProcessState(trace.backgroundTraceState));
// Log the trace metric with its console URL.
if ([trace.name hasPrefix:kFPRPrefixForScreenTraceName]) {
FPRLogInfo(kFPRClientMetricLogged,
@"Logging trace metric - %@ %.4fms. In a minute, visit the Firebase console to "
@"view your data: %@",
trace.name, metric.trace_metric.duration_us / 1000.0,
[FPRConsoleURLGenerator generateScreenTraceURLWithProjectID:self.projectID
bundleID:self.bundleID
traceName:trace.name]);
} else {
FPRLogInfo(kFPRClientMetricLogged,
@"Logging trace metric - %@ %.4fms. In a minute, visit the Firebase console to "
@"view your data: %@",
trace.name, metric.trace_metric.duration_us / 1000.0,
[FPRConsoleURLGenerator generateCustomTraceURLWithProjectID:self.projectID
bundleID:self.bundleID
traceName:trace.name]);
}
[self processAndLogEvent:metric];
});
} else {
FPRLogWarning(kFPRClientInvalidTrace, @"Invalid trace, skipping send.");
}
}
- (void)logNetworkTrace:(nonnull FPRNetworkTrace *)trace {
if (self.configured == NO) {
FPRLogError(kFPRClientPerfNotConfigured, @"Dropping trace event %@. Perf SDK not configured.",
trace.URLRequest.URL.absoluteString);
return;
}
dispatch_group_async(self.eventsQueueGroup, self.eventsQueue, ^{
if ([trace isValid]) {
firebase_perf_v1_NetworkRequestMetric networkRequestMetric =
FPRGetNetworkRequestMetric(trace);
int64_t duration = networkRequestMetric.has_time_to_response_completed_us
? networkRequestMetric.time_to_response_completed_us
: 0;
NSString *responseCode = networkRequestMetric.has_http_response_code
? [@(networkRequestMetric.http_response_code) stringValue]
: @"UNKNOWN";
FPRLogInfo(kFPRClientMetricLogged,
@"Logging network request trace - %@, Response code: %@, %.4fms",
trace.trimmedURLString, responseCode, duration / 1000.0);
firebase_perf_v1_PerfMetric metric = FPRGetPerfMetricMessage(self.config.appID);
FPRSetNetworkRequestMetric(&metric, networkRequestMetric);
FPRSetApplicationProcessState(&metric,
FPRApplicationProcessState(trace.backgroundTraceState));
[self processAndLogEvent:metric];
}
});
}
- (void)logGaugeMetric:(nonnull NSArray *)gaugeData forSessionId:(nonnull NSString *)sessionId {
if (self.configured == NO) {
FPRLogError(kFPRClientPerfNotConfigured, @"Dropping session event. Perf SDK not configured.");
return;
}
dispatch_group_async(self.eventsQueueGroup, self.eventsQueue, ^{
firebase_perf_v1_PerfMetric metric = FPRGetPerfMetricMessage(self.config.appID);
firebase_perf_v1_GaugeMetric gaugeMetric = firebase_perf_v1_GaugeMetric_init_default;
if ((gaugeData != nil && gaugeData.count != 0) && (sessionId != nil && sessionId.length != 0)) {
gaugeMetric = FPRGetGaugeMetric(gaugeData, sessionId);
}
FPRSetGaugeMetric(&metric, gaugeMetric);
[self processAndLogEvent:metric];
});
// Check and update the sessionID if the session is running for too long.
[[FPRSessionManager sharedInstance] stopGaugesIfRunningTooLong];
}
- (void)processAndLogEvent:(firebase_perf_v1_PerfMetric)event {
BOOL tracingEnabled = self.configuration.isDataCollectionEnabled;
if (!tracingEnabled) {
FPRLogDebug(kFPRClientPerfNotConfigured, @"Dropping event since data collection is disabled.");
return;
}
BOOL sdkEnabled = [self.configuration sdkEnabled];
if (!sdkEnabled) {
FPRLogInfo(kFPRClientSDKDisabled, @"Dropping event since Performance SDK is disabled.");
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (self.installations == nil) {
// Delayed initialization of installations because FIRApp needs to be configured first.
self.installations = [FIRInstallations installations];
}
});
// Attempts to dispatch events if successfully retrieve installation ID.
[self.installations
installationIDWithCompletion:^(NSString *_Nullable identifier, NSError *_Nullable error) {
if (error) {
FPRLogError(kFPRClientInstanceIDNotAvailable, @"FIRInstallations error: %@",
error.description);
} else {
dispatch_group_async(self.eventsQueueGroup, self.eventsQueue, ^{
firebase_perf_v1_PerfMetric updatedEvent = event;
updatedEvent.application_info.app_instance_id = FPREncodeString(identifier);
[self.gdtLogger logEvent:updatedEvent];
});
}
}];
}
#pragma mark - Clearcut log directory removal methods
+ (void)cleanupClearcutCacheDirectory {
NSString *logDirectoryPath = [FPRClient logDirectoryPath];
if (logDirectoryPath != nil) {
BOOL logDirectoryExists = [[NSFileManager defaultManager] fileExistsAtPath:logDirectoryPath];
if (logDirectoryExists) {
NSError *directoryError = nil;
[[NSFileManager defaultManager] removeItemAtPath:logDirectoryPath error:&directoryError];
if (directoryError) {
FPRLogDebug(kFPRClientTempDirectory,
@"Failed to delete the stale log directory at path: %@ with error: %@.",
logDirectoryPath, directoryError);
}
}
}
}
+ (NSString *)logDirectoryPath {
static NSString *cacheDir;
static NSString *fireperfCacheDir;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cacheDir =
[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
if (!cacheDir) {
fireperfCacheDir = nil;
} else {
fireperfCacheDir = [cacheDir stringByAppendingPathComponent:@"firebase_perf_logging"];
}
});
return fireperfCacheDir;
}
#pragma mark - Unswizzling, use only for unit tests
- (void)disableInstrumentation {
[self.instrumentation deregisterInstrumentGroup:kFPRInstrumentationGroupNetworkKey];
[self.instrumentation deregisterInstrumentGroup:kFPRInstrumentationGroupUIKitKey];
self.swizzled = NO;
[self.configuration setInstrumentationEnabled:NO];
}
#pragma mark - FIRSessionsSubscriber
- (void)onSessionChanged:(FIRSessionDetails *_Nonnull)session {
[[FPRSessionManager sharedInstance] updateSessionId:session.sessionId];
}
- (BOOL)isDataCollectionEnabled {
return self.configuration.isDataCollectionEnabled;
}
- (FIRSessionsSubscriberName)sessionsSubscriberName {
return FIRSessionsSubscriberNamePerformance;
}
@end

View File

@ -0,0 +1,50 @@
// 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 <Foundation/Foundation.h>
/**
* @brief Configures the behavior of FPR.
*/
@interface FPRConfiguration : NSObject <NSCopying>
/**
* Designated initializer.
* @brief Creates a new configuration.
*
* @param appID Identifies app on Firebase
* @param APIKey Authenticates app on Firebase
* @param autoPush Google Data Transport destination - prod/autopush
*/
- (instancetype)initWithAppID:(NSString *)appID
APIKey:(NSString *)APIKey
autoPush:(BOOL)autoPush NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
/** This is a class method for initWithAppID:APIKey:autoPush:. */
+ (instancetype)configurationWithAppID:(NSString *)appID
APIKey:(NSString *)APIKey
autoPush:(BOOL)autoPush;
/** @brief Identifies app on Firebase. */
@property(readonly, nonatomic, copy) NSString *appID;
/** @brief Authenticates app on Firebase. */
@property(readonly, nonatomic, copy) NSString *APIKey;
/** @brief Use autopush or prod logging. */
@property(readonly, nonatomic, assign, getter=isAutoPush) BOOL autoPush;
@end

View File

@ -0,0 +1,47 @@
// 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/FPRConfiguration.h"
#import "FirebasePerformance/Sources/Common/FPRDiagnostics.h"
@implementation FPRConfiguration
- (instancetype)init {
FPRAssert(NO, @"init called on NS_UNAVAILABLE init");
return nil;
}
- (instancetype)initWithAppID:(NSString *)appID APIKey:(NSString *)APIKey autoPush:(BOOL)autoPush {
self = [super init];
if (self) {
_appID = [appID copy];
_APIKey = [APIKey copy];
_autoPush = autoPush;
}
return self;
}
+ (instancetype)configurationWithAppID:(NSString *)appID
APIKey:(NSString *)APIKey
autoPush:(BOOL)autoPush {
return [[self alloc] initWithAppID:appID APIKey:APIKey autoPush:autoPush];
}
- (id)copyWithZone:(NSZone *)zone {
return self; // This class is immutable
}
@end

View File

@ -0,0 +1,95 @@
// 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 "FirebaseCore/Extension/FIRLogger.h"
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXTERN NSString* const kFIRLoggerPerf;
#define FPRLogDebug(messageCode, ...) FIRLogDebug(kFIRLoggerPerf, messageCode, __VA_ARGS__)
#define FPRLogError(messageCode, ...) FIRLogError(kFIRLoggerPerf, messageCode, __VA_ARGS__)
#define FPRLogInfo(messageCode, ...) FIRLogInfo(kFIRLoggerPerf, messageCode, __VA_ARGS__)
#define FPRLogNotice(messageCode, ...) FIRLogNotice(kFIRLoggerPerf, messageCode, __VA_ARGS__)
#define FPRLogWarning(messageCode, ...) FIRLogWarning(kFIRLoggerPerf, messageCode, __VA_ARGS__)
// FPR Client message codes.
FOUNDATION_EXTERN NSString* const kFPRClientInitialize;
FOUNDATION_EXTERN NSString* const kFPRClientTempDirectory;
FOUNDATION_EXTERN NSString* const kFPRClientCreateWorkingDirectory;
FOUNDATION_EXTERN NSString* const kFPRClientClearcutUpload;
FOUNDATION_EXTERN NSString* const kFPRClientInstanceIDNotAvailable;
FOUNDATION_EXTERN NSString* const kFPRClientNameTruncated;
FOUNDATION_EXTERN NSString* const kFPRClientNameReserved;
FOUNDATION_EXTERN NSString* const kFPRClientInvalidTrace;
FOUNDATION_EXTERN NSString* const kFPRClientMetricLogged;
FOUNDATION_EXTERN NSString* const kFPRClientDataUpload;
FOUNDATION_EXTERN NSString* const kFPRClientNameLengthCheckFailed;
FOUNDATION_EXTERN NSString* const kFPRClientPerfNotConfigured;
FOUNDATION_EXTERN NSString* const kFPRClientSDKDisabled;
// FPR Trace message codes.
FOUNDATION_EXTERN NSString* const kFPRTraceNoName;
FOUNDATION_EXTERN NSString* const kFPRTraceAlreadyStopped;
FOUNDATION_EXTERN NSString* const kFPRTraceNotStarted;
FOUNDATION_EXTERN NSString* const kFPRTraceDisabled;
FOUNDATION_EXTERN NSString* const kFPRTraceEmptyName;
FOUNDATION_EXTERN NSString* const kFPRTraceStartedNotStopped;
FOUNDATION_EXTERN NSString* const kFPRTraceNotCreated;
FOUNDATION_EXTERN NSString* const kFPRTraceInvalidName;
// FPR NetworkTrace message codes.
FOUNDATION_EXTERN NSString* const kFPRNetworkTraceFileError;
FOUNDATION_EXTERN NSString* const kFPRNetworkTraceInvalidInputs;
FOUNDATION_EXTERN NSString* const kFPRNetworkTraceURLLengthExceeds;
FOUNDATION_EXTERN NSString* const kFPRNetworkTraceURLLengthTruncation;
FOUNDATION_EXTERN NSString* const kFPRNetworkTraceNotTrackable;
// FPR LogSampler message codes.
FOUNDATION_EXTERN NSString* const kFPRSamplerInvalidConfigs;
// FPR attributes message codes.
FOUNDATION_EXTERN NSString* const kFPRAttributeNoName;
FOUNDATION_EXTERN NSString* const kFPRAttributeNoValue;
FOUNDATION_EXTERN NSString* const kFPRMaxAttributesReached;
FOUNDATION_EXTERN NSString* const kFPRAttributeNameIllegalCharacters;
// Manual network instrumentation codes.
FOUNDATION_EXTERN NSString* const kFPRInstrumentationInvalidInputs;
FOUNDATION_EXTERN NSString* const kFPRInstrumentationDisabledAfterConfigure;
// FPR diagnostic message codes.
FOUNDATION_EXTERN NSString* const kFPRDiagnosticInfo;
FOUNDATION_EXTERN NSString* const kFPRDiagnosticFailure;
FOUNDATION_EXTERN NSString* const kFPRDiagnosticLog;
// FPR Configuration related error codes.
FOUNDATION_EXTERN NSString* const kFPRConfigurationFetchFailure;
// FPR URL filtering message codes.
FOUNDATION_EXTERN NSString* const kFPRURLAllowlistingEnabled;
// FPR Gauge manager codes.
FOUNDATION_EXTERN NSString* const kFPRGaugeManagerDataCollected;
FOUNDATION_EXTERN NSString* const kFPRSessionId;
FOUNDATION_EXTERN NSString* const kFPRCPUCollection;
FOUNDATION_EXTERN NSString* const kFPRMemoryCollection;
// FPRSDKConfiguration message codes.
FOUNDATION_EXTERN NSString* const kFPRSDKFeaturesBlock;
// FPRGDTEvent message codes.
FOUNDATION_EXTERN NSString* const kFPRTransportBytesError;
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,86 @@
// 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/FPRConsoleLogger.h"
// The Firebase service used in the Firebase logger.
FIRLoggerService kFIRLoggerPerf = @"[FirebasePerformance]";
// FPR Client message codes.
NSString* const kFPRClientInitialize = @"I-PRF100001";
NSString* const kFPRClientTempDirectory = @"I-PRF100002";
NSString* const kFPRClientCreateWorkingDirectory = @"I-PRF100003";
NSString* const kFPRClientClearcutUpload = @"I-PRF100004";
NSString* const kFPRClientInstanceIDNotAvailable = @"I-PRF100005";
NSString* const kFPRClientNameTruncated = @"I-PRF100006";
NSString* const kFPRClientNameReserved = @"I-PRF100007";
NSString* const kFPRClientInvalidTrace = @"I-PRF100008";
NSString* const kFPRClientMetricLogged = @"I-PRF100009";
NSString* const kFPRClientDataUpload = @"I-PRF100010";
NSString* const kFPRClientNameLengthCheckFailed = @"I-PRF100012";
NSString* const kFPRClientPerfNotConfigured = @"I-PRF100013";
NSString* const kFPRClientSDKDisabled = @"I-PRF100014";
// FPR Trace message codes.
NSString* const kFPRTraceNoName = @"I-PRF200001";
NSString* const kFPRTraceAlreadyStopped = @"I-PRF200002";
NSString* const kFPRTraceNotStarted = @"I-PRF200003";
NSString* const kFPRTraceDisabled = @"I-PRF200004";
NSString* const kFPRTraceEmptyName = @"I-PRF200005";
NSString* const kFPRTraceStartedNotStopped = @"I-PRF200006";
NSString* const kFPRTraceNotCreated = @"I-PRF200007";
NSString* const kFPRTraceInvalidName = @"I-PRF200008";
// FPR NetworkTrace message codes.
NSString* const kFPRNetworkTraceFileError = @"I-PRF300001";
NSString* const kFPRNetworkTraceInvalidInputs = @"I-PRF300002";
NSString* const kFPRNetworkTraceURLLengthExceeds = @"I-PRF300003";
NSString* const kFPRNetworkTraceNotTrackable = @"I-PRF300004";
NSString* const kFPRNetworkTraceURLLengthTruncation = @"I-PRF300005";
// FPR LogSampler message codes.
NSString* const kFPRSamplerInvalidConfigs = @"I-PRF400001";
// FPR Attributes message codes.
NSString* const kFPRAttributeNoName = @"I-PRF500001";
NSString* const kFPRAttributeNoValue = @"I-PRF500002";
NSString* const kFPRMaxAttributesReached = @"I-PRF500003";
NSString* const kFPRAttributeNameIllegalCharacters = @"I-PRF500004";
// Manual network instrumentation codes.
NSString* const kFPRInstrumentationInvalidInputs = @"I-PRF600001";
NSString* const kFPRInstrumentationDisabledAfterConfigure = @"I-PRF600002";
// FPR diagnostic message codes.
NSString* const kFPRDiagnosticInfo = @"I-PRF700001";
NSString* const kFPRDiagnosticFailure = @"I-PRF700002";
NSString* const kFPRDiagnosticLog = @"I-PRF700003";
// FPR Configuration related error codes.
NSString* const kFPRConfigurationFetchFailure = @"I-PRF710001";
// FPR URL filtering message codes.
NSString* const kFPRURLAllowlistingEnabled = @"I-PRF800001";
// FPR Gauge manager codes.
NSString* const kFPRGaugeManagerDataCollected = @"I-PRF900001";
NSString* const kFPRSessionId = @"I-PRF900002";
NSString* const kFPRCPUCollection = @"I-PRF900003";
NSString* const kFPRMemoryCollection = @"I-PRF900004";
// FPRSDKConfiguration message codes.
NSString* const kFPRSDKFeaturesBlock = @"I-PRF910001";
// FPRGDTEvent message codes.
NSString* const kFPRTransportBytesError = @"I-PRF920001";

View File

@ -0,0 +1,48 @@
// 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 <Foundation/Foundation.h>
/** Trims the given name string and checks if the name is reservable.
*
* @param name The name to be checked for reservability.
* @return Reservable name or nil if there is an error.
*/
FOUNDATION_EXTERN NSString *FPRReservableName(NSString *name);
/** Trims the given name string and checks if the name is reservable.
*
* @param name The name to be checked for reservability for an attribute name.
* @return Reservable name or nil if there is an error.
*/
FOUNDATION_EXTERN NSString *FPRReservableAttributeName(NSString *name);
/** Checks if the given attribute value follows length restrictions.
*
* @param value The value to be checked.
* @return Valid value or nil if that does not adhere to length restrictions.
*/
FOUNDATION_EXTERN NSString *FPRValidatedAttributeValue(NSString *value);
/** Truncates the URL string if the length of the URL going beyond the defined limit. The truncation
* will happen upto the end of a complete query sub path whose length is less than limit.
* For example: If the URL is abc.com/one/two/three/four and if the URL max length is 20, trimmed
* URL will be to abc.com/one/two and not abc.com/one/two/thre (three is incomplete).
* If the domain name goes beyond 2000 characters (which is unlikely), that might result in an
* empty string being returned.
*
* @param URLString A URL string.
* @return The unchanged url string or a truncated version if the length goes beyond the limit.
*/
FOUNDATION_EXTERN NSString *FPRTruncatedURLString(NSString *URLString);

View File

@ -0,0 +1,127 @@
// 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/FPRDataUtils.h"
#import "FirebasePerformance/Sources/Common/FPRConstants.h"
#import "FirebasePerformance/Sources/FPRConsoleLogger.h"
#pragma mark - Public functions
NSString *FPRReservableName(NSString *name) {
NSString *reservableName =
[name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([reservableName hasPrefix:kFPRInternalNamePrefix]) {
FPRLogError(kFPRClientNameReserved, @"%@ prefix is reserved. Dropped %@.",
kFPRInternalNamePrefix, reservableName);
return nil;
}
if (reservableName.length == 0) {
FPRLogError(kFPRClientNameLengthCheckFailed, @"Given name is empty.");
return nil;
}
if (reservableName.length > kFPRMaxNameLength) {
FPRLogError(kFPRClientNameLengthCheckFailed, @"%@ is greater than %d characters, dropping it.",
reservableName, kFPRMaxNameLength);
return nil;
}
return reservableName;
}
NSString *FPRReservableAttributeName(NSString *name) {
NSString *reservableName =
[name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
static NSArray<NSString *> *reservedPrefix = nil;
static NSPredicate *characterCheck = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
reservedPrefix = @[ @"firebase_", @"google_", @"ga_" ];
NSString *characterRegex = @"[A-Z0-9a-z_]*";
characterCheck = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", characterRegex];
});
__block BOOL containsReservedPrefix = NO;
[reservedPrefix enumerateObjectsUsingBlock:^(NSString *prefix, NSUInteger idx, BOOL *stop) {
if ([reservableName hasPrefix:prefix]) {
FPRLogError(kFPRClientNameReserved, @"%@ prefix is reserved. Dropped %@.", prefix,
reservableName);
*stop = YES;
containsReservedPrefix = YES;
}
}];
if (containsReservedPrefix) {
return nil;
}
if (reservableName.length == 0) {
FPRLogError(kFPRClientNameLengthCheckFailed, @"Given name is empty.");
return nil;
}
if ([characterCheck evaluateWithObject:reservableName] == NO) {
FPRLogError(kFPRAttributeNameIllegalCharacters,
@"Illegal characters used for attribute name, "
"characters allowed are alphanumeric or underscore.");
return nil;
}
if (reservableName.length > kFPRMaxAttributeNameLength) {
FPRLogError(kFPRClientNameLengthCheckFailed, @"%@ is greater than %d characters, dropping it.",
reservableName, kFPRMaxAttributeNameLength);
return nil;
}
return reservableName;
}
NSString *FPRValidatedAttributeValue(NSString *value) {
if (value.length == 0) {
FPRLogError(kFPRClientNameLengthCheckFailed, @"Given value is empty.");
return nil;
}
if (value.length > kFPRMaxAttributeValueLength) {
FPRLogError(kFPRClientNameLengthCheckFailed, @"%@ is greater than %d characters, dropping it.",
value, kFPRMaxAttributeValueLength);
return nil;
}
return value;
}
NSString *FPRTruncatedURLString(NSString *URLString) {
NSString *truncatedURLString = URLString;
NSString *pathSeparator = @"/";
if (truncatedURLString.length > kFPRMaxURLLength) {
NSString *truncationCharacter =
[truncatedURLString substringWithRange:NSMakeRange(kFPRMaxURLLength, 1)];
truncatedURLString = [URLString substringToIndex:kFPRMaxURLLength];
if (![pathSeparator isEqual:truncationCharacter]) {
NSRange rangeOfTruncation = [truncatedURLString rangeOfString:pathSeparator
options:NSBackwardsSearch];
if (rangeOfTruncation.location != NSNotFound) {
truncatedURLString = [URLString substringToIndex:rangeOfTruncation.location];
}
}
FPRLogWarning(kFPRClientNameTruncated, @"URL exceeds %d characters. Truncated url: %@",
kFPRMaxURLLength, truncatedURLString);
}
return truncatedURLString;
}

View File

@ -0,0 +1,161 @@
// Copyright 2021 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 <TargetConditionals.h>
#if __has_include("CoreTelephony/CTTelephonyNetworkInfo.h") && !TARGET_OS_MACCATALYST
#define TARGET_HAS_MOBILE_CONNECTIVITY
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#endif
#import "FirebasePerformance/Sources/AppActivity/FPRTraceBackgroundActivityTracker.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.h"
#import "FirebasePerformance/Sources/Public/FirebasePerformance/FIRTrace.h"
#import "FirebasePerformance/Sources/Protogen/nanopb/perf_metric.nanopb.h"
/**nanopb struct of encoded NSDictionary<NSString *, NSString *>.*/
typedef struct {
pb_bytes_array_t* _Nonnull key;
pb_bytes_array_t* _Nonnull value;
} StringToStringMap;
/**nanopb struct of encoded NSDictionary<NSString *, NSNumber *>.*/
typedef struct {
pb_bytes_array_t* _Nonnull key;
bool has_value;
int64_t value;
} StringToNumberMap;
/** Callocs a pb_bytes_array and copies the given NSData bytes into the bytes array.
*
* @note Memory needs to be free manually, through pb_free or pb_release.
* @param data The data to copy into the new bytes array.
* @return pb_byte array
*/
extern pb_bytes_array_t* _Nullable FPREncodeData(NSData* _Nonnull data);
/** Callocs a pb_bytes_array and copies the given NSString's bytes into the bytes array.
*
* @note Memory needs to be free manually, through pb_free or pb_release.
* @param string The string to encode as pb_bytes.
* @return pb_byte array
*/
extern pb_bytes_array_t* _Nullable FPREncodeString(NSString* _Nonnull string);
/** Callocs a nanopb StringToStringMap and copies the given NSDictionary bytes into the
* StringToStringMap.
*
* @param dict The dict to copy into the new StringToStringMap.
* @return A reference to StringToStringMap
*/
extern StringToStringMap* _Nullable FPREncodeStringToStringMap(NSDictionary* _Nullable dict);
/** Callocs a nanopb StringToNumberMap and copies the given NSDictionary bytes into the
* StringToStringMap.
*
* @param dict The dict to copy into the new StringToNumberMap.
* @return A reference to StringToNumberMap
*/
extern StringToNumberMap* _Nullable FPREncodeStringToNumberMap(NSDictionary* _Nullable dict);
/** Creates a new firebase_perf_v1_PerfMetric struct populated with system metadata.
* @param appID The Google app id to put into the message
* @return A firebase_perf_v1_PerfMetric struct.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
extern firebase_perf_v1_PerfMetric FPRGetPerfMetricMessage(NSString* _Nonnull appID);
/** Creates a new firebase_perf_v1_ApplicationInfo struct populated with system metadata.
* @return A firebase_perf_v1_ApplicationInfo struct.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
extern firebase_perf_v1_ApplicationInfo FPRGetApplicationInfoMessage(void);
/** Converts the FIRTrace object to a firebase_perf_v1_TraceMetric struct.
* @return A firebase_perf_v1_TraceMetric struct.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
extern firebase_perf_v1_TraceMetric FPRGetTraceMetric(FIRTrace* _Nonnull trace);
/** Converts the FPRNetworkTrace object to a firebase_perf_v1_NetworkRequestMetric struct.
* @return A firebase_perf_v1_NetworkRequestMetric struct.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
extern firebase_perf_v1_NetworkRequestMetric FPRGetNetworkRequestMetric(
FPRNetworkTrace* _Nonnull trace);
/** Converts the gaugeData array object to a firebase_perf_v1_GaugeMetric struct.
* @return A firebase_perf_v1_GaugeMetric struct.
*/
extern firebase_perf_v1_GaugeMetric FPRGetGaugeMetric(NSArray* _Nonnull gaugeData,
NSString* _Nonnull sessionId);
/** Converts the FPRTraceState to a firebase_perf_v1_ApplicationProcessState struct.
* @return A firebase_perf_v1_ApplicationProcessState struct.
*/
extern firebase_perf_v1_ApplicationProcessState FPRApplicationProcessState(FPRTraceState state);
/** Populate a firebase_perf_v1_PerfMetric object with the given firebase_perf_v1_ApplicationInfo.
*
* @param perfMetric The reference to a firebase_perf_v1_PerfMetric object to be populated.
* @param appInfo The firebase_perf_v1_ApplicationInfo object that will be added to
* firebase_perf_v1_PerfMetric.
*/
extern void FPRSetApplicationInfo(firebase_perf_v1_PerfMetric* _Nonnull perfMetric,
firebase_perf_v1_ApplicationInfo appInfo);
/** Populate a firebase_perf_v1_PerfMetric object with the given firebase_perf_v1_TraceMetric.
*
* @param perfMetric The reference to firebase_perf_v1_PerfMetric to be populated.
* @param traceMetric The firebase_perf_v1_TraceMetric object that will be added to
* firebase_perf_v1_PerfMetric.
*/
extern void FPRSetTraceMetric(firebase_perf_v1_PerfMetric* _Nonnull perfMetric,
firebase_perf_v1_TraceMetric traceMetric);
/** Populate a firebase_perf_v1_PerfMetric object with the given
* firebase_perf_v1_NetworkRequestMetric.
*
* @param perfMetric The reference to a firebase_perf_v1_PerfMetric object to be populated.
* @param networkMetric The firebase_perf_v1_NetworkRequestMetric object that will be added to
* firebase_perf_v1_PerfMetric.
*/
extern void FPRSetNetworkRequestMetric(firebase_perf_v1_PerfMetric* _Nonnull perfMetric,
firebase_perf_v1_NetworkRequestMetric networkMetric);
/** Populate a firebase_perf_v1_PerfMetric object with the given firebase_perf_v1_GaugeMetric.
*
* @param perfMetric The reference to a firebase_perf_v1_PerfMetric object to be populated.
* @param gaugeMetric The firebase_perf_v1_GaugeMetric object that will be added to
* firebase_perf_v1_PerfMetric.
*/
extern void FPRSetGaugeMetric(firebase_perf_v1_PerfMetric* _Nonnull perfMetric,
firebase_perf_v1_GaugeMetric gaugeMetric);
/** Populate a firebase_perf_v1_PerfMetric object with the given
* firebase_perf_v1_ApplicationProcessState.
*
* @param perfMetric The reference to a firebase_perf_v1_PerfMetric object to be populated.
* @param state The firebase_perf_v1_ApplicationProcessState object that will be added to
* firebase_perf_v1_PerfMetric.
*/
extern void FPRSetApplicationProcessState(firebase_perf_v1_PerfMetric* _Nonnull perfMetric,
firebase_perf_v1_ApplicationProcessState state);
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
/** Obtain a CTTelephonyNetworkInfo object to determine device network attributes.
* @return CTTelephonyNetworkInfo object.
*/
extern CTTelephonyNetworkInfo* _Nullable FPRNetworkInfo(void);
#endif

View File

@ -0,0 +1,472 @@
// Copyright 2021 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/FPRNanoPbUtils.h"
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
#import <CoreTelephony/CTCarrier.h>
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#endif
#import "FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.h"
#import "FirebasePerformance/Sources/Common/FPRConstants.h"
#import "FirebasePerformance/Sources/FIRPerformance+Internal.h"
#import "FirebasePerformance/Sources/FPRDataUtils.h"
#import "FirebasePerformance/Sources/Public/FirebasePerformance/FIRPerformance.h"
#import "FirebasePerformance/Sources/Timer/FIRTrace+Internal.h"
#import "FirebasePerformance/Sources/Timer/FIRTrace+Private.h"
#import "FirebasePerformance/Sources/Gauges/CPU/FPRCPUGaugeData.h"
#import "FirebasePerformance/Sources/Gauges/Memory/FPRMemoryGaugeData.h"
#define BYTES_TO_KB(x) (x / 1024)
static firebase_perf_v1_NetworkRequestMetric_HttpMethod FPRHTTPMethodForString(
NSString *methodString);
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
static firebase_perf_v1_NetworkConnectionInfo_MobileSubtype FPRCellularNetworkType(void);
#endif
NSArray<FPRSessionDetails *> *FPRMakeFirstSessionVerbose(NSArray<FPRSessionDetails *> *sessions);
#pragma mark - Nanopb creation utilities
/** Converts the network method string to a value defined in the enum
* firebase_perf_v1_NetworkRequestMetric_HttpMethod.
* @return Enum value of the method string. If there is no mapping value defined for the method
* firebase_perf_v1_NetworkRequestMetric_HttpMethod_HTTP_METHOD_UNKNOWN is returned.
*/
static firebase_perf_v1_NetworkRequestMetric_HttpMethod FPRHTTPMethodForString(
NSString *methodString) {
static NSDictionary<NSString *, NSNumber *> *HTTPToFPRNetworkTraceMethod;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
HTTPToFPRNetworkTraceMethod = @{
@"GET" : @(firebase_perf_v1_NetworkRequestMetric_HttpMethod_GET),
@"POST" : @(firebase_perf_v1_NetworkRequestMetric_HttpMethod_POST),
@"PUT" : @(firebase_perf_v1_NetworkRequestMetric_HttpMethod_PUT),
@"DELETE" : @(firebase_perf_v1_NetworkRequestMetric_HttpMethod_DELETE),
@"HEAD" : @(firebase_perf_v1_NetworkRequestMetric_HttpMethod_HEAD),
@"PATCH" : @(firebase_perf_v1_NetworkRequestMetric_HttpMethod_PATCH),
@"OPTIONS" : @(firebase_perf_v1_NetworkRequestMetric_HttpMethod_OPTIONS),
@"TRACE" : @(firebase_perf_v1_NetworkRequestMetric_HttpMethod_TRACE),
@"CONNECT" : @(firebase_perf_v1_NetworkRequestMetric_HttpMethod_CONNECT),
};
});
NSNumber *HTTPMethod = HTTPToFPRNetworkTraceMethod[methodString];
if (HTTPMethod == nil) {
return firebase_perf_v1_NetworkRequestMetric_HttpMethod_HTTP_METHOD_UNKNOWN;
}
return HTTPMethod.intValue;
}
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
/** Get the current cellular network connection type in
* firebase_perf_v1_NetworkConnectionInfo_MobileSubtype format.
* @return Current cellular network connection type.
*/
static firebase_perf_v1_NetworkConnectionInfo_MobileSubtype FPRCellularNetworkType(void) {
static NSDictionary<NSString *, NSNumber *> *cellularNetworkToMobileSubtype;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
cellularNetworkToMobileSubtype = @{
CTRadioAccessTechnologyGPRS : @(firebase_perf_v1_NetworkConnectionInfo_MobileSubtype_GPRS),
CTRadioAccessTechnologyEdge : @(firebase_perf_v1_NetworkConnectionInfo_MobileSubtype_EDGE),
CTRadioAccessTechnologyWCDMA : @(firebase_perf_v1_NetworkConnectionInfo_MobileSubtype_CDMA),
CTRadioAccessTechnologyHSDPA : @(firebase_perf_v1_NetworkConnectionInfo_MobileSubtype_HSDPA),
CTRadioAccessTechnologyHSUPA : @(firebase_perf_v1_NetworkConnectionInfo_MobileSubtype_HSUPA),
CTRadioAccessTechnologyCDMA1x : @(firebase_perf_v1_NetworkConnectionInfo_MobileSubtype_CDMA),
CTRadioAccessTechnologyCDMAEVDORev0 :
@(firebase_perf_v1_NetworkConnectionInfo_MobileSubtype_EVDO_0),
CTRadioAccessTechnologyCDMAEVDORevA :
@(firebase_perf_v1_NetworkConnectionInfo_MobileSubtype_EVDO_A),
CTRadioAccessTechnologyCDMAEVDORevB :
@(firebase_perf_v1_NetworkConnectionInfo_MobileSubtype_EVDO_B),
CTRadioAccessTechnologyeHRPD : @(firebase_perf_v1_NetworkConnectionInfo_MobileSubtype_EHRPD),
CTRadioAccessTechnologyLTE : @(firebase_perf_v1_NetworkConnectionInfo_MobileSubtype_LTE)
};
});
NSDictionary<NSString *, NSString *> *radioAccessors =
FPRNetworkInfo().serviceCurrentRadioAccessTechnology;
if (radioAccessors.count > 0) {
NSString *networkString = [radioAccessors.allValues objectAtIndex:0];
NSNumber *cellularNetworkType = cellularNetworkToMobileSubtype[networkString];
return cellularNetworkType.intValue;
}
return firebase_perf_v1_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE;
}
#endif
#pragma mark - Nanopb decode and encode helper methods
pb_bytes_array_t *FPREncodeData(NSData *data) {
pb_bytes_array_t *pbBytesArray = calloc(1, PB_BYTES_ARRAY_T_ALLOCSIZE(data.length));
if (pbBytesArray != NULL) {
[data getBytes:pbBytesArray->bytes length:data.length];
pbBytesArray->size = (pb_size_t)data.length;
}
return pbBytesArray;
}
pb_bytes_array_t *FPREncodeString(NSString *string) {
NSData *stringBytes = [string dataUsingEncoding:NSUTF8StringEncoding];
return FPREncodeData(stringBytes);
}
StringToStringMap *_Nullable FPREncodeStringToStringMap(NSDictionary *_Nullable dict) {
StringToStringMap *map = calloc(dict.count, sizeof(StringToStringMap));
__block NSUInteger index = 0;
[dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) {
map[index].key = FPREncodeString(key);
map[index].value = FPREncodeString(value);
index++;
}];
return map;
}
StringToNumberMap *_Nullable FPREncodeStringToNumberMap(NSDictionary *_Nullable dict) {
StringToNumberMap *map = calloc(dict.count, sizeof(StringToNumberMap));
__block NSUInteger index = 0;
[dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *value, BOOL *stop) {
map[index].key = FPREncodeString(key);
map[index].value = [value longLongValue];
map[index].has_value = true;
index++;
}];
return map;
}
firebase_perf_v1_PerfSession *FPREncodePerfSessions(NSArray<FPRSessionDetails *> *sessions,
NSInteger count) {
firebase_perf_v1_PerfSession *perfSessions = calloc(count, sizeof(firebase_perf_v1_PerfSession));
__block NSUInteger perfSessionIndex = 0;
[sessions enumerateObjectsUsingBlock:^(FPRSessionDetails *_Nonnull session, NSUInteger index,
BOOL *_Nonnull stop) {
perfSessions[perfSessionIndex].session_id = FPREncodeString(session.sessionId);
perfSessions[perfSessionIndex].session_verbosity_count = 0;
if ((session.options & FPRSessionOptionsEvents) ||
(session.options & FPRSessionOptionsGauges)) {
perfSessions[perfSessionIndex].session_verbosity_count = 1;
perfSessions[perfSessionIndex].session_verbosity =
calloc(perfSessions[perfSessionIndex].session_verbosity_count,
sizeof(firebase_perf_v1_SessionVerbosity));
perfSessions[perfSessionIndex].session_verbosity[0] =
firebase_perf_v1_SessionVerbosity_GAUGES_AND_SYSTEM_EVENTS;
}
perfSessionIndex++;
}];
return perfSessions;
}
#pragma mark - Public methods
firebase_perf_v1_PerfMetric FPRGetPerfMetricMessage(NSString *appID) {
firebase_perf_v1_PerfMetric perfMetricMessage = firebase_perf_v1_PerfMetric_init_default;
FPRSetApplicationInfo(&perfMetricMessage, FPRGetApplicationInfoMessage());
perfMetricMessage.application_info.google_app_id = FPREncodeString(appID);
return perfMetricMessage;
}
firebase_perf_v1_ApplicationInfo FPRGetApplicationInfoMessage(void) {
firebase_perf_v1_ApplicationInfo appInfoMessage = firebase_perf_v1_ApplicationInfo_init_default;
firebase_perf_v1_IosApplicationInfo iosAppInfo = firebase_perf_v1_IosApplicationInfo_init_default;
NSBundle *mainBundle = [NSBundle mainBundle];
iosAppInfo.bundle_short_version =
FPREncodeString([mainBundle infoDictionary][@"CFBundleShortVersionString"]);
iosAppInfo.sdk_version = FPREncodeString([NSString stringWithUTF8String:kFPRSDKVersion]);
iosAppInfo.network_connection_info.network_type =
[FPRAppActivityTracker sharedInstance].networkType;
iosAppInfo.has_network_connection_info = true;
iosAppInfo.network_connection_info.has_network_type = true;
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
if (iosAppInfo.network_connection_info.network_type ==
firebase_perf_v1_NetworkConnectionInfo_NetworkType_MOBILE) {
iosAppInfo.network_connection_info.mobile_subtype = FPRCellularNetworkType();
iosAppInfo.network_connection_info.has_mobile_subtype = true;
}
#endif
appInfoMessage.ios_app_info = iosAppInfo;
appInfoMessage.has_ios_app_info = true;
NSDictionary<NSString *, NSString *> *attributes =
[[FIRPerformance sharedInstance].attributes mutableCopy];
appInfoMessage.custom_attributes_count = (pb_size_t)attributes.count;
appInfoMessage.custom_attributes =
(firebase_perf_v1_ApplicationInfo_CustomAttributesEntry *)FPREncodeStringToStringMap(
attributes);
return appInfoMessage;
}
firebase_perf_v1_TraceMetric FPRGetTraceMetric(FIRTrace *trace) {
firebase_perf_v1_TraceMetric traceMetric = firebase_perf_v1_TraceMetric_init_default;
traceMetric.name = FPREncodeString(trace.name);
// Set if the trace is an internally created trace.
traceMetric.is_auto = trace.isInternal;
traceMetric.has_is_auto = true;
// Convert the trace duration from seconds to microseconds.
traceMetric.duration_us = trace.totalTraceTimeInterval * USEC_PER_SEC;
traceMetric.has_duration_us = true;
// Convert the start time from seconds to microseconds.
traceMetric.client_start_time_us = trace.startTimeSinceEpoch * USEC_PER_SEC;
traceMetric.has_client_start_time_us = true;
// Filling counters
NSDictionary<NSString *, NSNumber *> *counters = trace.counters;
traceMetric.counters_count = (pb_size_t)counters.count;
traceMetric.counters =
(firebase_perf_v1_TraceMetric_CountersEntry *)FPREncodeStringToNumberMap(counters);
// Filling subtraces
traceMetric.subtraces_count = (pb_size_t)[trace.stages count];
firebase_perf_v1_TraceMetric *subtraces =
calloc(traceMetric.subtraces_count, sizeof(firebase_perf_v1_TraceMetric));
__block NSUInteger subtraceIndex = 0;
[trace.stages
enumerateObjectsUsingBlock:^(FIRTrace *_Nonnull stage, NSUInteger idx, BOOL *_Nonnull stop) {
subtraces[subtraceIndex] = FPRGetTraceMetric(stage);
subtraceIndex++;
}];
traceMetric.subtraces = subtraces;
// Filling custom attributes
NSDictionary<NSString *, NSString *> *attributes = [trace.attributes mutableCopy];
traceMetric.custom_attributes_count = (pb_size_t)attributes.count;
traceMetric.custom_attributes =
(firebase_perf_v1_TraceMetric_CustomAttributesEntry *)FPREncodeStringToStringMap(attributes);
// Filling session details
NSArray<FPRSessionDetails *> *orderedSessions = FPRMakeFirstSessionVerbose(trace.sessions);
traceMetric.perf_sessions_count = (pb_size_t)[orderedSessions count];
traceMetric.perf_sessions =
FPREncodePerfSessions(orderedSessions, traceMetric.perf_sessions_count);
return traceMetric;
}
firebase_perf_v1_NetworkRequestMetric FPRGetNetworkRequestMetric(FPRNetworkTrace *trace) {
firebase_perf_v1_NetworkRequestMetric networkMetric =
firebase_perf_v1_NetworkRequestMetric_init_default;
networkMetric.url = FPREncodeString(trace.trimmedURLString);
networkMetric.http_method = FPRHTTPMethodForString(trace.URLRequest.HTTPMethod);
networkMetric.has_http_method = true;
// Convert the start time from seconds to microseconds.
networkMetric.client_start_time_us = trace.startTimeSinceEpoch * USEC_PER_SEC;
networkMetric.has_client_start_time_us = true;
networkMetric.request_payload_bytes = trace.requestSize;
networkMetric.has_request_payload_bytes = true;
networkMetric.response_payload_bytes = trace.responseSize;
networkMetric.has_response_payload_bytes = true;
networkMetric.http_response_code = trace.responseCode;
networkMetric.has_http_response_code = true;
networkMetric.response_content_type = FPREncodeString(trace.responseContentType);
if (trace.responseError) {
networkMetric.network_client_error_reason =
firebase_perf_v1_NetworkRequestMetric_NetworkClientErrorReason_GENERIC_CLIENT_ERROR;
networkMetric.has_network_client_error_reason = true;
}
NSTimeInterval requestTimeUs =
USEC_PER_SEC *
[trace timeIntervalBetweenCheckpointState:FPRNetworkTraceCheckpointStateInitiated
andState:FPRNetworkTraceCheckpointStateRequestCompleted];
if (requestTimeUs > 0) {
networkMetric.time_to_request_completed_us = requestTimeUs;
networkMetric.has_time_to_request_completed_us = true;
}
NSTimeInterval responseIntiationTimeUs =
USEC_PER_SEC *
[trace timeIntervalBetweenCheckpointState:FPRNetworkTraceCheckpointStateInitiated
andState:FPRNetworkTraceCheckpointStateResponseReceived];
if (responseIntiationTimeUs > 0) {
networkMetric.time_to_response_initiated_us = responseIntiationTimeUs;
networkMetric.has_time_to_response_initiated_us = true;
}
NSTimeInterval responseCompletedUs =
USEC_PER_SEC *
[trace timeIntervalBetweenCheckpointState:FPRNetworkTraceCheckpointStateInitiated
andState:FPRNetworkTraceCheckpointStateResponseCompleted];
if (responseCompletedUs > 0) {
networkMetric.time_to_response_completed_us = responseCompletedUs;
networkMetric.has_time_to_response_completed_us = true;
}
// Filling custom attributes
NSDictionary<NSString *, NSString *> *attributes = [trace.attributes mutableCopy];
networkMetric.custom_attributes_count = (pb_size_t)attributes.count;
networkMetric.custom_attributes =
(firebase_perf_v1_NetworkRequestMetric_CustomAttributesEntry *)FPREncodeStringToStringMap(
attributes);
// Filling session details
NSArray<FPRSessionDetails *> *orderedSessions = FPRMakeFirstSessionVerbose(trace.sessions);
networkMetric.perf_sessions_count = (pb_size_t)[orderedSessions count];
networkMetric.perf_sessions =
FPREncodePerfSessions(orderedSessions, networkMetric.perf_sessions_count);
return networkMetric;
}
firebase_perf_v1_GaugeMetric FPRGetGaugeMetric(NSArray *gaugeData, NSString *sessionId) {
firebase_perf_v1_GaugeMetric gaugeMetric = firebase_perf_v1_GaugeMetric_init_default;
gaugeMetric.session_id = FPREncodeString(sessionId);
__block NSInteger cpuReadingsCount = 0;
__block NSInteger memoryReadingsCount = 0;
firebase_perf_v1_CpuMetricReading *cpuReadings =
calloc([gaugeData count], sizeof(firebase_perf_v1_CpuMetricReading));
firebase_perf_v1_IosMemoryReading *memoryReadings =
calloc([gaugeData count], sizeof(firebase_perf_v1_IosMemoryReading));
[gaugeData enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[FPRCPUGaugeData class]]) {
FPRCPUGaugeData *gaugeData = (FPRCPUGaugeData *)obj;
cpuReadings[cpuReadingsCount].client_time_us =
gaugeData.collectionTime.timeIntervalSince1970 * USEC_PER_SEC;
cpuReadings[cpuReadingsCount].has_client_time_us = true;
cpuReadings[cpuReadingsCount].system_time_us = gaugeData.systemTime;
cpuReadings[cpuReadingsCount].has_system_time_us = true;
cpuReadings[cpuReadingsCount].user_time_us = gaugeData.userTime;
cpuReadings[cpuReadingsCount].has_user_time_us = true;
cpuReadingsCount++;
}
if ([obj isKindOfClass:[FPRMemoryGaugeData class]]) {
FPRMemoryGaugeData *gaugeData = (FPRMemoryGaugeData *)obj;
memoryReadings[memoryReadingsCount].client_time_us =
gaugeData.collectionTime.timeIntervalSince1970 * USEC_PER_SEC;
memoryReadings[memoryReadingsCount].has_client_time_us = true;
memoryReadings[memoryReadingsCount].used_app_heap_memory_kb =
(int32_t)BYTES_TO_KB(gaugeData.heapUsed);
memoryReadings[memoryReadingsCount].has_used_app_heap_memory_kb = true;
memoryReadings[memoryReadingsCount].free_app_heap_memory_kb =
(int32_t)BYTES_TO_KB(gaugeData.heapAvailable);
memoryReadings[memoryReadingsCount].has_free_app_heap_memory_kb = true;
memoryReadingsCount++;
}
}];
cpuReadings = realloc(cpuReadings, cpuReadingsCount * sizeof(firebase_perf_v1_CpuMetricReading));
memoryReadings =
realloc(memoryReadings, memoryReadingsCount * sizeof(firebase_perf_v1_IosMemoryReading));
gaugeMetric.cpu_metric_readings = cpuReadings;
gaugeMetric.cpu_metric_readings_count = (pb_size_t)cpuReadingsCount;
gaugeMetric.ios_memory_readings = memoryReadings;
gaugeMetric.ios_memory_readings_count = (pb_size_t)memoryReadingsCount;
return gaugeMetric;
}
firebase_perf_v1_ApplicationProcessState FPRApplicationProcessState(FPRTraceState state) {
firebase_perf_v1_ApplicationProcessState processState =
firebase_perf_v1_ApplicationProcessState_APPLICATION_PROCESS_STATE_UNKNOWN;
switch (state) {
case FPRTraceStateForegroundOnly:
processState = firebase_perf_v1_ApplicationProcessState_FOREGROUND;
break;
case FPRTraceStateBackgroundOnly:
processState = firebase_perf_v1_ApplicationProcessState_BACKGROUND;
break;
case FPRTraceStateBackgroundAndForeground:
processState = firebase_perf_v1_ApplicationProcessState_FOREGROUND_BACKGROUND;
break;
default:
break;
}
return processState;
}
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
CTTelephonyNetworkInfo *FPRNetworkInfo(void) {
static CTTelephonyNetworkInfo *networkInfo;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
networkInfo = [[CTTelephonyNetworkInfo alloc] init];
});
return networkInfo;
}
#endif
/** Reorders the list of sessions to make sure the first session is verbose if at least one session
* in the list is verbose.
* @return Ordered list of sessions.
*/
NSArray<FPRSessionDetails *> *FPRMakeFirstSessionVerbose(NSArray<FPRSessionDetails *> *sessions) {
NSMutableArray<FPRSessionDetails *> *orderedSessions =
[[NSMutableArray<FPRSessionDetails *> alloc] initWithArray:sessions];
NSInteger firstVerboseSessionIndex = -1;
for (int i = 0; i < [sessions count]; i++) {
if ([sessions[i] isVerbose]) {
firstVerboseSessionIndex = i;
break;
}
}
if (firstVerboseSessionIndex > 0) {
FPRSessionDetails *verboseSession = orderedSessions[firstVerboseSessionIndex];
[orderedSessions removeObjectAtIndex:firstVerboseSessionIndex];
[orderedSessions insertObject:verboseSession atIndex:0];
}
return [orderedSessions copy];
}
#pragma mark - Nanopb struct fields populating helper methods
void FPRSetApplicationInfo(firebase_perf_v1_PerfMetric *perfMetric,
firebase_perf_v1_ApplicationInfo appInfo) {
perfMetric->application_info = appInfo;
perfMetric->has_application_info = true;
}
void FPRSetTraceMetric(firebase_perf_v1_PerfMetric *perfMetric,
firebase_perf_v1_TraceMetric traceMetric) {
perfMetric->trace_metric = traceMetric;
perfMetric->has_trace_metric = true;
}
void FPRSetNetworkRequestMetric(firebase_perf_v1_PerfMetric *perfMetric,
firebase_perf_v1_NetworkRequestMetric networkMetric) {
perfMetric->network_request_metric = networkMetric;
perfMetric->has_network_request_metric = true;
}
void FPRSetGaugeMetric(firebase_perf_v1_PerfMetric *perfMetric,
firebase_perf_v1_GaugeMetric gaugeMetric) {
perfMetric->gauge_metric = gaugeMetric;
perfMetric->has_gauge_metric = true;
}
void FPRSetApplicationProcessState(firebase_perf_v1_PerfMetric *perfMetric,
firebase_perf_v1_ApplicationProcessState state) {
perfMetric->application_info.application_process_state = state;
perfMetric->application_info.has_application_process_state = true;
}

View File

@ -0,0 +1,43 @@
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** Allows the filtering of URLs based on a allowlist specified in the Info.plist. */
@interface FPRURLFilter : NSObject
/** Returns a singleton URL filterer.
*
* @return The singleton instance. */
+ (instancetype)sharedInstance;
/** Default initializer is disabled.
*/
- (instancetype)init NS_UNAVAILABLE;
/** Checks the allowlist and denylist, and returns a YES or NO depending on their state.
*
* @note The current implementation is very naive. The denylist is only set by the SDK, and these
* URLs will not be allowed, even if we explicitly allow them.
*
* @param URL The URL string to check.
* @return YES if the URL should be instrumented, NO otherwise.
*/
- (BOOL)shouldInstrumentURL:(NSString *)URL;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,147 @@
// 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/FPRURLFilter.h"
#import "FirebasePerformance/Sources/FPRURLFilter_Private.h"
#import "FirebasePerformance/Sources/FPRConsoleLogger.h"
#import <GoogleDataTransport/GoogleDataTransport.h>
/** The expected key of the domain allowlist array. */
static NSString *const kFPRAllowlistDomainsKey = @"FPRWhitelistedDomains";
/** Allowlist status enums. */
typedef NS_ENUM(NSInteger, FPRURLAllowlistStatus) {
/** No allowlist is present, so the URL will be allowed. */
FPRURLAllowlistStatusDoesNotExist = 1,
/** The URL is allowed. */
FPRURLAllowlistStatusAllowed = 2,
/** The URL is NOT allowed. */
FPRURLAllowlistStatusNotAllowed = 3
};
/** Returns the set of denied URL strings.
*
* @return the set of denied URL strings.
*/
NSSet<NSString *> *GetSystemDenyListURLStrings(void) {
// The denylist of URLs for uploading events to avoid cyclic generation of those network events.
static NSSet *denylist = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
denylist = [[NSSet alloc] initWithArray:@[
[[GDTCOREndpoints uploadURLForTarget:kGDTCORTargetCCT] absoluteString],
[[GDTCOREndpoints uploadURLForTarget:kGDTCORTargetFLL] absoluteString]
]];
});
return denylist;
}
@implementation FPRURLFilter
+ (instancetype)sharedInstance {
static FPRURLFilter *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[FPRURLFilter alloc] initWithBundle:[NSBundle mainBundle]];
});
return sharedInstance;
}
- (instancetype)initWithBundle:(NSBundle *)bundle {
self = [super init];
if (self) {
_mainBundle = bundle;
_allowlistDomains = [self retrieveAllowlistFromPlist];
}
return self;
}
- (BOOL)shouldInstrumentURL:(NSString *)URL {
if ([self isURLDeniedByTheSDK:URL]) {
return NO;
}
FPRURLAllowlistStatus allowlistStatus = [self isURLAllowed:URL];
if (allowlistStatus == FPRURLAllowlistStatusDoesNotExist) {
return YES;
}
return allowlistStatus == FPRURLAllowlistStatusAllowed;
}
#pragma mark - Private helper methods
/** Determines if the URL is denied by the SDK.
*
* @param URL the URL string to check.
* @return YES if the URL is allowed by the SDK, NO otherwise.
*/
- (BOOL)isURLDeniedByTheSDK:(NSString *)URL {
BOOL shouldDenyURL = NO;
for (NSString *denyListURL in GetSystemDenyListURLStrings()) {
if ([URL hasPrefix:denyListURL]) {
shouldDenyURL = YES;
break;
}
}
return shouldDenyURL;
}
/** Determines if the URL is allowed by the Developer.
*
* @param URL The URL string to check.
* @return FPRURLAllowlistStatusAllowed if the URL is allowed,
* FPRURLAllowlistStatusNotAllowed if the URL is not allowed, or
* FPRURLAllowlistStatusDoesNotExist if the allowlist does not exist.
*/
- (FPRURLAllowlistStatus)isURLAllowed:(NSString *)URL {
if (self.allowlistDomains && !self.disablePlist) {
for (NSString *allowlistDomain in self.allowlistDomains) {
NSURLComponents *components = [[NSURLComponents alloc] initWithString:URL];
if ([components.host containsString:allowlistDomain]) {
return FPRURLAllowlistStatusAllowed;
}
}
return FPRURLAllowlistStatusNotAllowed;
}
return FPRURLAllowlistStatusDoesNotExist;
}
/** Retrieves the allowlist from an Info.plist.
*
* @return An array of the allowlist values, or nil if the allowlist key is not found.
*/
- (nullable NSArray<NSString *> *)retrieveAllowlistFromPlist {
NSArray<NSString *> *allowlist = nil;
id plistObject = [self.mainBundle objectForInfoDictionaryKey:kFPRAllowlistDomainsKey];
if (!plistObject) {
NSBundle *localBundle = [NSBundle bundleForClass:[self class]];
plistObject = [localBundle objectForInfoDictionaryKey:kFPRAllowlistDomainsKey];
}
if ([plistObject isKindOfClass:[NSArray class]]) {
FPRLogInfo(kFPRURLAllowlistingEnabled, @"A domain allowlist was detected. Domains not "
"explicitly allowlisted will not be instrumented.");
allowlist = plistObject;
}
return allowlist;
}
@end

View File

@ -0,0 +1,45 @@
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** This extension should only be used for testing. */
@interface FPRURLFilter ()
/** Set to YES to disable the retrieval of allowed domains from the Info.plist. This property
* should only be used in tests in order to prevent the need for mocks.
*/
@property(nonatomic) BOOL disablePlist;
/** List of domains that are allowed for instrumenting network requests.
*/
@property(nonatomic, readonly, nullable) NSArray *allowlistDomains;
/** NSBundle that is used for referring to allowed domains.
*/
@property(nonatomic, readonly, nullable) NSBundle *mainBundle;
/** Custom initializer to be used in unit tests for taking in a custom bundle and return an instance
* of FPRURLFilter.
*
* @param bundle Custom bundle to use for initialization.
* @return Instance of FPRURLFilter.
*/
- (instancetype)initWithBundle:(NSBundle *)bundle;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,39 @@
// 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 <Foundation/Foundation.h>
#import "FirebasePerformance/Sources/Gauges/CPU/FPRCPUGaugeCollector.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXTERN FPRCPUGaugeData *fprCollectCPUMetric(void);
/** This extension should only be used for testing. */
@interface FPRCPUGaugeCollector ()
/** @brief Override configurations. */
@property(nonatomic) FPRConfigurations *configurations;
/** @brief Stop CPU data collection. */
- (void)stopCollecting;
/** @brief Resumes CPU data collection. */
- (void)resumeCollecting;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,64 @@
// 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 <Foundation/Foundation.h>
#import "FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.h"
#import "FirebasePerformance/Sources/Gauges/CPU/FPRCPUGaugeData.h"
#import "FirebasePerformance/Sources/Gauges/FPRGaugeCollector.h"
NS_ASSUME_NONNULL_BEGIN
@class FPRCPUGaugeCollector;
/** Delegate method for the CPU Gauge collector to report back the CPU gauge data. */
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@protocol FPRCPUGaugeCollectorDelegate
/**
* Reports the collected CPU gauge data back to its delegate.
*
* @param collector CPU gauge collector that collected the information.
* @param gaugeData CPU gauge data.
*/
- (void)cpuGaugeCollector:(FPRCPUGaugeCollector *)collector gaugeData:(FPRCPUGaugeData *)gaugeData;
@end
/** CPU Gauge collector implementation. This class collects the CPU utilization and reports back
* to the delegate.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRCPUGaugeCollector : NSObject <FPRGaugeCollector>
/** Reference to the delegate object. */
@property(nonatomic, weak, readonly) id<FPRCPUGaugeCollectorDelegate> delegate;
/**
* Initializes the CPU collector object with the delegate object provided.
*
* @param delegate Delegate object to which the CPU gauge data is provided.
* @return Instance of the CPU Gauge collector.
*/
- (instancetype)initWithDelegate:(id<FPRCPUGaugeCollectorDelegate>)delegate
NS_DESIGNATED_INITIALIZER;
/**
* Initializer for the CPU Gauge collector. This is not available.
*/
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,174 @@
// 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/Gauges/CPU/FPRCPUGaugeCollector.h"
#import "FirebasePerformance/Sources/Gauges/CPU/FPRCPUGaugeCollector+Private.h"
#import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
#import "FirebasePerformance/Sources/FPRConsoleLogger.h"
#import <assert.h>
#import <mach/mach.h>
@interface FPRCPUGaugeCollector ()
/** @brief Timer property used for the frequency of CPU data collection. */
@property(nonatomic) dispatch_source_t timerSource;
/** @brief Gauge collector queue on which the gauge data collected. */
@property(nonatomic) dispatch_queue_t gaugeCollectorQueue;
/** @brief Boolean to see if the timer is active or paused. */
@property(nonatomic) BOOL timerPaused;
@end
/**
* Fetches the CPU metric and returns an instance of FPRCPUGaugeData.
*
* This implementation is inspired by the following references:
* http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/thread_basic_info.html
* https://stackoverflow.com/a/8382889
*
* @return Instance of FPRCPUGaugeData.
*/
FPRCPUGaugeData *fprCollectCPUMetric(void) {
kern_return_t kernelReturnValue;
mach_msg_type_number_t task_info_count;
task_info_data_t taskInfo;
thread_array_t threadList;
mach_msg_type_number_t threadCount;
task_basic_info_t taskBasicInfo;
thread_basic_info_t threadBasicInfo;
NSDate *collectionTime = [NSDate date];
// Get the task info to find out the CPU time used by terminated threads.
task_info_count = TASK_BASIC_INFO_COUNT;
kernelReturnValue =
task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)taskInfo, &task_info_count);
if (kernelReturnValue != KERN_SUCCESS) {
return nil;
}
taskBasicInfo = (task_basic_info_t)taskInfo;
// Get the current set of threads and find their CPU time.
kernelReturnValue = task_threads(mach_task_self(), &threadList, &threadCount);
if (kernelReturnValue != KERN_SUCCESS) {
return nil;
}
uint64_t totalUserTimeUsec =
taskBasicInfo->user_time.seconds * USEC_PER_SEC + taskBasicInfo->user_time.microseconds;
uint64_t totalSystemTimeUsec =
taskBasicInfo->system_time.seconds * USEC_PER_SEC + taskBasicInfo->system_time.microseconds;
thread_info_data_t threadInfo;
mach_msg_type_number_t threadInfoCount;
for (int i = 0; i < (int)threadCount; i++) {
threadInfoCount = THREAD_INFO_MAX;
kernelReturnValue =
thread_info(threadList[i], THREAD_BASIC_INFO, (thread_info_t)threadInfo, &threadInfoCount);
if (kernelReturnValue != KERN_SUCCESS) {
return nil;
}
threadBasicInfo = (thread_basic_info_t)threadInfo;
if (!(threadBasicInfo->flags & TH_FLAGS_IDLE)) {
totalUserTimeUsec += threadBasicInfo->user_time.seconds * USEC_PER_SEC +
threadBasicInfo->user_time.microseconds;
totalSystemTimeUsec += threadBasicInfo->system_time.seconds * USEC_PER_SEC +
threadBasicInfo->system_time.microseconds;
}
}
kernelReturnValue =
vm_deallocate(mach_task_self(), (vm_offset_t)threadList, threadCount * sizeof(thread_t));
assert(kernelReturnValue == KERN_SUCCESS);
FPRCPUGaugeData *gaugeData = [[FPRCPUGaugeData alloc] initWithCollectionTime:collectionTime
systemTime:totalSystemTimeUsec
userTime:totalUserTimeUsec];
return gaugeData;
}
@implementation FPRCPUGaugeCollector
- (instancetype)initWithDelegate:(id<FPRCPUGaugeCollectorDelegate>)delegate {
self = [super init];
if (self) {
_delegate = delegate;
_gaugeCollectorQueue =
dispatch_queue_create("com.google.firebase.FPRCPUGaugeCollector", DISPATCH_QUEUE_SERIAL);
_configurations = [FPRConfigurations sharedInstance];
_timerPaused = YES;
[self updateSamplingFrequencyForApplicationState:[FPRAppActivityTracker sharedInstance]
.applicationState];
}
return self;
}
- (void)stopCollecting {
if (self.timerPaused == NO) {
dispatch_source_cancel(self.timerSource);
self.timerPaused = YES;
}
}
- (void)resumeCollecting {
[self updateSamplingFrequencyForApplicationState:[FPRAppActivityTracker sharedInstance]
.applicationState];
}
- (void)updateSamplingFrequencyForApplicationState:(FPRApplicationState)applicationState {
uint32_t frequencyInMs = (applicationState == FPRApplicationStateBackground)
? [self.configurations cpuSamplingFrequencyInBackgroundInMS]
: [self.configurations cpuSamplingFrequencyInForegroundInMS];
[self captureCPUGaugeAtFrequency:frequencyInMs];
}
/**
* Captures the CPU gauge at a defined frequency.
*
* @param frequencyInMs Frequency at which the CPU gauges are collected.
*/
- (void)captureCPUGaugeAtFrequency:(uint32_t)frequencyInMs {
[self stopCollecting];
if (frequencyInMs > 0) {
self.timerSource =
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.gaugeCollectorQueue);
dispatch_source_set_timer(self.timerSource,
dispatch_time(DISPATCH_TIME_NOW, frequencyInMs * NSEC_PER_MSEC),
frequencyInMs * NSEC_PER_MSEC, (1ull * NSEC_PER_SEC) / 10);
FPRCPUGaugeCollector __weak *weakSelf = self;
dispatch_source_set_event_handler(weakSelf.timerSource, ^{
FPRCPUGaugeCollector *strongSelf = weakSelf;
if (strongSelf) {
[strongSelf collectMetric];
}
});
dispatch_resume(self.timerSource);
self.timerPaused = NO;
} else {
FPRLogDebug(kFPRCPUCollection, @"CPU metric collection is disabled.");
}
}
- (void)collectMetric {
FPRCPUGaugeData *gaugeMetric = fprCollectCPUMetric();
[self.delegate cpuGaugeCollector:self gaugeData:gaugeMetric];
}
@end

View File

@ -0,0 +1,49 @@
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Class that contains CPU gauge information for any point in time.
*/
@interface FPRCPUGaugeData : NSObject
/** @brief Time which which CPU data was measured. */
@property(nonatomic, readonly) NSDate *collectionTime;
/** @brief CPU system time used in microseconds. */
@property(nonatomic, readonly) uint64_t systemTime;
/** @brief CPU user time used in microseconds. */
@property(nonatomic, readonly) uint64_t userTime;
- (instancetype)init NS_UNAVAILABLE;
/**
* Creates an instance of CPU gauge data with the provided information.
*
* @param collectionTime Time at which the gauge data was collected.
* @param systemTime CPU system time in microseconds.
* @param userTime CPU user time in microseconds.
* @return Instance of CPU gauge data.
*/
- (instancetype)initWithCollectionTime:(NSDate *)collectionTime
systemTime:(uint64_t)systemTime
userTime:(uint64_t)userTime NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,31 @@
// 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/Gauges/CPU/FPRCPUGaugeData.h"
@implementation FPRCPUGaugeData
- (instancetype)initWithCollectionTime:(NSDate *)collectionTime
systemTime:(uint64_t)systemTime
userTime:(uint64_t)userTime {
self = [super init];
if (self) {
_collectionTime = collectionTime;
_systemTime = systemTime;
_userTime = userTime;
}
return self;
}
@end

View File

@ -0,0 +1,34 @@
// 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 <Foundation/Foundation.h>
#import "FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.h"
@protocol FPRGaugeCollector <NSObject>
/** Initiates a fetch for the gauge metric. */
- (void)collectMetric;
/**
* Adapts to application state and starts capturing the gauge metric at a pre-configured rate as
* defined in the configuration system.
*
* @note Call this method when the application state has changed.
*
* @param applicationState Application state.
*/
- (void)updateSamplingFrequencyForApplicationState:(FPRApplicationState)applicationState;
@end

View File

@ -0,0 +1,51 @@
// 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/Gauges/FPRGaugeManager.h"
#import "FirebasePerformance/Sources/Gauges/CPU/FPRCPUGaugeCollector.h"
#import "FirebasePerformance/Sources/Gauges/Memory/FPRMemoryGaugeCollector.h"
/** This extension should only be used for testing. */
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRGaugeManager ()
/** @brief Tracks if gauge collection is enabled. */
@property(nonatomic, readonly) BOOL gaugeCollectionEnabled;
/** @brief CPU gauge collector. */
@property(nonatomic, readwrite, nullable) FPRCPUGaugeCollector *cpuGaugeCollector;
/** @brief Memory gauge collector. */
@property(nonatomic, readwrite, nullable) FPRMemoryGaugeCollector *memoryGaugeCollector;
/** @brief Serial queue to manage gauge data collection. */
@property(nonatomic, readwrite, nonnull) dispatch_queue_t gaugeDataProtectionQueue;
/** @brief Tracks if this session is a cold start of the application. */
@property(nonatomic) BOOL isColdStart;
/**
* Creates an instance of FPRGaugeManager with the gauges required.
*/
- (nonnull instancetype)initWithGauges:(FPRGauges)gauges;
/**
* Prepares for dispatching the current set of gauge data to Google Data Transport.
*
* @param sessionId SessionId that will be used for dispatching the gauge data
*/
- (void)prepareAndDispatchCollectedGaugeDataWithSessionId:(nullable NSString *)sessionId;
@end

View File

@ -0,0 +1,79 @@
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXTERN NSInteger const kGaugeDataBatchSize;
/** List of gauges the gauge manager controls. */
typedef NS_OPTIONS(NSUInteger, FPRGauges) {
FPRGaugeNone = 0,
FPRGaugeCPU = (1 << 0),
FPRGaugeMemory = (1 << 1),
};
/** This class controls different gauge collection in the system. List of the gauges this class
manages are listed above. */
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRGaugeManager : NSObject
/** @brief List of gauges that are currently being actively captured. */
@property(nonatomic, readonly) FPRGauges activeGauges;
/**
* Creates an instance of GaugeManager.
*
* @return Instance of GaugeManager.
*/
+ (instancetype)sharedInstance;
/**
* Initializer for the gauge manager. This is not available.
*/
- (instancetype)init NS_UNAVAILABLE;
/**
* Starts collecting gauge metrics for the specified set of gauges. Calling this will dispatch all
* the currently existing gauge data and will start collecting the new data with the new sessionId.
*
* @param gauges Gauges that needs to be collected.
* @param sessionId SessionId for which the gauges are collected.
*/
- (void)startCollectingGauges:(FPRGauges)gauges forSessionId:(NSString *)sessionId;
/**
* Stops collecting gauge metrics for the specified set of gauges. Calling this will dispatch all
* the existing gauge data.
*
* @param gauges Gauges that needs to be stopped collecting.
*/
- (void)stopCollectingGauges:(FPRGauges)gauges;
/**
* Collects all the gauges.
*/
- (void)collectAllGauges;
/**
* Takes a gauge metric and tries to dispatch the gauge metric.
*
* @param gaugeMetric Gauge metric that needs to be dispatched.
*/
- (void)dispatchMetric:(id)gaugeMetric;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,223 @@
// 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/Gauges/FPRGaugeManager.h"
#import "FirebasePerformance/Sources/Gauges/FPRGaugeManager+Private.h"
#import "FirebasePerformance/Sources/Common/FPRDiagnostics.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
#import "FirebasePerformance/Sources/FPRClient.h"
#import "FirebasePerformance/Sources/Gauges/CPU/FPRCPUGaugeCollector.h"
#import "FirebasePerformance/Sources/Gauges/Memory/FPRMemoryGaugeCollector.h"
#import <UIKit/UIKit.h>
// Number of gauge data information after which that gets flushed to Google Data Transport.
NSInteger const kGaugeDataBatchSize = 25;
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRGaugeManager () <FPRCPUGaugeCollectorDelegate, FPRMemoryGaugeCollectorDelegate>
/** @brief List of gauges that are currently being actively captured. */
@property(nonatomic, readwrite) FPRGauges activeGauges;
/** @brief List of gauge information collected. Intentionally this is not a typed collection. Gauge
* data could be CPU Gauge data or Memory gauge data.
*/
@property(nonatomic) NSMutableArray *gaugeData;
/** @brief Currently active sessionID. */
@property(nonatomic, readwrite, copy) NSString *currentSessionId;
@end
@implementation FPRGaugeManager
+ (instancetype)sharedInstance {
static FPRGaugeManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[FPRGaugeManager alloc] initWithGauges:FPRGaugeNone];
});
return instance;
}
- (instancetype)initWithGauges:(FPRGauges)gauges {
self = [super init];
if (self) {
_activeGauges = FPRGaugeNone;
_gaugeData = [[NSMutableArray alloc] init];
_gaugeDataProtectionQueue =
dispatch_queue_create("com.google.perf.gaugeManager.gaugeData", DISPATCH_QUEUE_SERIAL);
_isColdStart = YES;
[self startAppActivityTracking];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidBecomeActiveNotification
object:[UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillResignActiveNotification
object:[UIApplication sharedApplication]];
}
/**
* Starts tracking the application state changes.
*/
- (void)startAppActivityTracking {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appStateChanged:)
name:UIApplicationDidBecomeActiveNotification
object:[UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appStateChanged:)
name:UIApplicationWillResignActiveNotification
object:[UIApplication sharedApplication]];
}
- (void)appStateChanged:(NSNotification *)notification {
FPRApplicationState applicationState = [FPRAppActivityTracker sharedInstance].applicationState;
[self.cpuGaugeCollector updateSamplingFrequencyForApplicationState:applicationState];
[self.memoryGaugeCollector updateSamplingFrequencyForApplicationState:applicationState];
self.isColdStart = NO;
}
#pragma mark - Implementation methods
- (BOOL)gaugeCollectionEnabled {
// Allow gauge collection to happen during cold start. During dispatch time, we do another check
// to make sure if gauge collection is enabled. This is to accommodate gauge metric collection
// during app_start scenario.
if (self.isColdStart) {
return YES;
}
// Check if the SDK is enabled to collect gauge data.
BOOL sdkEnabled = [[FPRConfigurations sharedInstance] sdkEnabled];
if (!sdkEnabled) {
return NO;
}
return [FPRConfigurations sharedInstance].isDataCollectionEnabled;
}
- (void)startCollectingGauges:(FPRGauges)gauges forSessionId:(NSString *)sessionId {
// Dispatch the already available gauge data with old sessionId.
[self prepareAndDispatchCollectedGaugeDataWithSessionId:self.currentSessionId];
self.currentSessionId = sessionId;
if (self.gaugeCollectionEnabled) {
if ((gauges & FPRGaugeCPU) == FPRGaugeCPU) {
self.cpuGaugeCollector = [[FPRCPUGaugeCollector alloc] initWithDelegate:self];
}
if ((gauges & FPRGaugeMemory) == FPRGaugeMemory) {
self.memoryGaugeCollector = [[FPRMemoryGaugeCollector alloc] initWithDelegate:self];
}
self.activeGauges = self.activeGauges | gauges;
}
}
- (void)stopCollectingGauges:(FPRGauges)gauges {
if ((gauges & FPRGaugeCPU) == FPRGaugeCPU) {
self.cpuGaugeCollector = nil;
}
if ((gauges & FPRGaugeMemory) == FPRGaugeMemory) {
self.memoryGaugeCollector = nil;
}
self.activeGauges = self.activeGauges & ~(gauges);
// Flush out all the already collected gauge metrics
[self prepareAndDispatchCollectedGaugeDataWithSessionId:self.currentSessionId];
}
- (void)collectAllGauges {
if (self.cpuGaugeCollector) {
[self.cpuGaugeCollector collectMetric];
}
if (self.memoryGaugeCollector) {
[self.memoryGaugeCollector collectMetric];
}
}
- (void)dispatchMetric:(id)gaugeMetric {
// If the gauge metric is of type CPU, then dispatch only if CPU collection is enabled.
if ([gaugeMetric isKindOfClass:[FPRCPUGaugeData class]] &&
((self.activeGauges & FPRGaugeCPU) == FPRGaugeCPU)) {
[self addGaugeData:gaugeMetric];
}
// If the gauge metric is of type memory, then dispatch only if memory collection is enabled.
if ([gaugeMetric isKindOfClass:[FPRMemoryGaugeData class]] &&
((self.activeGauges & FPRGaugeMemory) == FPRGaugeMemory)) {
[self addGaugeData:gaugeMetric];
}
}
#pragma mark - Utils
- (void)prepareAndDispatchCollectedGaugeDataWithSessionId:(nullable NSString *)sessionId {
dispatch_async(self.gaugeDataProtectionQueue, ^{
NSArray *dispatchGauges = [self.gaugeData copy];
self.gaugeData = [[NSMutableArray alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (dispatchGauges.count > 0 && sessionId != nil) {
[[FPRClient sharedInstance] logGaugeMetric:dispatchGauges forSessionId:sessionId];
FPRLogInfo(kFPRGaugeManagerDataCollected, @"Logging %lu gauge metrics.",
(unsigned long)dispatchGauges.count);
}
});
});
}
/**
* Adds the gauge to the batch and decide on when to dispatch the events to Google Data Transport.
*
* @param gauge Gauge data received from the collectors.
*/
- (void)addGaugeData:(id)gauge {
dispatch_async(self.gaugeDataProtectionQueue, ^{
if (gauge) {
[self.gaugeData addObject:gauge];
if (self.gaugeData.count >= kGaugeDataBatchSize) {
[self prepareAndDispatchCollectedGaugeDataWithSessionId:self.currentSessionId];
}
}
});
}
#pragma mark - FPRCPUGaugeCollectorDelegate methods
- (void)cpuGaugeCollector:(FPRCPUGaugeCollector *)collector gaugeData:(FPRCPUGaugeData *)gaugeData {
[self addGaugeData:gaugeData];
}
#pragma mark - FPRMemoryGaugeCollectorDelegate methods
- (void)memoryGaugeCollector:(FPRMemoryGaugeCollector *)collector
gaugeData:(FPRMemoryGaugeData *)gaugeData {
[self addGaugeData:gaugeData];
}
@end

View File

@ -0,0 +1,39 @@
// 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 <Foundation/Foundation.h>
#import "FirebasePerformance/Sources/Gauges/Memory/FPRMemoryGaugeCollector.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXTERN FPRMemoryGaugeData *fprCollectMemoryMetric(void);
/** This extension should only be used for testing. */
@interface FPRMemoryGaugeCollector ()
/** @brief Override configurations. */
@property(nonatomic) FPRConfigurations *configurations;
/** @brief Stop memory data collection. */
- (void)stopCollecting;
/** @brief Resumes memory data collection. */
- (void)resumeCollecting;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,61 @@
// 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 <Foundation/Foundation.h>
#import "FirebasePerformance/Sources/Gauges/FPRGaugeCollector.h"
#import "FirebasePerformance/Sources/Gauges/Memory/FPRMemoryGaugeData.h"
NS_ASSUME_NONNULL_BEGIN
@class FPRMemoryGaugeCollector;
/** Delegate method for the memory Gauge collector to report back the memory gauge data. */
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@protocol FPRMemoryGaugeCollectorDelegate
/**
* Reports the collected memory gauge data back to its delegate.
*
* @param collector memory gauge collector that collected the information.
* @param gaugeData memory gauge data.
*/
- (void)memoryGaugeCollector:(FPRMemoryGaugeCollector *)collector
gaugeData:(FPRMemoryGaugeData *)gaugeData;
@end
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRMemoryGaugeCollector : NSObject <FPRGaugeCollector>
/** Reference to the delegate object. */
@property(nonatomic, weak, readonly) id<FPRMemoryGaugeCollectorDelegate> delegate;
/**
* Initializes the memory collector object with the delegate object provided.
*
* @param delegate Delegate object to which the memory gauge data is provided.
* @return Instance of the memory gauge collector.
*/
- (instancetype)initWithDelegate:(id<FPRMemoryGaugeCollectorDelegate>)delegate
NS_DESIGNATED_INITIALIZER;
/**
* Initializer for the memory Gauge collector. This is not available.
*/
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,116 @@
// 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/Gauges/Memory/FPRMemoryGaugeCollector.h"
#import "FirebasePerformance/Sources/Gauges/Memory/FPRMemoryGaugeCollector+Private.h"
#import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
#import "FirebasePerformance/Sources/FPRConsoleLogger.h"
#include <malloc/malloc.h>
@interface FPRMemoryGaugeCollector ()
/** @brief Timer property used for the frequency of CPU data collection. */
@property(nonatomic) dispatch_source_t timerSource;
/** @brief Gauge collector queue on which the gauge data collected. */
@property(nonatomic) dispatch_queue_t gaugeCollectorQueue;
/** @brief Boolean to see if the timer is active or paused. */
@property(nonatomic) BOOL timerPaused;
@end
FPRMemoryGaugeData *fprCollectMemoryMetric(void) {
NSDate *collectionTime = [NSDate date];
struct mstats ms = mstats();
FPRMemoryGaugeData *gaugeData = [[FPRMemoryGaugeData alloc] initWithCollectionTime:collectionTime
heapUsed:ms.bytes_used
heapAvailable:ms.bytes_free];
return gaugeData;
}
@implementation FPRMemoryGaugeCollector
- (instancetype)initWithDelegate:(id<FPRMemoryGaugeCollectorDelegate>)delegate {
self = [super init];
if (self) {
_delegate = delegate;
_gaugeCollectorQueue =
dispatch_queue_create("com.google.firebase.FPRMemoryGaugeCollector", DISPATCH_QUEUE_SERIAL);
_configurations = [FPRConfigurations sharedInstance];
_timerPaused = YES;
[self updateSamplingFrequencyForApplicationState:[FPRAppActivityTracker sharedInstance]
.applicationState];
}
return self;
}
- (void)stopCollecting {
if (self.timerPaused == NO) {
dispatch_source_cancel(self.timerSource);
self.timerPaused = YES;
}
}
- (void)resumeCollecting {
[self updateSamplingFrequencyForApplicationState:[FPRAppActivityTracker sharedInstance]
.applicationState];
}
- (void)updateSamplingFrequencyForApplicationState:(FPRApplicationState)applicationState {
uint32_t frequencyInMs = (applicationState == FPRApplicationStateBackground)
? [self.configurations memorySamplingFrequencyInBackgroundInMS]
: [self.configurations memorySamplingFrequencyInForegroundInMS];
[self captureMemoryGaugeAtFrequency:frequencyInMs];
}
#pragma mark - Internal methods.
/**
* Captures the memory gauge at a defined frequency.
*
* @param frequencyInMs Frequency at which the memory gauges are collected.
*/
- (void)captureMemoryGaugeAtFrequency:(uint32_t)frequencyInMs {
[self stopCollecting];
if (frequencyInMs > 0) {
self.timerSource =
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.gaugeCollectorQueue);
dispatch_source_set_timer(self.timerSource,
dispatch_time(DISPATCH_TIME_NOW, frequencyInMs * NSEC_PER_MSEC),
frequencyInMs * NSEC_PER_MSEC, (1ull * NSEC_PER_SEC) / 10);
FPRMemoryGaugeCollector __weak *weakSelf = self;
dispatch_source_set_event_handler(weakSelf.timerSource, ^{
FPRMemoryGaugeCollector *strongSelf = weakSelf;
if (strongSelf) {
[strongSelf collectMetric];
}
});
dispatch_resume(self.timerSource);
self.timerPaused = NO;
} else {
FPRLogDebug(kFPRMemoryCollection, @"Memory metric collection is disabled.");
}
}
- (void)collectMetric {
FPRMemoryGaugeData *gaugeMetric = fprCollectMemoryMetric();
[self.delegate memoryGaugeCollector:self gaugeData:gaugeMetric];
}
@end

View File

@ -0,0 +1,49 @@
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Class that contains memory gauge information for any point in time.
*/
@interface FPRMemoryGaugeData : NSObject
/** @brief Time at which memory data was measured. */
@property(nonatomic, readonly) NSDate *collectionTime;
/** @brief Heap memory that is used. */
@property(nonatomic, readonly) u_long heapUsed;
/** @brief Heap memory that is available. */
@property(nonatomic, readonly) u_long heapAvailable;
- (instancetype)init NS_UNAVAILABLE;
/**
* Creates an instance of memory gauge data with the provided information.
*
* @param collectionTime Time at which the gauge data was collected.
* @param heapUsed Heap memory that is used.
* @param heapAvailable Heap memory that is available.
* @return Instance of memory gauge data.
*/
- (instancetype)initWithCollectionTime:(NSDate *)collectionTime
heapUsed:(u_long)heapUsed
heapAvailable:(u_long)heapAvailable NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,31 @@
// 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/Gauges/Memory/FPRMemoryGaugeData.h"
@implementation FPRMemoryGaugeData
- (instancetype)initWithCollectionTime:(NSDate *)collectionTime
heapUsed:(u_long)heapUsed
heapAvailable:(u_long)heapAvailable {
self = [super init];
if (self) {
_collectionTime = collectionTime;
_heapUsed = heapUsed;
_heapAvailable = heapAvailable;
}
return self;
}
@end

View File

@ -0,0 +1,23 @@
// Copyright 2019 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/ISASwizzler/FPRObjectSwizzler.h"
FOUNDATION_EXPORT const NSString *const kGULSwizzlerAssociatedObjectKey;
@interface FPRObjectSwizzler (Internal)
- (void)swizzledObjectHasBeenDeallocatedWithGeneratedSubclass:(BOOL)isInstanceOfGeneratedSubclass;
@end

View File

@ -0,0 +1,123 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** Enums that map to their OBJC-prefixed counterparts. */
typedef OBJC_ENUM(uintptr_t, GUL_ASSOCIATION){
// Is a weak association.
GUL_ASSOCIATION_ASSIGN,
// Is a nonatomic strong association.
GUL_ASSOCIATION_RETAIN_NONATOMIC,
// Is a nonatomic copy association.
GUL_ASSOCIATION_COPY_NONATOMIC,
// Is an atomic strong association.
GUL_ASSOCIATION_RETAIN,
// Is an atomic copy association.
GUL_ASSOCIATION_COPY};
/** This class handles swizzling a specific instance of a class by generating a
* dynamic subclass and installing selectors and properties onto the dynamic
* subclass. Then, the instance's class is set to the dynamic subclass. There
* should be a 1:1 ratio of object swizzlers to swizzled instances.
*/
@interface FPRObjectSwizzler : NSObject
/** The subclass that is generated. */
@property(nullable, nonatomic, readonly) Class generatedClass;
/** Sets an associated object in the runtime. This mechanism can be used to
* simulate adding properties.
*
* @param object The object that will be queried for the associated object.
* @param key The key of the associated object.
* @param value The value to associate to the swizzled object.
* @param association The mechanism to use when associating the objects.
*/
+ (void)setAssociatedObject:(id)object
key:(const void *)key
value:(nullable id)value
association:(GUL_ASSOCIATION)association;
/** Gets an associated object in the runtime. This mechanism can be used to
* simulate adding properties.
*
* @param object The object that will be queried for the associated object.
* @param key The key of the associated object.
*/
+ (nullable id)getAssociatedObject:(id)object key:(const void *)key;
/** Please use the designated initializer. */
- (instancetype)init NS_UNAVAILABLE;
/** Instantiates an object swizzler using an object it will operate on.
* Generates a new class pair.
*
* @note There is no need to store this object. After calling -swizzle, this
* object can be found by calling -gul_objectSwizzler
*
* @param object The object to be swizzled.
* @return An instance of this class.
*/
- (instancetype)initWithObject:(id)object NS_DESIGNATED_INITIALIZER;
/** Sets an associated object in the runtime. This mechanism can be used to
* simulate adding properties.
*
* @param key The key of the associated object.
* @param value The value to associate to the swizzled object.
* @param association The mechanism to use when associating the objects.
*/
- (void)setAssociatedObjectWithKey:(const void *)key
value:(id)value
association:(GUL_ASSOCIATION)association;
/** Gets an associated object in the runtime. This mechanism can be used to
* simulate adding properties.
*
* @param key The key of the associated object.
*/
- (nullable id)getAssociatedObjectForKey:(const void *)key;
/** Copies a selector from an existing class onto the generated dynamic subclass
* that this object will adopt. This mechanism can be used to add methods to
* specific instances of a class.
*
* @note Should not be called after calling -swizzle.
* @param selector The selector to add to the instance.
* @param aClass The class supplying an implementation of the method.
* @param isClassSelector A BOOL specifying whether the selector is a class or
* instance selector.
*/
- (void)copySelector:(SEL)selector fromClass:(Class)aClass isClassSelector:(BOOL)isClassSelector;
/** Swizzles the object, changing its class to the generated class. Registers
* the class pair. */
- (void)swizzle;
/** @return The value of -[objectBeingSwizzled isProxy] */
- (BOOL)isSwizzlingProxyObject;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,212 @@
// Copyright 2018 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/ISASwizzler/FPRObjectSwizzler.h"
#import <objc/runtime.h>
#import "FirebasePerformance/Sources/ISASwizzler/FPRObjectSwizzler+Internal.h"
#import "FirebasePerformance/Sources/ISASwizzler/FPRSwizzledObject.h"
@implementation FPRObjectSwizzler {
// The swizzled object.
__weak id _swizzledObject;
// The original class of the object.
Class _originalClass;
// The dynamically generated subclass of _originalClass.
Class _generatedClass;
}
#pragma mark - Class methods
+ (void)setAssociatedObject:(id)object
key:(const void *)key
value:(nullable id)value
association:(GUL_ASSOCIATION)association {
objc_AssociationPolicy resolvedAssociation;
switch (association) {
case GUL_ASSOCIATION_ASSIGN:
resolvedAssociation = OBJC_ASSOCIATION_ASSIGN;
break;
case GUL_ASSOCIATION_RETAIN_NONATOMIC:
resolvedAssociation = OBJC_ASSOCIATION_RETAIN_NONATOMIC;
break;
case GUL_ASSOCIATION_COPY_NONATOMIC:
resolvedAssociation = OBJC_ASSOCIATION_COPY_NONATOMIC;
break;
case GUL_ASSOCIATION_RETAIN:
resolvedAssociation = OBJC_ASSOCIATION_RETAIN;
break;
case GUL_ASSOCIATION_COPY:
resolvedAssociation = OBJC_ASSOCIATION_COPY;
break;
default:
break;
}
objc_setAssociatedObject(object, key, value, resolvedAssociation);
}
+ (nullable id)getAssociatedObject:(id)object key:(const void *)key {
return objc_getAssociatedObject(object, key);
}
#pragma mark - Instance methods
/** Instantiates an instance of this class.
*
* @param object The object to swizzle.
* @return An instance of this class.
*/
- (instancetype)initWithObject:(id)object {
if (object == nil) {
return nil;
}
FPRObjectSwizzler *existingSwizzler =
[[self class] getAssociatedObject:object key:&kGULSwizzlerAssociatedObjectKey];
if ([existingSwizzler isKindOfClass:[FPRObjectSwizzler class]]) {
// The object has been swizzled already, no need to swizzle again.
return existingSwizzler;
}
self = [super init];
if (self) {
_swizzledObject = object;
_originalClass = object_getClass(object);
NSString *newClassName = [NSString stringWithFormat:@"fir_%@_%@", [[NSUUID UUID] UUIDString],
NSStringFromClass(_originalClass)];
_generatedClass = objc_allocateClassPair(_originalClass, newClassName.UTF8String, 0);
NSAssert(_generatedClass, @"Wasn't able to allocate the class pair.");
}
return self;
}
- (void)copySelector:(SEL)selector fromClass:(Class)aClass isClassSelector:(BOOL)isClassSelector {
NSAssert(_generatedClass, @"This object has already been unswizzled.");
Method method = isClassSelector ? class_getClassMethod(aClass, selector)
: class_getInstanceMethod(aClass, selector);
Class targetClass = isClassSelector ? object_getClass(_generatedClass) : _generatedClass;
IMP implementation = method_getImplementation(method);
const char *typeEncoding = method_getTypeEncoding(method);
class_replaceMethod(targetClass, selector, implementation, typeEncoding);
}
- (void)setAssociatedObjectWithKey:(const void *)key
value:(id)value
association:(GUL_ASSOCIATION)association {
__strong id swizzledObject = _swizzledObject;
if (swizzledObject) {
[[self class] setAssociatedObject:swizzledObject key:key value:value association:association];
}
}
- (nullable id)getAssociatedObjectForKey:(const void *)key {
__strong id swizzledObject = _swizzledObject;
if (swizzledObject) {
return [[self class] getAssociatedObject:swizzledObject key:key];
}
return nil;
}
- (void)swizzle {
__strong id swizzledObject = _swizzledObject;
FPRObjectSwizzler *existingSwizzler =
[[self class] getAssociatedObject:swizzledObject key:&kGULSwizzlerAssociatedObjectKey];
if (existingSwizzler != nil) {
NSAssert(existingSwizzler == self, @"The swizzled object has a different swizzler.");
// The object has been swizzled already.
return;
}
if (swizzledObject) {
[FPRObjectSwizzler setAssociatedObject:swizzledObject
key:&kGULSwizzlerAssociatedObjectKey
value:self
association:GUL_ASSOCIATION_RETAIN];
[FPRSwizzledObject copyDonorSelectorsUsingObjectSwizzler:self];
NSAssert(_originalClass == object_getClass(swizzledObject),
@"The original class is not the reported class now.");
NSAssert(class_getInstanceSize(_originalClass) == class_getInstanceSize(_generatedClass),
@"The instance size of the generated class must be equal to the original class.");
objc_registerClassPair(_generatedClass);
Class doubleCheckOriginalClass __unused = object_setClass(_swizzledObject, _generatedClass);
NSAssert(_originalClass == doubleCheckOriginalClass,
@"The original class must be the same as the class returned by object_setClass");
} else {
NSAssert(NO, @"You can't swizzle a nil object");
}
}
- (void)dealloc {
// When the Zombies instrument is enabled, a zombie is created for the swizzled object upon
// deallocation. Because this zombie subclasses the generated class, the swizzler should not
// dispose it during the swizzler's deallocation.
//
// There are other special cases where the generated class might be subclassed by a third-party
// generated classes, for example: https://github.com/firebase/firebase-ios-sdk/issues/9083
// To avoid errors in such cases, the environment variable `GULGeneratedClassDisposeDisabled` can
// be set with `YES`.
NSDictionary *environment = [[NSProcessInfo processInfo] environment];
if ([[environment objectForKey:@"NSZombieEnabled"] boolValue] ||
[[environment objectForKey:@"GULGeneratedClassDisposeDisabled"] boolValue]) {
return;
}
if (_generatedClass) {
if (_swizzledObject == nil) {
// The swizzled object has been deallocated already, so the generated class can be disposed
// now.
objc_disposeClassPair(_generatedClass);
return;
}
// FPRSwizzledObject is retained by the swizzled object which means that the swizzled object is
// being deallocated now. Let's see if we should schedule the generated class disposal.
// If the swizzled object has a different class, it most likely indicates that the object was
// ISA swizzled one more time. In this case it is not safe to dispose the generated class. We
// will have to keep it to prevent a crash.
// TODO: Consider adding a flag that can be set by the host application to dispose the class
// pair unconditionally. It may be used by apps that use ISA Swizzling themself and are
// confident in disposing their subclasses.
BOOL isSwizzledObjectInstanceOfGeneratedClass =
object_getClass(_swizzledObject) == _generatedClass;
if (isSwizzledObjectInstanceOfGeneratedClass) {
Class generatedClass = _generatedClass;
// Schedule the generated class disposal after the swizzled object has been deallocated.
dispatch_async(dispatch_get_main_queue(), ^{
objc_disposeClassPair(generatedClass);
});
}
}
}
- (BOOL)isSwizzlingProxyObject {
return [_swizzledObject isProxy];
}
@end

View File

@ -0,0 +1,46 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class FPRObjectSwizzler;
/** This class exists as a method donor. These methods will be added to all objects that are
* swizzled by the object swizzler. This class should not be instantiated.
*/
@interface FPRSwizzledObject : NSObject
- (instancetype)init NS_UNAVAILABLE;
/** Copies the methods below to the swizzled object.
*
* @param objectSwizzler The swizzler to use when adding the methods below.
*/
+ (void)copyDonorSelectorsUsingObjectSwizzler:(FPRObjectSwizzler *)objectSwizzler;
#pragma mark - Donor methods.
/** @return The generated subclass. Used in respondsToSelector: calls. */
- (Class)gul_class;
/** @return The object swizzler that manages this object. */
- (FPRObjectSwizzler *)gul_objectSwizzler;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,64 @@
// Copyright 2018 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 <objc/runtime.h>
#import "FirebasePerformance/Sources/ISASwizzler/FPRObjectSwizzler+Internal.h"
#import "FirebasePerformance/Sources/ISASwizzler/FPRSwizzledObject.h"
const NSString *const kGULSwizzlerAssociatedObjectKey = @"gul_objectSwizzler";
@interface FPRSwizzledObject ()
@end
@implementation FPRSwizzledObject
+ (void)copyDonorSelectorsUsingObjectSwizzler:(FPRObjectSwizzler *)objectSwizzler {
[objectSwizzler copySelector:@selector(gul_objectSwizzler) fromClass:self isClassSelector:NO];
[objectSwizzler copySelector:@selector(gul_class) fromClass:self isClassSelector:NO];
// This is needed because NSProxy objects usually override -[NSObjectProtocol respondsToSelector:]
// and ask this question to the underlying object. Since we don't swizzle the underlying object
// but swizzle the proxy, when someone calls -[NSObjectProtocol respondsToSelector:] on the proxy,
// the answer ends up being NO even if we added new methods to the subclass through ISA Swizzling.
// To solve that, we override -[NSObjectProtocol respondsToSelector:] in such a way that takes
// into account the fact that we've added new methods.
if ([objectSwizzler isSwizzlingProxyObject]) {
[objectSwizzler copySelector:@selector(respondsToSelector:) fromClass:self isClassSelector:NO];
}
}
- (instancetype)init {
NSAssert(NO, @"Do not instantiate this class, it's only a donor class");
return nil;
}
- (FPRObjectSwizzler *)gul_objectSwizzler {
return [FPRObjectSwizzler getAssociatedObject:self key:&kGULSwizzlerAssociatedObjectKey];
}
#pragma mark - Donor methods
- (Class)gul_class {
return [[self gul_objectSwizzler] generatedClass];
}
// Only added to a class when we detect it is a proxy.
- (BOOL)respondsToSelector:(SEL)aSelector {
Class gulClass = [[self gul_objectSwizzler] generatedClass];
return [gulClass instancesRespondToSelector:aSelector] || [super respondsToSelector:aSelector];
}
@end

View File

@ -0,0 +1,37 @@
// 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/Instrumentation/FPRNetworkTrace.h"
/**
* Extension that is added on top of the class FIRHTTPMetric to make the private properties visible
* between the implementation file and the unit tests.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FIRHTTPMetric ()
/* Network trace to capture the HTTPMetric information. */
@property(nonatomic, strong) FPRNetworkTrace *networkTrace;
/**
* Marks the end time of the request.
*/
- (void)markRequestComplete;
/**
* Marks the start time of the response.
*/
- (void)markResponseStart;
@end

View File

@ -0,0 +1,155 @@
// 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/Public/FirebasePerformance/FIRHTTPMetric.h"
#import "FirebasePerformance/Sources/Instrumentation/FIRHTTPMetric+Private.h"
#import "FirebasePerformance/Sources/Common/FPRConstants.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
#import "FirebasePerformance/Sources/FPRConsoleLogger.h"
#import "FirebasePerformance/Sources/FPRDataUtils.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.h"
@interface FIRHTTPMetric ()
/* A placeholder URLRequest used for SDK metric tracking. */
@property(nonatomic, strong) NSURLRequest *URLRequest;
@end
@implementation FIRHTTPMetric
- (nullable instancetype)initWithURL:(nonnull NSURL *)URL HTTPMethod:(FIRHTTPMethod)httpMethod {
BOOL tracingEnabled = [FPRConfigurations sharedInstance].isDataCollectionEnabled;
if (!tracingEnabled) {
FPRLogInfo(kFPRTraceDisabled, @"Trace feature is disabled. Dropping http metric - %@",
URL.absoluteString);
return nil;
}
BOOL sdkEnabled = [[FPRConfigurations sharedInstance] sdkEnabled];
if (!sdkEnabled) {
FPRLogInfo(kFPRTraceDisabled, @"Dropping event since Performance SDK is disabled.");
return nil;
}
NSMutableURLRequest *URLRequest = [[NSMutableURLRequest alloc] initWithURL:URL];
NSString *HTTPMethodString = nil;
switch (httpMethod) {
case FIRHTTPMethodGET:
HTTPMethodString = @"GET";
break;
case FIRHTTPMethodPUT:
HTTPMethodString = @"PUT";
break;
case FIRHTTPMethodPOST:
HTTPMethodString = @"POST";
break;
case FIRHTTPMethodHEAD:
HTTPMethodString = @"HEAD";
break;
case FIRHTTPMethodDELETE:
HTTPMethodString = @"DELETE";
break;
case FIRHTTPMethodPATCH:
HTTPMethodString = @"PATCH";
break;
case FIRHTTPMethodOPTIONS:
HTTPMethodString = @"OPTIONS";
break;
case FIRHTTPMethodTRACE:
HTTPMethodString = @"TRACE";
break;
case FIRHTTPMethodCONNECT:
HTTPMethodString = @"CONNECT";
break;
}
[URLRequest setHTTPMethod:HTTPMethodString];
if (URLRequest && HTTPMethodString != nil) {
self = [super init];
_networkTrace = [[FPRNetworkTrace alloc] initWithURLRequest:URLRequest];
_URLRequest = [URLRequest copy];
return self;
}
FPRLogError(kFPRInstrumentationInvalidInputs, @"Invalid URL");
return nil;
}
- (void)start {
[self.networkTrace start];
}
- (void)markRequestComplete {
[self.networkTrace checkpointState:FPRNetworkTraceCheckpointStateRequestCompleted];
}
- (void)markResponseStart {
[self.networkTrace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
}
- (void)stop {
self.networkTrace.requestSize = self.requestPayloadSize;
self.networkTrace.responseSize = self.responsePayloadSize;
// Create a dummy URL Response that will be used for data extraction.
NSString *responsePayloadSize =
[[NSString alloc] initWithFormat:@"%ld", self.responsePayloadSize];
NSMutableDictionary<NSString *, NSString *> *headerFields =
[[NSMutableDictionary<NSString *, NSString *> alloc] init];
if (self.responseContentType) {
[headerFields setObject:self.responseContentType forKey:@"Content-Type"];
}
[headerFields setObject:responsePayloadSize forKey:@"Content-Length"];
if (self.responseCode == 0) {
FPRLogError(kFPRInstrumentationInvalidInputs, @"Response code not set for request - %@",
self.URLRequest.URL);
return;
}
NSHTTPURLResponse *URLResponse = [[NSHTTPURLResponse alloc] initWithURL:self.URLRequest.URL
statusCode:self.responseCode
HTTPVersion:nil
headerFields:headerFields];
[self.networkTrace didCompleteRequestWithResponse:URLResponse error:nil];
}
#pragma mark - Custom attributes related methods
- (NSDictionary<NSString *, NSString *> *)attributes {
return [self.networkTrace attributes];
}
- (void)setValue:(NSString *)value forAttribute:(nonnull NSString *)attribute {
[self.networkTrace setValue:value forAttribute:attribute];
}
- (NSString *)valueForAttribute:(NSString *)attribute {
return [self.networkTrace valueForAttribute:attribute];
}
- (void)removeAttribute:(NSString *)attribute {
[self.networkTrace removeAttribute:attribute];
}
@end

View File

@ -0,0 +1,62 @@
// 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 <Foundation/Foundation.h>
@class FPRSelectorInstrumentor;
NS_ASSUME_NONNULL_BEGIN
/**
* Each instrumented class (even classes within class clusters) needs to have its own instrumentor.
*/
@interface FPRClassInstrumentor : NSObject
/** The class being instrumented. */
@property(nonatomic, readonly) Class instrumentedClass;
/** Please use the designated initializer. */
- (instancetype)init NS_UNAVAILABLE;
/** Initializes with a class name and stores a reference to that string. This is the designated
* initializer.
*
* @param aClass The class to be instrumented.
* @return An instance of this class.
*/
- (instancetype)initWithClass:(Class)aClass NS_DESIGNATED_INITIALIZER;
/** Creates and adds a class selector instrumentor to this class instrumentor.
*
* @param selector The selector to build and add to this class instrumentor;
* @return An FPRSelectorInstrumentor if the class/selector combination exists, nil otherwise.
*/
- (nullable FPRSelectorInstrumentor *)instrumentorForClassSelector:(SEL)selector;
/** Creates and adds an instance selector instrumentor to this class instrumentor.
*
* @param selector The selector to build and add to this class instrumentor;
* @return An FPRSelectorInstrumentor if the class/selector combination exists, nil otherwise.
*/
- (nullable FPRSelectorInstrumentor *)instrumentorForInstanceSelector:(SEL)selector;
/** Swizzles the set of selector instrumentors. */
- (void)swizzle;
/** Removes all selector instrumentors and unswizzles their implementations. */
- (BOOL)unswizzle;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,103 @@
// 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/Instrumentation/FPRClassInstrumentor.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRClassInstrumentor_Private.h"
#import "FirebasePerformance/Sources/Common/FPRDiagnostics.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRSelectorInstrumentor.h"
/** Use ivars instead of properties to reduce message sending overhead. */
@interface FPRClassInstrumentor () {
// The selector instrumentors associated with this class.
NSMutableSet<FPRSelectorInstrumentor *> *_selectorInstrumentors;
}
@end
@implementation FPRClassInstrumentor
#pragma mark - Public methods
- (instancetype)init {
FPRAssert(NO, @"%@: please use the designated initializer.", NSStringFromClass([self class]));
return nil;
}
- (instancetype)initWithClass:(Class)aClass {
self = [super init];
if (self) {
FPRAssert(aClass, @"You must supply a class in order to instrument its methods");
_instrumentedClass = aClass;
_selectorInstrumentors = [[NSMutableSet<FPRSelectorInstrumentor *> alloc] init];
}
return self;
}
- (nullable FPRSelectorInstrumentor *)instrumentorForClassSelector:(SEL)selector {
return [self buildAndAddSelectorInstrumentorForSelector:selector isClassSelector:YES];
}
- (nullable FPRSelectorInstrumentor *)instrumentorForInstanceSelector:(SEL)selector {
return [self buildAndAddSelectorInstrumentorForSelector:selector isClassSelector:NO];
}
- (void)swizzle {
for (FPRSelectorInstrumentor *selectorInstrumentor in _selectorInstrumentors) {
[selectorInstrumentor swizzle];
}
}
- (BOOL)unswizzle {
for (FPRSelectorInstrumentor *selectorInstrumentor in _selectorInstrumentors) {
[selectorInstrumentor unswizzle];
}
[_selectorInstrumentors removeAllObjects];
return _selectorInstrumentors.count == 0;
}
#pragma mark - Private methods
/** Creates and adds a selector instrumentor to this class instrumentor.
*
* @param selector The selector to build and add to this class instrumentor;
* @param isClassSelector If YES, then the selector is a class selector.
* @return An FPRSelectorInstrumentor if the class/selector combination exists, nil otherwise.
*/
- (nullable FPRSelectorInstrumentor *)buildAndAddSelectorInstrumentorForSelector:(SEL)selector
isClassSelector:
(BOOL)isClassSelector {
FPRSelectorInstrumentor *selectorInstrumentor =
[[FPRSelectorInstrumentor alloc] initWithSelector:selector
class:_instrumentedClass
isClassSelector:isClassSelector];
if (selectorInstrumentor) {
[self addSelectorInstrumentor:selectorInstrumentor];
}
return selectorInstrumentor;
}
/** Adds a selector instrumentors to an existing running list of instrumented selectors.
*
* @param selectorInstrumentor A non-nil selector instrumentor, whose SEL objects will be swizzled.
*/
- (void)addSelectorInstrumentor:(nonnull FPRSelectorInstrumentor *)selectorInstrumentor {
if ([_selectorInstrumentors containsObject:selectorInstrumentor]) {
FPRAssert(NO, @"You cannot instrument the same selector (%@) twice",
NSStringFromSelector(selectorInstrumentor.selector));
}
[_selectorInstrumentors addObject:selectorInstrumentor];
}
@end

View File

@ -0,0 +1,24 @@
// 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.
NS_ASSUME_NONNULL_BEGIN
@interface FPRClassInstrumentor ()
/** The set of selector instrumentors on this class. Only used for testing. */
@property(nonatomic, readonly) NSSet *selectorInstrumentors;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,60 @@
// 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 <Foundation/Foundation.h>
@class FPRClassInstrumentor;
NS_ASSUME_NONNULL_BEGIN
/** FPRInstrument instances can instrument many different classes, but should try to instrument
* only a single class in the general case. Due to class clusters, FPRInstruments need to be able
* to support logical groups of classes, even if the public API is a single class (e.g.
* NSDictionary or NSURLSession. FPRInstrument is expected to be subclassed by other classes that
* actually implement the instrument. Subclasses should provide their own implementations of
* registerInstrumentor
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRInstrument : NSObject
/** The list of class instrumentors. count should == 1 in most cases, and be > 1 for class clusters.
*/
@property(nonatomic, readonly) NSArray<FPRClassInstrumentor *> *classInstrumentors;
/** A set of the instrumented classes. */
@property(nonatomic, readonly) NSSet<Class> *instrumentedClasses;
/**
* Checks if the given object is instrumentable and returns YES if instrumentable. NO, otherwise.
*
* @param object Object that needs to be validated.
* @return Yes if instrumentable, NO otherwise.
*/
- (BOOL)isObjectInstrumentable:(id)object;
/** Registers all instrumentors this instrument will utilize. Should be instrumented in a subclass.
*
* @note This method is thread-safe.
*/
- (void)registerInstrumentors;
/** Deregisters the instrumentors by using API provided by FPRClassInstrumentor. Called by dealloc.
*
* @note This method is thread-safe.
*/
- (void)deregisterInstrumentors;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,78 @@
// 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/Instrumentation/FPRInstrument.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRInstrument_Private.h"
#import "FirebasePerformance/Sources/Common/FPRDiagnostics.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRClassInstrumentor.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRObjectInstrumentor.h"
@implementation FPRInstrument {
NSMutableArray<FPRClassInstrumentor *> *_classInstrumentors;
NSMutableSet<Class> *_instrumentedClasses;
}
- (instancetype)init {
self = [super init];
if (self) {
_classInstrumentors = [[NSMutableArray<FPRClassInstrumentor *> alloc] init];
_instrumentedClasses = [[NSMutableSet<Class> alloc] init];
}
return self;
}
- (NSMutableArray<FPRClassInstrumentor *> *)classInstrumentors {
return _classInstrumentors;
}
- (NSMutableSet<Class> *)instrumentedClasses {
return _instrumentedClasses;
}
- (void)registerInstrumentors {
FPRAssert(NO, @"registerInstrumentors should be implemented in a concrete subclass.");
}
- (BOOL)isObjectInstrumentable:(id)object {
if ([object isKindOfClass:[NSOperation class]]) {
return NO;
}
return YES;
}
- (BOOL)registerClassInstrumentor:(FPRClassInstrumentor *)instrumentor {
@synchronized(self) {
if ([_instrumentedClasses containsObject:instrumentor.instrumentedClass] ||
[instrumentor.instrumentedClass instancesRespondToSelector:@selector(gul_class)]) {
return NO;
}
[_instrumentedClasses addObject:instrumentor.instrumentedClass];
[_classInstrumentors addObject:instrumentor];
return YES;
}
}
- (void)deregisterInstrumentors {
@synchronized(self) {
for (FPRClassInstrumentor *classInstrumentor in self.classInstrumentors) {
[classInstrumentor unswizzle];
}
[_classInstrumentors removeAllObjects];
[_instrumentedClasses removeAllObjects];
}
}
@end

View File

@ -0,0 +1,30 @@
// 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/Instrumentation/FPRInstrument.h"
NS_ASSUME_NONNULL_BEGIN
@interface FPRInstrument ()
/** Registers an instrumentor for a class. Should be called by subclasses.
*
* @param instrumentor The instrumentor to register.
* @return NO if the class has already been instrumented, YES otherwise.
*/
- (BOOL)registerClassInstrumentor:(FPRClassInstrumentor *)instrumentor;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,45 @@
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** The key for the network instrumentation group. */
FOUNDATION_EXTERN NSString *const kFPRInstrumentationGroupNetworkKey;
/** The key for the UIKit instrumentation group. */
FOUNDATION_EXTERN NSString *const kFPRInstrumentationGroupUIKitKey;
/** This class manages all automatic instrumentation. */
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRInstrumentation : NSObject
/** Registers the instrument group.
*
* @param group The group whose instrumentation should be registered.
* @return The number of instruments in the group.
*/
- (NSUInteger)registerInstrumentGroup:(NSString *)group;
/** Deregisters the instrument group.
*
* @param group The group whose instrumentation should be deregistered.
* @return YES if there are no registered instruments in the group, NO otherwise.
*/
- (BOOL)deregisterInstrumentGroup:(NSString *)group;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,92 @@
// 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/Instrumentation/FPRInstrumentation.h"
#import "FirebasePerformance/Sources/Common/FPRDiagnostics.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRInstrument.h"
#import "FirebasePerformance/Sources/Instrumentation/Network/FPRNSURLConnectionInstrument.h"
#import "FirebasePerformance/Sources/Instrumentation/Network/FPRNSURLSessionInstrument.h"
#import "FirebasePerformance/Sources/Instrumentation/UIKit/FPRUIViewControllerInstrument.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
// The instrumentation group keys.
NSString *const kFPRInstrumentationGroupNetworkKey = @"network";
NSString *const kFPRInstrumentationGroupUIKitKey = @"uikit";
/** Use ivars instead of properties to reduce message sending overhead. */
@interface FPRInstrumentation () {
// A dictionary of the instrument groups.
NSDictionary<NSString *, NSMutableArray *> *_instrumentGroups;
}
/** Registers an instrument in the given group.
*
* @param instrument The instrument to register.
* @param group The group to register the instrument in.
*/
- (void)registerInstrument:(FPRInstrument *)instrument group:(NSString *)group;
@end
@implementation FPRInstrumentation
- (instancetype)init {
self = [super init];
if (self) {
_instrumentGroups = @{
kFPRInstrumentationGroupNetworkKey : [[NSMutableArray alloc] init],
kFPRInstrumentationGroupUIKitKey : [[NSMutableArray alloc] init]
};
}
return self;
}
- (void)registerInstrument:(FPRInstrument *)instrument group:(NSString *)group {
FPRAssert(instrument, @"Instrument must be non-nil.");
FPRAssert(_instrumentGroups[group], @"groups and group must be non-nil, and groups[group] must be"
"non-nil.");
if (instrument != nil) {
[_instrumentGroups[group] addObject:instrument];
}
[instrument registerInstrumentors];
}
- (NSUInteger)registerInstrumentGroup:(NSString *)group {
FPRAssert(_instrumentGroups[group], @"The group key does not exist", group);
FPRAssert(_instrumentGroups[group].count == 0, @"This group is already instrumented");
if ([group isEqualToString:kFPRInstrumentationGroupNetworkKey]) {
[self registerInstrument:[[FPRNSURLSessionInstrument alloc] init] group:group];
[self registerInstrument:[[FPRNSURLConnectionInstrument alloc] init] group:group];
}
if ([group isEqualToString:kFPRInstrumentationGroupUIKitKey]) {
[self registerInstrument:[[FPRUIViewControllerInstrument alloc] init] group:group];
}
return _instrumentGroups[group].count;
}
- (BOOL)deregisterInstrumentGroup:(NSString *)group {
FPRAssert(_instrumentGroups[group], @"You're attempting to deregister an invalid group key.");
for (FPRInstrument *instrument in _instrumentGroups[group]) {
[instrument deregisterInstrumentors];
}
[_instrumentGroups[group] removeAllObjects];
return _instrumentGroups[group].count == 0;
}
@end

View File

@ -0,0 +1,31 @@
// 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/Instrumentation/FPRNetworkTrace.h"
#import "FirebasePerformance/Sources/AppActivity/FPRSessionDetails.h"
/**
* Extension that is added on top of the class FPRNetworkTrace to make the private properties
* visible between the implementation file and the unit tests.
*/
@interface FPRNetworkTrace ()
/** @brief List of sessions the trace is associated with. */
@property(nonatomic, readwrite, nonnull) NSMutableArray<FPRSessionDetails *> *activeSessions;
/** Serial queue to manage concurrency. */
@property(nonatomic, readwrite, nonnull) dispatch_queue_t syncQueue;
@end

View File

@ -0,0 +1,195 @@
// 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/AppActivity/FPRTraceBackgroundActivityTracker.h"
#import "FirebasePerformance/Sources/AppActivity/FPRSessionDetails.h"
#import "FirebasePerformance/Sources/FIRPerformance+Internal.h"
/** Possible checkpoint states of network trace */
typedef NS_ENUM(NSInteger, FPRNetworkTraceCheckpointState) {
FPRNetworkTraceCheckpointStateUnknown,
// Network request has been initiated.
FPRNetworkTraceCheckpointStateInitiated,
// Network request is completed (All necessary uploads for the request is complete).
FPRNetworkTraceCheckpointStateRequestCompleted,
// Network request has received its first response. There could be more.
FPRNetworkTraceCheckpointStateResponseReceived,
// Network request has completed (Could be network error/request successful completion).
FPRNetworkTraceCheckpointStateResponseCompleted
};
@protocol FPRNetworkResponseHandler <NSObject>
/**
* Records the size of the file that is uploaded during the request.
*
* @param URL The URL object that is being used for uploading from the network request.
*/
- (void)didUploadFileWithURL:(nullable NSURL *)URL;
/**
* Records the amount of data that is fetched during the request. This can be called multiple times
* when the network delegate comes back with some data.
*
* @param data The data object as received from the network request.
*/
- (void)didReceiveData:(nullable NSData *)data;
/**
* Records the size of the file that is fetched during the request. This can be called multiple
* times when the network delegate comes back with some data.
*
* @param URL The URL object as received from the network request.
*/
- (void)didReceiveFileURL:(nullable NSURL *)URL;
/**
* Records the end state of the network request. This is usually called at the end of the network
* request with a valid response or an error.
*
* @param response Response of the network request.
* @param error Error with the network request.
*/
- (void)didCompleteRequestWithResponse:(nullable NSURLResponse *)response
error:(nullable NSError *)error;
@end
/**
* FPRNetworkTrace object contains information about an NSURLRequest. Every object contains
* information about the URL, type of request, and details of the response.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRNetworkTrace : NSObject <FPRNetworkResponseHandler, FIRPerformanceAttributable>
/** @brief Start time of the trace since epoch. */
@property(nonatomic, assign, readonly) NSTimeInterval startTimeSinceEpoch;
/** @brief The size of the request. The value is in bytes. */
@property(nonatomic) int64_t requestSize;
/** @brief The response size for the request. The value is in bytes. */
@property(nonatomic) int64_t responseSize;
/** @brief The HTTP response code for the request. */
@property(nonatomic) int32_t responseCode;
/** @brief Yes if a valid response code is set, NO otherwise. */
@property(nonatomic) BOOL hasValidResponseCode;
/** @brief The content type of the request as received from the server. */
@property(nonatomic, copy, nullable) NSString *responseContentType;
/** @brief The checkpoint states for the request. The key to the dictionary is the value referred in
* enum FPRNetworkTraceCheckpointState mentioned above. The value is the number of seconds since the
* reference date.
*/
@property(nonatomic, readonly, nullable) NSDictionary<NSString *, NSNumber *> *checkpointStates;
/** @brief The network request object. */
@property(nonatomic, readonly, nullable) NSURLRequest *URLRequest;
/** @brief The URL string with all the query params cleaned. The URL string will be of the format:
* scheme:[//[user:password@]host[:port]][/]path.
*/
@property(nonatomic, readonly, nullable) NSString *trimmedURLString;
/** @brief Error object received with the network response. */
@property(nonatomic, readonly, nullable) NSError *responseError;
/** Background state of the trace. */
@property(nonatomic, readonly) FPRTraceState backgroundTraceState;
/** @brief List of sessions the trace is associated with. */
@property(nonnull, atomic, readonly) NSArray<FPRSessionDetails *> *sessions;
/** @brief Serial queue to manage usage of session Ids. */
@property(nonatomic, readonly, nonnull) dispatch_queue_t sessionIdSerialQueue;
/**
* Associate a network trace to an object project. This uses ObjC runtime to associate the network
* trace with the object provided.
*
* @param networkTrace Network trace object to be associated with the provided object.
* @param object The provided object to whom the network trace object will be associated with.
*/
+ (void)addNetworkTrace:(nonnull FPRNetworkTrace *)networkTrace toObject:(nonnull id)object;
/**
* Gets the network trace associated with the provided object. If the network trace is not
* associated with the object, return nil. This uses ObjC runtime to fetch the object.
*
* @param object The provided object from which the network object would be fetched.
* @return The network trace object associated with the provided object.
*/
+ (nullable FPRNetworkTrace *)networkTraceFromObject:(nonnull id)object;
/**
* Remove the network trace associated with the provided object. If the network trace is not
* associated with the object, does nothing. This uses ObjC runtime to remove the object.
*
* @param object The provided object from which the network object would be removed.
*/
+ (void)removeNetworkTraceFromObject:(nonnull id)object;
/**
* Creates an instance of the FPRNetworkTrace with the provided URL and the HTTP method.
*
* @param URLRequest NSURLRequest object.
* @return An instance of FPRNetworkTrace.
*/
- (nullable instancetype)initWithURLRequest:(nonnull NSURLRequest *)URLRequest
NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)init NS_UNAVAILABLE;
/**
* Records the beginning of the network request. This is usually called just before initiating the
* request.
*/
- (void)start;
/**
* Checkpoints a particular state of the network request. Checkpoint states are listed in the enum
* FPRNetworkTraceCheckpointState mentioned above.
*
* @param state A state as mentioned in enum FPRNetworkTraceCheckpointState.
*/
- (void)checkpointState:(FPRNetworkTraceCheckpointState)state;
/**
* Provides the time difference between the provided checkpoint states in seconds. If the starting
* checkpoint state is greater than the ending checkpoint state, the return value will be negative.
* If either of the states does not exist, returns 0.
*
* @param startState The starting checkpoint state.
* @param endState The ending checkpoint state.
* @return Difference between the ending checkpoint state and starting checkpoint state in seconds.
*/
- (NSTimeInterval)timeIntervalBetweenCheckpointState:(FPRNetworkTraceCheckpointState)startState
andState:(FPRNetworkTraceCheckpointState)endState;
/**
* Checks if the network trace is valid.
*
* @return true if the network trace is valid.
*/
- (BOOL)isValid;
@end

View File

@ -0,0 +1,465 @@
// 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/Instrumentation/FPRNetworkTrace.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace+Private.h"
#import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
#import "FirebasePerformance/Sources/Common/FPRConstants.h"
#import "FirebasePerformance/Sources/Common/FPRDiagnostics.h"
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
#import "FirebasePerformance/Sources/FPRClient.h"
#import "FirebasePerformance/Sources/FPRConsoleLogger.h"
#import "FirebasePerformance/Sources/FPRDataUtils.h"
#import "FirebasePerformance/Sources/FPRURLFilter.h"
#import "FirebasePerformance/Sources/Gauges/FPRGaugeManager.h"
#import "FirebasePerformance/Sources/ISASwizzler/FPRObjectSwizzler.h"
NSString *const kFPRNetworkTracePropertyName = @"fpr_networkTrace";
@interface FPRNetworkTrace ()
@property(nonatomic, readwrite) NSURLRequest *URLRequest;
@property(nonatomic, readwrite, nullable) NSError *responseError;
/** State to know if the trace has started. */
@property(nonatomic) BOOL traceStarted;
/** State to know if the trace has completed. */
@property(nonatomic) BOOL traceCompleted;
/** Background activity tracker to know the background state of the trace. */
@property(nonatomic) FPRTraceBackgroundActivityTracker *backgroundActivityTracker;
/** Custom attribute managed internally. */
@property(nonatomic) NSMutableDictionary<NSString *, NSString *> *customAttributes;
/** @brief Serial queue to manage the updation of session Ids. */
@property(nonatomic, readwrite) dispatch_queue_t sessionIdSerialQueue;
/**
* Updates the current trace with the current session details.
* @param sessionDetails Updated session details of the currently active session.
*/
- (void)updateTraceWithCurrentSession:(FPRSessionDetails *)sessionDetails;
@end
@implementation FPRNetworkTrace {
/**
* @brief Object containing different states of the network request. Stores the information about
* the state of a network request (defined in FPRNetworkTraceCheckpointState) and the time at
* which the event happened.
*/
NSMutableDictionary<NSString *, NSNumber *> *_states;
}
- (nullable instancetype)initWithURLRequest:(NSURLRequest *)URLRequest {
if (URLRequest.URL == nil) {
FPRLogError(kFPRNetworkTraceInvalidInputs, @"Invalid URL. URL is nil.");
return nil;
}
// Fail early instead of creating a trace here.
// IMPORTANT: Order is important here. This check needs to be done before looking up on remote
// config. Reference bug: b/141861005.
if (![[FPRURLFilter sharedInstance] shouldInstrumentURL:URLRequest.URL.absoluteString]) {
return nil;
}
BOOL tracingEnabled = [FPRConfigurations sharedInstance].isDataCollectionEnabled;
if (!tracingEnabled) {
FPRLogInfo(kFPRTraceDisabled, @"Trace feature is disabled.");
return nil;
}
BOOL sdkEnabled = [[FPRConfigurations sharedInstance] sdkEnabled];
if (!sdkEnabled) {
FPRLogInfo(kFPRTraceDisabled, @"Dropping event since Performance SDK is disabled.");
return nil;
}
NSString *trimmedURLString = [FPRNetworkTrace stringByTrimmingURLString:URLRequest];
if (!trimmedURLString || trimmedURLString.length <= 0) {
FPRLogWarning(kFPRNetworkTraceURLLengthExceeds, @"URL length outside limits, returning nil.");
return nil;
}
if (![URLRequest.URL.absoluteString isEqualToString:trimmedURLString]) {
FPRLogInfo(kFPRNetworkTraceURLLengthTruncation,
@"URL length exceeds limits, truncating recorded URL - %@.", trimmedURLString);
}
self = [super init];
if (self) {
_URLRequest = URLRequest;
_trimmedURLString = trimmedURLString;
_states = [[NSMutableDictionary<NSString *, NSNumber *> alloc] init];
_hasValidResponseCode = NO;
_customAttributes = [[NSMutableDictionary<NSString *, NSString *> alloc] init];
_syncQueue =
dispatch_queue_create("com.google.perf.networkTrace.metric", DISPATCH_QUEUE_SERIAL);
_sessionIdSerialQueue =
dispatch_queue_create("com.google.perf.sessionIds.networkTrace", DISPATCH_QUEUE_SERIAL);
_activeSessions = [[NSMutableArray<FPRSessionDetails *> alloc] init];
if (![FPRNetworkTrace isCompleteAndValidTrimmedURLString:_trimmedURLString
URLRequest:_URLRequest]) {
return nil;
};
}
return self;
}
- (instancetype)init {
FPRAssert(NO, @"Not a designated initializer.");
return nil;
}
- (void)dealloc {
// Safety net to ensure the notifications are not received anymore.
FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
[sessionManager.sessionNotificationCenter removeObserver:self
name:kFPRSessionIdUpdatedNotification
object:sessionManager];
}
- (NSString *)description {
return [NSString stringWithFormat:@"Request: %@", _URLRequest];
}
- (void)sessionChanged:(NSNotification *)notification {
if (self.traceStarted && !self.traceCompleted) {
NSDictionary<NSString *, FPRSessionDetails *> *userInfo = notification.userInfo;
FPRSessionDetails *sessionDetails = [userInfo valueForKey:kFPRSessionIdNotificationKey];
if (sessionDetails) {
[self updateTraceWithCurrentSession:sessionDetails];
}
}
}
- (void)updateTraceWithCurrentSession:(FPRSessionDetails *)sessionDetails {
if (sessionDetails != nil) {
dispatch_sync(self.sessionIdSerialQueue, ^{
[self.activeSessions addObject:sessionDetails];
});
}
}
- (NSArray<FPRSessionDetails *> *)sessions {
__block NSArray<FPRSessionDetails *> *sessionInfos = nil;
dispatch_sync(self.sessionIdSerialQueue, ^{
sessionInfos = [self.activeSessions copy];
});
return sessionInfos;
}
- (NSDictionary<NSString *, NSNumber *> *)checkpointStates {
__block NSDictionary<NSString *, NSNumber *> *copiedStates;
dispatch_sync(self.syncQueue, ^{
copiedStates = [_states copy];
});
return copiedStates;
}
- (void)checkpointState:(FPRNetworkTraceCheckpointState)state {
if (!self.traceCompleted && self.traceStarted) {
NSString *stateKey = @(state).stringValue;
if (stateKey) {
dispatch_sync(self.syncQueue, ^{
NSNumber *existingState = _states[stateKey];
if (existingState == nil) {
double intervalSinceEpoch = [[NSDate date] timeIntervalSince1970];
[_states setObject:@(intervalSinceEpoch) forKey:stateKey];
}
});
} else {
FPRAssert(NO, @"stateKey wasn't created for checkpoint state %ld", (long)state);
}
}
}
- (void)start {
if (!self.traceCompleted) {
[[FPRSessionManager sharedInstance] collectAllGaugesOnce];
self.traceStarted = YES;
self.backgroundActivityTracker = [[FPRTraceBackgroundActivityTracker alloc] init];
[self checkpointState:FPRNetworkTraceCheckpointStateInitiated];
if ([self.URLRequest.HTTPMethod isEqualToString:@"POST"] ||
[self.URLRequest.HTTPMethod isEqualToString:@"PUT"]) {
self.requestSize = self.URLRequest.HTTPBody.length;
}
FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
[self updateTraceWithCurrentSession:[sessionManager.sessionDetails copy]];
[sessionManager.sessionNotificationCenter addObserver:self
selector:@selector(sessionChanged:)
name:kFPRSessionIdUpdatedNotification
object:sessionManager];
}
}
- (FPRTraceState)backgroundTraceState {
FPRTraceBackgroundActivityTracker *backgroundActivityTracker = self.backgroundActivityTracker;
if (backgroundActivityTracker) {
return backgroundActivityTracker.traceBackgroundState;
}
return FPRTraceStateUnknown;
}
- (NSTimeInterval)startTimeSinceEpoch {
NSString *stateKey =
[NSString stringWithFormat:@"%lu", (unsigned long)FPRNetworkTraceCheckpointStateInitiated];
__block NSTimeInterval timeSinceEpoch;
dispatch_sync(self.syncQueue, ^{
timeSinceEpoch = [[_states objectForKey:stateKey] doubleValue];
});
return timeSinceEpoch;
}
#pragma mark - Overrides
- (void)setResponseCode:(int32_t)responseCode {
_responseCode = responseCode;
if (responseCode != 0) {
_hasValidResponseCode = YES;
}
}
#pragma mark - FPRNetworkResponseHandler methods
- (void)didCompleteRequestWithResponse:(NSURLResponse *)response error:(NSError *)error {
if (!self.traceCompleted && self.traceStarted) {
// Extract needed fields for the trace object.
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response;
self.responseCode = (int32_t)HTTPResponse.statusCode;
}
self.responseError = error;
self.responseContentType = response.MIMEType;
[self checkpointState:FPRNetworkTraceCheckpointStateResponseCompleted];
// Send the network trace for logging.
[[FPRSessionManager sharedInstance] collectAllGaugesOnce];
[[FPRClient sharedInstance] logNetworkTrace:self];
self.traceCompleted = YES;
}
FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
[sessionManager.sessionNotificationCenter removeObserver:self
name:kFPRSessionIdUpdatedNotification
object:sessionManager];
}
- (void)didUploadFileWithURL:(NSURL *)URL {
NSNumber *value = nil;
NSError *error = nil;
if ([URL getResourceValue:&value forKey:NSURLFileSizeKey error:&error]) {
if (error) {
FPRLogNotice(kFPRNetworkTraceFileError, @"Unable to determine the size of file.");
} else {
self.requestSize = value.unsignedIntegerValue;
}
}
}
- (void)didReceiveData:(NSData *)data {
self.responseSize = data.length;
}
- (void)didReceiveFileURL:(NSURL *)URL {
NSNumber *value = nil;
NSError *error = nil;
if ([URL getResourceValue:&value forKey:NSURLFileSizeKey error:&error]) {
if (error) {
FPRLogNotice(kFPRNetworkTraceFileError, @"Unable to determine the size of file.");
} else {
self.responseSize = value.unsignedIntegerValue;
}
}
}
- (NSTimeInterval)timeIntervalBetweenCheckpointState:(FPRNetworkTraceCheckpointState)startState
andState:(FPRNetworkTraceCheckpointState)endState {
__block NSNumber *startStateTime;
__block NSNumber *endStateTime;
dispatch_sync(self.syncQueue, ^{
startStateTime = [_states objectForKey:[@(startState) stringValue]];
endStateTime = [_states objectForKey:[@(endState) stringValue]];
});
// Fail fast. If any of the times do not exist, return 0.
if (startStateTime == nil || endStateTime == nil) {
return 0;
}
NSTimeInterval timeDiff = (endStateTime.doubleValue - startStateTime.doubleValue);
return timeDiff;
}
/** Trims and validates the URL string of a given NSURLRequest.
*
* @param URLRequest The NSURLRequest containing the URL string to trim.
* @return The trimmed string.
*/
+ (NSString *)stringByTrimmingURLString:(NSURLRequest *)URLRequest {
NSURLComponents *components = [NSURLComponents componentsWithURL:URLRequest.URL
resolvingAgainstBaseURL:NO];
components.query = nil;
components.fragment = nil;
components.user = nil;
components.password = nil;
NSURL *trimmedURL = [components URL];
NSString *truncatedURLString = FPRTruncatedURLString(trimmedURL.absoluteString);
NSURL *truncatedURL = [NSURL URLWithString:truncatedURLString];
if (!truncatedURL || truncatedURL.host == nil) {
return nil;
}
return truncatedURLString;
}
/** Validates the trace object by checking that it's http or https, and not a denied URL.
*
* @param trimmedURLString A trimmed URL string from the URLRequest.
* @param URLRequest The NSURLRequest that this trace will operate on.
* @return YES if the trace object is valid, NO otherwise.
*/
+ (BOOL)isCompleteAndValidTrimmedURLString:(NSString *)trimmedURLString
URLRequest:(NSURLRequest *)URLRequest {
if (![[FPRURLFilter sharedInstance] shouldInstrumentURL:trimmedURLString]) {
return NO;
}
// Check the URL begins with http or https.
NSURLComponents *components = [NSURLComponents componentsWithURL:URLRequest.URL
resolvingAgainstBaseURL:NO];
NSString *scheme = components.scheme;
if (!scheme || !([scheme caseInsensitiveCompare:@"HTTP"] == NSOrderedSame ||
[scheme caseInsensitiveCompare:@"HTTPS"] == NSOrderedSame)) {
FPRLogError(kFPRNetworkTraceInvalidInputs, @"Invalid URL - %@, returning nil.", URLRequest.URL);
return NO;
}
return YES;
}
#pragma mark - Custom attributes related methods
- (NSDictionary<NSString *, NSString *> *)attributes {
return [self.customAttributes copy];
}
- (void)setValue:(NSString *)value forAttribute:(nonnull NSString *)attribute {
BOOL canAddAttribute = YES;
if (self.traceCompleted) {
FPRLogError(kFPRTraceAlreadyStopped,
@"Failed to set attribute %@ because network request %@ has already stopped.",
attribute, self.URLRequest.URL);
canAddAttribute = NO;
}
NSString *validatedName = FPRReservableAttributeName(attribute);
NSString *validatedValue = FPRValidatedAttributeValue(value);
if (validatedName == nil) {
FPRLogError(kFPRAttributeNoName,
@"Failed to initialize because of a nil or zero length attribute name.");
canAddAttribute = NO;
}
if (validatedValue == nil) {
FPRLogError(kFPRAttributeNoValue,
@"Failed to initialize because of a nil or zero length attribute value.");
canAddAttribute = NO;
}
if (self.customAttributes.allKeys.count >= kFPRMaxGlobalCustomAttributesCount) {
FPRLogError(kFPRMaxAttributesReached,
@"Only %d attributes allowed. Already reached maximum attribute count.",
kFPRMaxGlobalCustomAttributesCount);
canAddAttribute = NO;
}
if (canAddAttribute) {
// Ensure concurrency during update of attributes.
dispatch_sync(self.syncQueue, ^{
self.customAttributes[validatedName] = validatedValue;
FPRLogDebug(kFPRClientMetricLogged, @"Setting attribute %@ to %@ on network request %@",
validatedName, validatedValue, self.URLRequest.URL);
});
}
}
- (NSString *)valueForAttribute:(NSString *)attribute {
// TODO(b/175053654): Should this be happening on the serial queue for thread safety?
return self.customAttributes[attribute];
}
- (void)removeAttribute:(NSString *)attribute {
if (self.traceCompleted) {
FPRLogError(kFPRTraceAlreadyStopped,
@"Failed to remove attribute %@ because network request %@ has already stopped.",
attribute, self.URLRequest.URL);
return;
}
[self.customAttributes removeObjectForKey:attribute];
}
#pragma mark - Class methods related to object association.
+ (void)addNetworkTrace:(FPRNetworkTrace *)networkTrace toObject:(id)object {
if (object != nil && networkTrace != nil) {
[FPRObjectSwizzler
setAssociatedObject:object
key:(__bridge const void *_Nonnull)kFPRNetworkTracePropertyName
value:networkTrace
association:GUL_ASSOCIATION_RETAIN_NONATOMIC];
}
}
+ (FPRNetworkTrace *)networkTraceFromObject:(id)object {
FPRNetworkTrace *networkTrace = nil;
if (object != nil) {
id traceObject = [FPRObjectSwizzler
getAssociatedObject:object
key:(__bridge const void *_Nonnull)kFPRNetworkTracePropertyName];
if ([traceObject isKindOfClass:[FPRNetworkTrace class]]) {
networkTrace = (FPRNetworkTrace *)traceObject;
}
}
return networkTrace;
}
+ (void)removeNetworkTraceFromObject:(id)object {
if (object != nil) {
[FPRObjectSwizzler
setAssociatedObject:object
key:(__bridge const void *_Nonnull)kFPRNetworkTracePropertyName
value:nil
association:GUL_ASSOCIATION_RETAIN_NONATOMIC];
}
}
- (BOOL)isValid {
return _hasValidResponseCode;
}
@end

View File

@ -0,0 +1,75 @@
// 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/Instrumentation/FPRInstrument.h"
#import "FirebasePerformance/Sources/ISASwizzler/FPRSwizzledObject.h"
@class FPRSelectorInstrumentor;
NS_ASSUME_NONNULL_BEGIN
/** Defines the interface that an instrumentor should implement if they are going to instrument
* objects.
*/
@protocol FPRObjectInstrumentorProtocol <NSObject>
@required
/** Registers an instance of the delegate class to be instrumented.
*
* @param object The instance to instrument.
*/
- (void)registerObject:(id)object;
@end
/** This class allows the instrumentation of specific objects by isa swizzling specific instances
* with a dynamically generated subclass of the object's original class and installing methods
* onto this new class.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRObjectInstrumentor : FPRInstrument
/** The instrumented object. */
@property(nonatomic, weak) id instrumentedObject;
/** YES if there is reason to swizzle, NO if swizzling is not needed. */
@property(nonatomic) BOOL hasModifications;
/** Please use the designated initializer. */
- (instancetype)init NS_UNAVAILABLE;
/** Instantiates an instance of this class. The designated initializer.
*
* @param object The object to be instrumented.
* @return An instance of this class.
*/
- (instancetype)initWithObject:(id)object NS_DESIGNATED_INITIALIZER;
/** Attempts to copy a selector from a donor class onto the dynamically generated subclass that the
* object will adopt when -swizzle is called.
*
* @param selector The selector to use.
* @param aClass The class to copy the selector from.
* @param isClassSelector YES if the selector is a class selector, NO otherwise.
*/
- (void)copySelector:(SEL)selector fromClass:(Class)aClass isClassSelector:(BOOL)isClassSelector;
/** Swizzles the isa of the object and sets its class to the dynamically created subclass. */
- (void)swizzle;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,59 @@
// 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/Instrumentation/FPRObjectInstrumentor.h"
#import "FirebasePerformance/Sources/Common/FPRDiagnostics.h"
#import "FirebasePerformance/Sources/ISASwizzler/FPRObjectSwizzler.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRInstrument_Private.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRSelectorInstrumentor.h"
@interface FPRObjectInstrumentor () {
// The object swizzler instance this instrumentor will use.
FPRObjectSwizzler *_objectSwizzler;
}
@end
@implementation FPRObjectInstrumentor
- (instancetype)init {
FPRAssert(NO, @"%@: Please use the designated initializer.", NSStringFromClass([self class]));
return nil;
}
- (instancetype)initWithObject:(id)object {
self = [super init];
if (self) {
_objectSwizzler = [[FPRObjectSwizzler alloc] initWithObject:object];
_instrumentedObject = object;
}
return self;
}
- (void)copySelector:(SEL)selector fromClass:(Class)aClass isClassSelector:(BOOL)isClassSelector {
__strong id instrumentedObject = _instrumentedObject;
if (instrumentedObject && ![instrumentedObject respondsToSelector:selector]) {
_hasModifications = YES;
[_objectSwizzler copySelector:selector fromClass:aClass isClassSelector:isClassSelector];
}
}
- (void)swizzle {
if (_hasModifications) {
[_objectSwizzler swizzle];
}
}
@end

View File

@ -0,0 +1,34 @@
// 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 <Foundation/Foundation.h>
/** This class helps the instrumentation deal with objects that have been wrapped with NSProxy
* objects after being swizzled by other SDKs. In particular, Crittercism swizzles NSURLSessions
* and makes every NSURLSession initialization method return an NSProxy subclass.
*/
@interface FPRProxyObjectHelper : NSObject
/** Registers a proxy object for a given class and runs the onSuccess block whenever an ivar of the
* given class is discovered on the proxy object.
*
* @param proxy The proxy object whose ivars will be iterated.
* @param superclass The superclass all ivars will be compared against. See varFoundHandler.
* @param varFoundHandler The block to run when an ivar isKindOfClass:aClass.
*/
+ (void)registerProxyObject:(id)proxy
forSuperclass:(Class)superclass
varFoundHandler:(void (^)(id ivar))varFoundHandler;
@end

View File

@ -0,0 +1,32 @@
// 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/Instrumentation/FPRProxyObjectHelper.h"
#import <GoogleUtilities/GULSwizzler.h>
@implementation FPRProxyObjectHelper
+ (void)registerProxyObject:(id)proxy
forSuperclass:(Class)superclass
varFoundHandler:(void (^)(id ivar))varFoundHandler {
NSArray<id> *ivars = [GULSwizzler ivarObjectsForObject:proxy];
for (id ivar in ivars) {
if ([ivar isKindOfClass:superclass]) {
varFoundHandler(ivar);
}
}
}
@end

View File

@ -0,0 +1,65 @@
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** This class is used to manage the swizzling of selectors on classes. An instance of this class
* should be created for every selector that is being swizzled.
*/
@interface FPRSelectorInstrumentor : NSObject
/** The swizzled selector. */
@property(nonatomic, readonly) SEL selector;
/** Please use designated initializer. */
- (instancetype)init NS_UNAVAILABLE;
/** Initializes an instance of this class. The designated initializer.
*
* @note Capture the current IMP outside the replacing block which will be the originalIMP once we
* swizzle.
*
* @param selector The selector pointer.
* @param aClass The class to operate on.
* @param isClassSelector YES specifies that the selector is a class selector.
* @return An instance of this class.
*/
- (instancetype)initWithSelector:(SEL)selector
class:(Class)aClass
isClassSelector:(BOOL)isClassSelector NS_DESIGNATED_INITIALIZER;
/** Sets the instrumentor's replacing block. To be used in conjunction with initWithSelector:.
*
* @param block The block to replace the original implementation with. Make sure to call
* originalImp in your replacing block.
*/
- (void)setReplacingBlock:(id)block;
/** The current IMP of the swizzled selector.
*
* @return The current IMP for the class, SEL of the FPRSelectorInstrumentor.
*/
- (IMP)currentIMP;
/** Swizzles the selector. */
- (void)swizzle;
/** Causes the original implementation to be run. */
- (void)unswizzle;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,107 @@
// 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/Instrumentation/FPRSelectorInstrumentor.h"
#import "FirebasePerformance/Sources/Common/FPRDiagnostics.h"
#import <GoogleUtilities/GULSwizzler.h>
#ifdef UNSWIZZLE_AVAILABLE
#import <GoogleUtilities/GULSwizzler+Unswizzle.h>
#endif
@implementation FPRSelectorInstrumentor {
// The class this instrumentor operates on.
Class _class;
// The selector this instrumentor operates on.
SEL _selector;
// YES indicates the selector swizzled is a class selector, as opposed to an instance selector.
BOOL _isClassSelector;
// YES indicates that this selector instrumentor has been swizzled.
BOOL _swizzled;
// A block to replace the original implementation. Can't be used with before/after blocks.
id _replacingBlock;
}
- (instancetype)init {
FPRAssert(NO, @"%@: Please use the designated initializer", NSStringFromClass([self class]));
return nil;
}
- (instancetype)initWithSelector:(SEL)selector
class:(Class)aClass
isClassSelector:(BOOL)isClassSelector {
if (![GULSwizzler selector:selector existsInClass:aClass isClassSelector:isClassSelector]) {
return nil;
}
self = [super init];
if (self) {
_selector = selector;
_class = aClass;
_isClassSelector = isClassSelector;
FPRAssert(_selector, @"A selector to swizzle must be provided.");
FPRAssert(_class, @"You can't swizzle a class that doesn't exist");
}
return self;
}
- (void)setReplacingBlock:(id)block {
_replacingBlock = [block copy];
}
- (void)swizzle {
_swizzled = YES;
FPRAssert(_replacingBlock, @"A replacingBlock needs to be set.");
[GULSwizzler swizzleClass:_class
selector:_selector
isClassSelector:_isClassSelector
withBlock:_replacingBlock];
}
- (void)unswizzle {
_swizzled = NO;
#ifdef UNSWIZZLE_AVAILABLE
[GULSwizzler unswizzleClass:_class selector:_selector isClassSelector:_isClassSelector];
#else
NSAssert(NO, @"Unswizzling is disabled.");
#endif
}
- (IMP)currentIMP {
return [GULSwizzler currentImplementationForClass:_class
selector:_selector
isClassSelector:_isClassSelector];
}
- (BOOL)isEqual:(id)object {
if ([object isKindOfClass:[FPRSelectorInstrumentor class]]) {
FPRSelectorInstrumentor *otherObject = object;
return otherObject->_class == _class && otherObject->_selector == _selector &&
otherObject->_isClassSelector == _isClassSelector && otherObject->_swizzled == _swizzled;
}
return NO;
}
- (NSUInteger)hash {
return [[NSString stringWithFormat:@"%@%@%d%d", NSStringFromClass(_class),
NSStringFromSelector(_selector), _isClassSelector, _swizzled]
hash];
}
@end

View File

@ -0,0 +1,25 @@
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRNSURLConnectionDelegate : NSObject <NSURLConnectionDelegate,
NSURLConnectionDataDelegate,
NSURLConnectionDownloadDelegate>
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,82 @@
// 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/Instrumentation/Network/Delegates/FPRNSURLConnectionDelegate.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.h"
@implementation FPRNSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace didCompleteRequestWithResponse:nil error:error];
[FPRNetworkTrace removeNetworkTraceFromObject:connection];
}
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(nonnull NSURLRequest *)request
redirectResponse:(nullable NSURLResponse *)response {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
return request;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
trace.responseCode = (int32_t)((NSHTTPURLResponse *)response).statusCode;
}
[trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
trace.responseSize += data.length;
}
- (void)connection:(NSURLConnection *)connection
didSendBodyData:(NSInteger)bytesWritten
totalBytesWritten:(NSInteger)totalBytesWritten
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
trace.requestSize = totalBytesWritten;
if (totalBytesWritten >= totalBytesExpectedToWrite) {
[trace checkpointState:FPRNetworkTraceCheckpointStateRequestCompleted];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace didCompleteRequestWithResponse:nil error:nil];
[FPRNetworkTrace removeNetworkTraceFromObject:connection];
}
- (void)connection:(NSURLConnection *)connection
didWriteData:(long long)bytesWritten
totalBytesWritten:(long long)totalBytesWritten
expectedTotalBytes:(long long)expectedTotalBytes {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
trace.requestSize = totalBytesWritten;
}
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection
destinationURL:(NSURL *)destinationURL {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace didReceiveFileURL:destinationURL];
[trace didCompleteRequestWithResponse:nil error:nil];
[FPRNetworkTrace removeNetworkTraceFromObject:connection];
}
@end

View File

@ -0,0 +1,36 @@
// 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/Instrumentation/FPRInstrument.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRObjectInstrumentor.h"
NS_ASSUME_NONNULL_BEGIN
/** This class instruments the delegate methods needed to start/stop trace correctly. This class is
* not intended to be used standalone--it should only be used by FPRNSURLConnectionInstrument.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRNSURLConnectionDelegateInstrument : FPRInstrument <FPRObjectInstrumentorProtocol>
/** Registers an instrument for a delegate class if it hasn't yet been instrumented.
*
* @note This method is thread-safe.
* @param aClass The class to instrument.
*/
- (void)registerClass:(Class)aClass;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,307 @@
// 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/Instrumentation/Network/Delegates/FPRNSURLConnectionDelegateInstrument.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRClassInstrumentor.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRInstrument_Private.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRSelectorInstrumentor.h"
#import "FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLConnectionDelegate.h"
#import "FirebasePerformance/Sources/Instrumentation/Network/FPRNetworkInstrumentHelpers.h"
#pragma mark - NSURLConnectionDelegate methods
/** Returns the dispatch queue for all instrumentation to occur on. */
static dispatch_queue_t GetInstrumentationQueue(void) {
static dispatch_queue_t queue;
static dispatch_once_t token;
dispatch_once(&token, ^{
queue = dispatch_queue_create("com.google.FPRNSURLConnectionDelegateInstrument",
DISPATCH_QUEUE_SERIAL);
});
return queue;
}
/** Instruments connection:didFailWithError:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionDidFailWithError(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connection:didFailWithError:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor
setReplacingBlock:^(id object, NSURLConnection *connection, NSError *error) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace didCompleteRequestWithResponse:nil error:error];
[FPRNetworkTrace removeNetworkTraceFromObject:connection];
typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSError *);
((OriginalImp)currentIMP)(object, selector, connection, error);
}];
}
}
#pragma mark - NSURLConnectionDataDelegate methods
/** Instruments connection:willSendRequest:redirectResponse:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionWillSendRequestRedirectResponse(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connection:willSendRequest:redirectResponse:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor setReplacingBlock:^(id object, NSURLConnection *connection,
NSURLRequest *request, NSURLResponse *response) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
typedef NSURLRequest *(*OriginalImp)(id, SEL, NSURLConnection *, NSURLRequest *,
NSURLResponse *);
return ((OriginalImp)currentIMP)(object, selector, connection, request, response);
}];
}
}
/** Instruments connection:didReceiveResponse:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionDidReceiveResponse(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connection:didReceiveResponse:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor
setReplacingBlock:^(id object, NSURLConnection *connection, NSURLResponse *response) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
trace.responseCode = (int32_t)((NSHTTPURLResponse *)response).statusCode;
}
[trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSURLResponse *);
((OriginalImp)currentIMP)(object, selector, connection, response);
}];
}
}
/** Instruments connection:didReceiveData:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionDidReceiveData(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connection:didReceiveData:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor
setReplacingBlock:^(id object, NSURLConnection *connection, NSData *data) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
trace.responseSize += data.length;
typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSData *);
((OriginalImp)currentIMP)(object, selector, connection, data);
}];
}
}
/** Instruments connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionAllTheTotals(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor
setReplacingBlock:^(id object, NSURLConnection *connection, NSInteger bytesWritten,
NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
trace.requestSize = totalBytesWritten;
if (totalBytesWritten >= totalBytesExpectedToWrite) {
[trace checkpointState:FPRNetworkTraceCheckpointStateRequestCompleted];
}
typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSInteger, NSInteger, NSInteger);
((OriginalImp)currentIMP)(object, selector, connection, bytesWritten, totalBytesWritten,
totalBytesExpectedToWrite);
}];
}
}
/** Instruments connectionDidFinishLoading:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionDidFinishLoading(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connectionDidFinishLoading:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor setReplacingBlock:^(id object, NSURLConnection *connection) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace didCompleteRequestWithResponse:nil error:nil];
[FPRNetworkTrace removeNetworkTraceFromObject:connection];
typedef void (*OriginalImp)(id, SEL, NSURLConnection *);
((OriginalImp)currentIMP)(object, selector, connection);
}];
}
}
/** Instruments connection:didWriteData:totalBytesWritten:expectedTotalBytes:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionDidWriteDataTotalBytesWrittenExpectedTotalBytes(
FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connection:didWriteData:totalBytesWritten:expectedTotalBytes:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor
setReplacingBlock:^(id object, NSURLConnection *connection, long long bytesWritten,
long long totalBytesWritten, long long expectedTotalBytes) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
trace.requestSize = totalBytesWritten;
typedef void (*OriginalImp)(id, SEL, NSURLConnection *, long long, long long, long long);
((OriginalImp)currentIMP)(object, selector, connection, bytesWritten, totalBytesWritten,
expectedTotalBytes);
}];
}
}
/** Instruments connectionDidFinishDownloading:destinationURL:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionDidFinishDownloadingDestinationURL(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connectionDidFinishDownloading:destinationURL:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor
setReplacingBlock:^(id object, NSURLConnection *connection, NSURL *destinationURL) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace didReceiveFileURL:destinationURL];
[trace didCompleteRequestWithResponse:nil error:nil];
[FPRNetworkTrace removeNetworkTraceFromObject:connection];
typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSURL *);
((OriginalImp)currentIMP)(object, selector, connection, destinationURL);
}];
}
}
#pragma mark - Helper functions
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void CopySelector(SEL selector, FPRObjectInstrumentor *instrumentor) {
static Class fromClass = Nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
fromClass = [FPRNSURLConnectionDelegate class];
});
if (![instrumentor.instrumentedObject respondsToSelector:selector]) {
[instrumentor copySelector:selector fromClass:fromClass isClassSelector:NO];
}
}
#pragma mark - FPRNSURLConnectionDelegateInstrument
@implementation FPRNSURLConnectionDelegateInstrument
- (void)registerInstrumentors {
// Do nothing by default. classes will be instrumented on-demand upon discovery.
}
- (void)registerClass:(Class)aClass {
dispatch_sync(GetInstrumentationQueue(), ^{
// If this class has already been instrumented, just return.
FPRClassInstrumentor *instrumentor = [[FPRClassInstrumentor alloc] initWithClass:aClass];
if (![self registerClassInstrumentor:instrumentor]) {
return;
}
InstrumentConnectionDidFailWithError(instrumentor);
InstrumentConnectionWillSendRequestRedirectResponse(instrumentor);
InstrumentConnectionDidReceiveResponse(instrumentor);
InstrumentConnectionDidReceiveData(instrumentor);
InstrumentConnectionAllTheTotals(instrumentor);
InstrumentConnectionDidFinishLoading(instrumentor);
InstrumentConnectionDidWriteDataTotalBytesWrittenExpectedTotalBytes(instrumentor);
InstrumentConnectionDidFinishDownloadingDestinationURL(instrumentor);
[instrumentor swizzle];
});
}
- (void)registerObject:(id)object {
dispatch_sync(GetInstrumentationQueue(), ^{
if ([object respondsToSelector:@selector(gul_class)]) {
return;
}
if (![self isObjectInstrumentable:object]) {
return;
}
FPRObjectInstrumentor *instrumentor = [[FPRObjectInstrumentor alloc] initWithObject:object];
// Register the non-swizzled versions of these methods.
CopySelector(@selector(connection:didFailWithError:), instrumentor);
CopySelector(@selector(connection:willSendRequest:redirectResponse:), instrumentor);
CopySelector(@selector(connection:didReceiveResponse:), instrumentor);
CopySelector(@selector(connection:didReceiveData:), instrumentor);
CopySelector(@selector(connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:),
instrumentor);
if (![object respondsToSelector:@selector(connectionDidFinishDownloading:destinationURL:)]) {
CopySelector(@selector(connectionDidFinishLoading:), instrumentor);
}
CopySelector(@selector(connection:didWriteData:totalBytesWritten:expectedTotalBytes:),
instrumentor);
if (![object respondsToSelector:@selector(connectionDidFinishLoading:)]) {
CopySelector(@selector(connectionDidFinishDownloading:destinationURL:), instrumentor);
}
[instrumentor swizzle];
});
}
@end

View File

@ -0,0 +1,27 @@
// 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 <Foundation/Foundation.h>
/** This class exists as a supplier of implementations for delegates that do not implement all
* methods. While swizzling a delegate, if their class doesn't implement the below methods, these
* implementations will be copied onto the delegate class.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRNSURLSessionDelegate : NSObject <NSURLSessionDelegate,
NSURLSessionDataDelegate,
NSURLSessionTaskDelegate,
NSURLSessionDownloadDelegate>
@end

View File

@ -0,0 +1,84 @@
// 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/Instrumentation/Network/Delegates/FPRNSURLSessionDelegate.h"
#import "FirebasePerformance/Sources/FPRConsoleLogger.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.h"
@implementation FPRNSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
@try {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:task];
[trace didCompleteRequestWithResponse:task.response error:error];
[FPRNetworkTrace removeNetworkTraceFromObject:task];
} @catch (NSException *exception) {
FPRLogWarning(kFPRNetworkTraceNotTrackable, @"Unable to track network request.");
}
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
@try {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:task];
trace.requestSize = totalBytesSent;
if (totalBytesSent >= totalBytesExpectedToSend) {
[trace checkpointState:FPRNetworkTraceCheckpointStateRequestCompleted];
}
} @catch (NSException *exception) {
FPRLogWarning(kFPRNetworkTraceNotTrackable, @"Unable to track network request.");
}
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:dataTask];
[trace didReceiveData:data];
[trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:downloadTask];
[trace didReceiveFileURL:location];
[trace didCompleteRequestWithResponse:downloadTask.response error:downloadTask.error];
[FPRNetworkTrace removeNetworkTraceFromObject:downloadTask];
}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:downloadTask];
[trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
trace.responseSize = totalBytesWritten;
if (totalBytesWritten >= totalBytesExpectedToWrite) {
if ([downloadTask.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)downloadTask.response;
[trace didCompleteRequestWithResponse:response error:downloadTask.error];
[FPRNetworkTrace removeNetworkTraceFromObject:downloadTask];
}
}
}
@end

View File

@ -0,0 +1,36 @@
// 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/Instrumentation/FPRInstrument.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRObjectInstrumentor.h"
NS_ASSUME_NONNULL_BEGIN
/** This class instruments the delegate methods needed to start/stop traces correctly. This class is
* not intended to be used standalone--it should only be used by FPRNSURLSessionInstrument.
*/
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
@interface FPRNSURLSessionDelegateInstrument : FPRInstrument <FPRObjectInstrumentorProtocol>
/** Registers an instrumentor for a delegate class if it hasn't yet been instrumented.
*
* @note This method is thread-safe.
* @param aClass The class to instrument.
*/
- (void)registerClass:(Class)aClass;
@end
NS_ASSUME_NONNULL_END

Some files were not shown because too many files have changed in this diff Show More