Files
firebase-ios-sdk/FirebasePerformance/Sources/FPRProtoUtils.m
T

396 lines
16 KiB
Objective-C

// 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/FPRProtoUtils.h"
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
#import <CoreTelephony/CTCarrier.h>
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#endif
#import <SystemConfiguration/SystemConfiguration.h>
#import "FirebasePerformance/Sources/Common/FPRConstants.h"
#import "FirebasePerformance/Sources/FIRPerformance+Internal.h"
#import "FirebasePerformance/Sources/FPRDataUtils.h"
#import "FirebasePerformance/Sources/Public/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 GPBStringInt64Dictionary *FPRGetProtoCounterForDictionary(
NSDictionary<NSString *, NSNumber *> *dictionary);
static FPRMSGNetworkRequestMetric_HttpMethod FPRHTTPMethodForString(NSString *methodString);
static FPRMSGNetworkConnectionInfo_NetworkType FPRNetworkConnectionInfoNetworkType(void);
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
static FPRMSGNetworkConnectionInfo_MobileSubtype FPRCellularNetworkType(void);
#endif
NSArray<FPRSessionDetails *> *FPRMakeFirstSessionVerbose(NSArray<FPRSessionDetails *> *sessions);
#pragma mark - Public methods
FPRMSGPerfMetric *FPRGetPerfMetricMessage(NSString *appID) {
FPRMSGPerfMetric *perfMetricMessage = [FPRMSGPerfMetric message];
perfMetricMessage.applicationInfo = FPRGetApplicationInfoMessage();
perfMetricMessage.applicationInfo.googleAppId = appID;
return perfMetricMessage;
}
FPRMSGApplicationInfo *FPRGetApplicationInfoMessage() {
FPRMSGApplicationInfo *appInfoMessage = [FPRMSGApplicationInfo message];
FPRMSGIosApplicationInfo *iosAppInfo = [FPRMSGIosApplicationInfo message];
NSBundle *mainBundle = [NSBundle mainBundle];
iosAppInfo.bundleShortVersion = [mainBundle infoDictionary][@"CFBundleShortVersionString"];
iosAppInfo.sdkVersion = [NSString stringWithUTF8String:kFPRSDKVersion];
iosAppInfo.networkConnectionInfo.networkType = FPRNetworkConnectionInfoNetworkType();
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
CTTelephonyNetworkInfo *networkInfo = FPRNetworkInfo();
CTCarrier *provider = networkInfo.subscriberCellularProvider;
NSString *mccMnc = FPRValidatedMccMnc(provider.mobileCountryCode, provider.mobileNetworkCode);
if (mccMnc) {
iosAppInfo.mccMnc = mccMnc;
}
if (iosAppInfo.networkConnectionInfo.networkType ==
FPRMSGNetworkConnectionInfo_NetworkType_Mobile) {
iosAppInfo.networkConnectionInfo.mobileSubtype = FPRCellularNetworkType();
}
#endif
appInfoMessage.iosAppInfo = iosAppInfo;
appInfoMessage.customAttributes = [[FIRPerformance sharedInstance].attributes mutableCopy];
return appInfoMessage;
}
FPRMSGTraceMetric *FPRGetTraceMetric(FIRTrace *trace) {
if (trace == nil) {
return nil;
}
FPRMSGTraceMetric *traceMetric = [FPRMSGTraceMetric message];
traceMetric.name = trace.name;
// Set if the trace is an internally created trace.
traceMetric.isAuto = trace.isInternal;
// Convert the trace duration from seconds to microseconds.
traceMetric.durationUs = trace.totalTraceTimeInterval * USEC_PER_SEC;
// Convert the start time from seconds to microseconds.
traceMetric.clientStartTimeUs = trace.startTimeSinceEpoch * USEC_PER_SEC;
traceMetric.counters = FPRGetProtoCounterForDictionary(trace.counters);
NSMutableArray<FPRMSGTraceMetric *> *subtraces = [[NSMutableArray alloc] init];
[trace.stages
enumerateObjectsUsingBlock:^(FIRTrace *_Nonnull stage, NSUInteger idx, BOOL *_Nonnull stop) {
[subtraces addObject:FPRGetTraceMetric(stage)];
}];
traceMetric.subtracesArray = subtraces;
traceMetric.customAttributes = [trace.attributes mutableCopy];
// Fillin session details
traceMetric.perfSessionsArray = [[NSMutableArray<FPRMSGPerfSession *> alloc] init];
NSArray<FPRSessionDetails *> *orderedSessions = FPRMakeFirstSessionVerbose(trace.sessions);
[orderedSessions enumerateObjectsUsingBlock:^(FPRSessionDetails *_Nonnull session,
NSUInteger index, BOOL *_Nonnull stop) {
FPRMSGPerfSession *perfSession = [FPRMSGPerfSession message];
perfSession.sessionId = session.sessionId;
perfSession.sessionVerbosityArray = [GPBEnumArray array];
if ((session.options & FPRSessionOptionsEvents) ||
(session.options & FPRSessionOptionsGauges)) {
[perfSession.sessionVerbosityArray addValue:FPRMSGSessionVerbosity_GaugesAndSystemEvents];
}
[traceMetric.perfSessionsArray addObject:perfSession];
}];
return traceMetric;
}
FPRMSGNetworkRequestMetric *FPRGetNetworkRequestMetric(FPRNetworkTrace *trace) {
if (trace == nil) {
return nil;
}
// If there is no valid status code, do not send the event to backend.
if (!trace.hasValidResponseCode) {
return nil;
}
FPRMSGNetworkRequestMetric *networkMetric = [FPRMSGNetworkRequestMetric message];
networkMetric.URL = trace.trimmedURLString;
networkMetric.HTTPMethod = FPRHTTPMethodForString(trace.URLRequest.HTTPMethod);
// Convert the start time from seconds to microseconds.
networkMetric.clientStartTimeUs = trace.startTimeSinceEpoch * USEC_PER_SEC;
networkMetric.requestPayloadBytes = trace.requestSize;
networkMetric.responsePayloadBytes = trace.responseSize;
networkMetric.HTTPResponseCode = trace.responseCode;
networkMetric.responseContentType = trace.responseContentType;
if (trace.responseError) {
networkMetric.networkClientErrorReason =
FPRMSGNetworkRequestMetric_NetworkClientErrorReason_GenericClientError;
}
NSTimeInterval requestTimeUs =
USEC_PER_SEC *
[trace timeIntervalBetweenCheckpointState:FPRNetworkTraceCheckpointStateInitiated
andState:FPRNetworkTraceCheckpointStateRequestCompleted];
if (requestTimeUs > 0) {
networkMetric.timeToRequestCompletedUs = requestTimeUs;
}
NSTimeInterval responseIntiationTimeUs =
USEC_PER_SEC *
[trace timeIntervalBetweenCheckpointState:FPRNetworkTraceCheckpointStateInitiated
andState:FPRNetworkTraceCheckpointStateResponseReceived];
if (responseIntiationTimeUs > 0) {
networkMetric.timeToResponseInitiatedUs = responseIntiationTimeUs;
}
NSTimeInterval responseCompletedUs =
USEC_PER_SEC *
[trace timeIntervalBetweenCheckpointState:FPRNetworkTraceCheckpointStateInitiated
andState:FPRNetworkTraceCheckpointStateResponseCompleted];
if (responseCompletedUs > 0) {
networkMetric.timeToResponseCompletedUs = responseCompletedUs;
}
networkMetric.customAttributes = [trace.attributes mutableCopy];
// Fillin session details
NSArray<FPRSessionDetails *> *orderedSessions = FPRMakeFirstSessionVerbose(trace.sessions);
networkMetric.perfSessionsArray = [[NSMutableArray<FPRMSGPerfSession *> alloc] init];
[orderedSessions enumerateObjectsUsingBlock:^(FPRSessionDetails *_Nonnull session,
NSUInteger index, BOOL *_Nonnull stop) {
FPRMSGPerfSession *perfSession = [FPRMSGPerfSession message];
perfSession.sessionId = session.sessionId;
perfSession.sessionVerbosityArray = [GPBEnumArray array];
if ((session.options & FPRSessionOptionsEvents) ||
(session.options & FPRSessionOptionsGauges)) {
[perfSession.sessionVerbosityArray addValue:FPRMSGSessionVerbosity_GaugesAndSystemEvents];
}
[networkMetric.perfSessionsArray addObject:perfSession];
}];
return networkMetric;
}
FPRMSGGaugeMetric *FPRGetGaugeMetric(NSArray *gaugeData, NSString *sessionId) {
if (gaugeData == nil || gaugeData.count == 0) {
return nil;
}
if (sessionId == nil || sessionId.length == 0) {
return nil;
}
FPRMSGGaugeMetric *gaugeMetric = [FPRMSGGaugeMetric message];
gaugeMetric.sessionId = sessionId;
NSMutableArray<FPRMSGCpuMetricReading *> *cpuReadings =
[[NSMutableArray<FPRMSGCpuMetricReading *> alloc] init];
NSMutableArray<FPRMSGIosMemoryReading *> *memoryReadings =
[[NSMutableArray<FPRMSGIosMemoryReading *> alloc] init];
[gaugeData enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[FPRCPUGaugeData class]]) {
FPRCPUGaugeData *gaugeData = (FPRCPUGaugeData *)obj;
FPRMSGCpuMetricReading *cpuReading = [FPRMSGCpuMetricReading message];
cpuReading.clientTimeUs = gaugeData.collectionTime.timeIntervalSince1970 * USEC_PER_SEC;
cpuReading.systemTimeUs = gaugeData.systemTime;
cpuReading.userTimeUs = gaugeData.userTime;
[cpuReadings addObject:cpuReading];
}
if ([obj isKindOfClass:[FPRMemoryGaugeData class]]) {
FPRMemoryGaugeData *gaugeData = (FPRMemoryGaugeData *)obj;
FPRMSGIosMemoryReading *memoryReading = [FPRMSGIosMemoryReading message];
memoryReading.clientTimeUs = gaugeData.collectionTime.timeIntervalSince1970 * USEC_PER_SEC;
memoryReading.usedAppHeapMemoryKb = (int32_t)BYTES_TO_KB(gaugeData.heapUsed);
memoryReading.freeAppHeapMemoryKb = (int32_t)BYTES_TO_KB(gaugeData.heapAvailable);
[memoryReadings addObject:memoryReading];
}
}];
gaugeMetric.cpuMetricReadingsArray = cpuReadings;
gaugeMetric.iosMemoryReadingsArray = memoryReadings;
return gaugeMetric;
}
FPRMSGApplicationProcessState FPRApplicationProcessState(FPRTraceState state) {
FPRMSGApplicationProcessState processState =
FPRMSGApplicationProcessState_ApplicationProcessStateUnknown;
switch (state) {
case FPRTraceStateForegroundOnly:
processState = FPRMSGApplicationProcessState_Foreground;
break;
case FPRTraceStateBackgroundOnly:
processState = FPRMSGApplicationProcessState_Background;
break;
case FPRTraceStateBackgroundAndForeground:
processState = FPRMSGApplicationProcessState_ForegroundBackground;
break;
default:
break;
}
return processState;
}
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
CTTelephonyNetworkInfo *FPRNetworkInfo() {
static CTTelephonyNetworkInfo *networkInfo;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
networkInfo = [[CTTelephonyNetworkInfo alloc] init];
});
return networkInfo;
}
#endif
#pragma mark - Proto creation utilities
/** Converts a dictionary of <NSString *, NSNumber *> to a GPBStringInt64Dictionary proto object.
* @return Reference to a GPBStringInt64Dictionary object.
*/
static GPBStringInt64Dictionary *FPRGetProtoCounterForDictionary(
NSDictionary<NSString *, NSNumber *> *dictionary) {
GPBStringInt64Dictionary *counterDictionary = [[GPBStringInt64Dictionary alloc] init];
[dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, NSNumber *_Nonnull value,
BOOL *_Nonnull stop) {
[counterDictionary setInt64:[value longLongValue] forKey:key];
}];
return counterDictionary;
}
/** Converts the network method string to a value defined in the enum
* FPRMSGNetworkRequestMetric_HttpMethod.
* @return Enum value of the method string. If there is no mapping value defined for the method
* string FPRMSGNetworkRequestMetric_HttpMethod_HTTPMethodUnknown is returned.
*/
static FPRMSGNetworkRequestMetric_HttpMethod FPRHTTPMethodForString(NSString *methodString) {
static NSDictionary<NSString *, NSNumber *> *HTTPToFPRNetworkTraceMethod;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
HTTPToFPRNetworkTraceMethod = @{
@"GET" : @(FPRMSGNetworkRequestMetric_HttpMethod_Get),
@"POST" : @(FPRMSGNetworkRequestMetric_HttpMethod_Post),
@"PUT" : @(FPRMSGNetworkRequestMetric_HttpMethod_Put),
@"DELETE" : @(FPRMSGNetworkRequestMetric_HttpMethod_Delete),
@"HEAD" : @(FPRMSGNetworkRequestMetric_HttpMethod_Head),
@"PATCH" : @(FPRMSGNetworkRequestMetric_HttpMethod_Patch),
@"OPTIONS" : @(FPRMSGNetworkRequestMetric_HttpMethod_Options),
@"TRACE" : @(FPRMSGNetworkRequestMetric_HttpMethod_Trace),
@"CONNECT" : @(FPRMSGNetworkRequestMetric_HttpMethod_Connect),
};
});
NSNumber *HTTPMethod = HTTPToFPRNetworkTraceMethod[methodString];
if (HTTPMethod == nil) {
return FPRMSGNetworkRequestMetric_HttpMethod_HTTPMethodUnknown;
}
return HTTPMethod.intValue;
}
/** Get the current network connection type in NetworkConnectionInfo_NetworkType format.
* @return Current network connection type.
*/
static FPRMSGNetworkConnectionInfo_NetworkType FPRNetworkConnectionInfoNetworkType() {
FPRMSGNetworkConnectionInfo_NetworkType networkType =
FPRMSGNetworkConnectionInfo_NetworkType_None;
static SCNetworkReachabilityRef reachabilityRef = 0;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
reachabilityRef = SCNetworkReachabilityCreateWithName(kCFAllocatorSystemDefault, "google.com");
});
SCNetworkReachabilityFlags reachabilityFlags = 0;
SCNetworkReachabilityGetFlags(reachabilityRef, &reachabilityFlags);
// Parse the network flags to set the network type.
if (reachabilityFlags & kSCNetworkReachabilityFlagsReachable) {
if (reachabilityFlags & kSCNetworkReachabilityFlagsIsWWAN) {
networkType = FPRMSGNetworkConnectionInfo_NetworkType_Mobile;
} else {
networkType = FPRMSGNetworkConnectionInfo_NetworkType_Wifi;
}
}
return networkType;
}
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
/** Get the current cellular network connection type in NetworkConnectionInfo_MobileSubtype format.
* @return Current cellular network connection type.
*/
static FPRMSGNetworkConnectionInfo_MobileSubtype FPRCellularNetworkType() {
static NSDictionary<NSString *, NSNumber *> *cellularNetworkToMobileSubtype;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
cellularNetworkToMobileSubtype = @{
CTRadioAccessTechnologyGPRS : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Gprs),
CTRadioAccessTechnologyEdge : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Edge),
CTRadioAccessTechnologyWCDMA : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Cdma),
CTRadioAccessTechnologyHSDPA : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Hsdpa),
CTRadioAccessTechnologyHSUPA : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Hsupa),
CTRadioAccessTechnologyCDMA1x : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Cdma),
CTRadioAccessTechnologyCDMAEVDORev0 : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Evdo0),
CTRadioAccessTechnologyCDMAEVDORevA : @(FPRMSGNetworkConnectionInfo_MobileSubtype_EvdoA),
CTRadioAccessTechnologyCDMAEVDORevB : @(FPRMSGNetworkConnectionInfo_MobileSubtype_EvdoB),
CTRadioAccessTechnologyeHRPD : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Ehrpd),
CTRadioAccessTechnologyLTE : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Lte)
};
});
NSString *networkString = FPRNetworkInfo().currentRadioAccessTechnology;
NSNumber *cellularNetworkType = cellularNetworkToMobileSubtype[networkString];
return cellularNetworkType.intValue;
}
#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];
__block NSInteger firstVerboseSessionIndex = -1;
[sessions enumerateObjectsUsingBlock:^(FPRSessionDetails *session, NSUInteger idx, BOOL *stop) {
if ([session isVerbose]) {
firstVerboseSessionIndex = idx;
*stop = YES;
}
}];
if (firstVerboseSessionIndex > 0) {
FPRSessionDetails *verboseSession = orderedSessions[firstVerboseSessionIndex];
[orderedSessions removeObjectAtIndex:firstVerboseSessionIndex];
[orderedSessions insertObject:verboseSession atIndex:0];
}
return [orderedSessions copy];
}