FPRProtoUtils.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. // Copyright 2020 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "FirebasePerformance/Sources/FPRProtoUtils.h"
  15. #import <CoreTelephony/CTCarrier.h>
  16. #import <CoreTelephony/CTTelephonyNetworkInfo.h>
  17. #import <SystemConfiguration/SystemConfiguration.h>
  18. #import "FirebasePerformance/Sources/Common/FPRConstants.h"
  19. #import "FirebasePerformance/Sources/FIRPerformance+Internal.h"
  20. #import "FirebasePerformance/Sources/FPRDataUtils.h"
  21. #import "FirebasePerformance/Sources/Public/FIRPerformance.h"
  22. #import "FirebasePerformance/Sources/Timer/FIRTrace+Internal.h"
  23. #import "FirebasePerformance/Sources/Timer/FIRTrace+Private.h"
  24. #import "FirebasePerformance/Sources/Gauges/CPU/FPRCPUGaugeData.h"
  25. #import "FirebasePerformance/Sources/Gauges/Memory/FPRMemoryGaugeData.h"
  26. #define BYTES_TO_KB(x) (x / 1024)
  27. static GPBStringInt64Dictionary *FPRGetProtoCounterForDictionary(
  28. NSDictionary<NSString *, NSNumber *> *dictionary);
  29. static FPRMSGNetworkRequestMetric_HttpMethod FPRHTTPMethodForString(NSString *methodString);
  30. static FPRMSGNetworkConnectionInfo_NetworkType FPRNetworkConnectionInfoNetworkType(void);
  31. static FPRMSGNetworkConnectionInfo_MobileSubtype FPRCellularNetworkType(void);
  32. NSArray<FPRSessionDetails *> *FPRMakeFirstSessionVerbose(NSArray<FPRSessionDetails *> *sessions);
  33. #pragma mark - Public methods
  34. FPRMSGPerfMetric *FPRGetPerfMetricMessage(NSString *appID) {
  35. FPRMSGPerfMetric *perfMetricMessage = [FPRMSGPerfMetric message];
  36. perfMetricMessage.applicationInfo = FPRGetApplicationInfoMessage();
  37. perfMetricMessage.applicationInfo.googleAppId = appID;
  38. return perfMetricMessage;
  39. }
  40. FPRMSGApplicationInfo *FPRGetApplicationInfoMessage() {
  41. FPRMSGApplicationInfo *appInfoMessage = [FPRMSGApplicationInfo message];
  42. FPRMSGIosApplicationInfo *iosAppInfo = [FPRMSGIosApplicationInfo message];
  43. CTTelephonyNetworkInfo *networkInfo = FPRNetworkInfo();
  44. CTCarrier *provider = networkInfo.subscriberCellularProvider;
  45. NSBundle *mainBundle = [NSBundle mainBundle];
  46. iosAppInfo.bundleShortVersion = [mainBundle infoDictionary][@"CFBundleShortVersionString"];
  47. iosAppInfo.sdkVersion = [NSString stringWithUTF8String:kFPRSDKVersion];
  48. NSString *mccMnc = FPRValidatedMccMnc(provider.mobileCountryCode, provider.mobileNetworkCode);
  49. if (mccMnc) {
  50. iosAppInfo.mccMnc = mccMnc;
  51. }
  52. iosAppInfo.networkConnectionInfo.networkType = FPRNetworkConnectionInfoNetworkType();
  53. if (iosAppInfo.networkConnectionInfo.networkType ==
  54. FPRMSGNetworkConnectionInfo_NetworkType_Mobile) {
  55. iosAppInfo.networkConnectionInfo.mobileSubtype = FPRCellularNetworkType();
  56. }
  57. appInfoMessage.iosAppInfo = iosAppInfo;
  58. appInfoMessage.customAttributes = [[FIRPerformance sharedInstance].attributes mutableCopy];
  59. return appInfoMessage;
  60. }
  61. FPRMSGTraceMetric *FPRGetTraceMetric(FIRTrace *trace) {
  62. if (trace == nil) {
  63. return nil;
  64. }
  65. FPRMSGTraceMetric *traceMetric = [FPRMSGTraceMetric message];
  66. traceMetric.name = trace.name;
  67. // Set if the trace is an internally created trace.
  68. traceMetric.isAuto = trace.isInternal;
  69. // Convert the trace duration from seconds to microseconds.
  70. traceMetric.durationUs = trace.totalTraceTimeInterval * USEC_PER_SEC;
  71. // Convert the start time from seconds to microseconds.
  72. traceMetric.clientStartTimeUs = trace.startTimeSinceEpoch * USEC_PER_SEC;
  73. traceMetric.counters = FPRGetProtoCounterForDictionary(trace.counters);
  74. NSMutableArray<FPRMSGTraceMetric *> *subtraces = [[NSMutableArray alloc] init];
  75. [trace.stages
  76. enumerateObjectsUsingBlock:^(FIRTrace *_Nonnull stage, NSUInteger idx, BOOL *_Nonnull stop) {
  77. [subtraces addObject:FPRGetTraceMetric(stage)];
  78. }];
  79. traceMetric.subtracesArray = subtraces;
  80. traceMetric.customAttributes = [trace.attributes mutableCopy];
  81. // Fillin session details
  82. traceMetric.perfSessionsArray = [[NSMutableArray<FPRMSGPerfSession *> alloc] init];
  83. NSArray<FPRSessionDetails *> *orderedSessions = FPRMakeFirstSessionVerbose(trace.sessions);
  84. [orderedSessions enumerateObjectsUsingBlock:^(FPRSessionDetails *_Nonnull session,
  85. NSUInteger index, BOOL *_Nonnull stop) {
  86. FPRMSGPerfSession *perfSession = [FPRMSGPerfSession message];
  87. perfSession.sessionId = session.sessionId;
  88. perfSession.sessionVerbosityArray = [GPBEnumArray array];
  89. if ((session.options & FPRSessionOptionsEvents) ||
  90. (session.options & FPRSessionOptionsGauges)) {
  91. [perfSession.sessionVerbosityArray addValue:FPRMSGSessionVerbosity_GaugesAndSystemEvents];
  92. }
  93. [traceMetric.perfSessionsArray addObject:perfSession];
  94. }];
  95. return traceMetric;
  96. }
  97. FPRMSGNetworkRequestMetric *FPRGetNetworkRequestMetric(FPRNetworkTrace *trace) {
  98. if (trace == nil) {
  99. return nil;
  100. }
  101. // If there is no valid status code, do not send the event to backend.
  102. if (!trace.hasValidResponseCode) {
  103. return nil;
  104. }
  105. FPRMSGNetworkRequestMetric *networkMetric = [FPRMSGNetworkRequestMetric message];
  106. networkMetric.URL = trace.trimmedURLString;
  107. networkMetric.HTTPMethod = FPRHTTPMethodForString(trace.URLRequest.HTTPMethod);
  108. // Convert the start time from seconds to microseconds.
  109. networkMetric.clientStartTimeUs = trace.startTimeSinceEpoch * USEC_PER_SEC;
  110. networkMetric.requestPayloadBytes = trace.requestSize;
  111. networkMetric.responsePayloadBytes = trace.responseSize;
  112. networkMetric.HTTPResponseCode = trace.responseCode;
  113. networkMetric.responseContentType = trace.responseContentType;
  114. if (trace.responseError) {
  115. networkMetric.networkClientErrorReason =
  116. FPRMSGNetworkRequestMetric_NetworkClientErrorReason_GenericClientError;
  117. }
  118. NSTimeInterval requestTimeUs =
  119. USEC_PER_SEC *
  120. [trace timeIntervalBetweenCheckpointState:FPRNetworkTraceCheckpointStateInitiated
  121. andState:FPRNetworkTraceCheckpointStateRequestCompleted];
  122. if (requestTimeUs > 0) {
  123. networkMetric.timeToRequestCompletedUs = requestTimeUs;
  124. }
  125. NSTimeInterval responseIntiationTimeUs =
  126. USEC_PER_SEC *
  127. [trace timeIntervalBetweenCheckpointState:FPRNetworkTraceCheckpointStateInitiated
  128. andState:FPRNetworkTraceCheckpointStateResponseReceived];
  129. if (responseIntiationTimeUs > 0) {
  130. networkMetric.timeToResponseInitiatedUs = responseIntiationTimeUs;
  131. }
  132. NSTimeInterval responseCompletedUs =
  133. USEC_PER_SEC *
  134. [trace timeIntervalBetweenCheckpointState:FPRNetworkTraceCheckpointStateInitiated
  135. andState:FPRNetworkTraceCheckpointStateResponseCompleted];
  136. if (responseCompletedUs > 0) {
  137. networkMetric.timeToResponseCompletedUs = responseCompletedUs;
  138. }
  139. networkMetric.customAttributes = [trace.attributes mutableCopy];
  140. // Fillin session details
  141. NSArray<FPRSessionDetails *> *orderedSessions = FPRMakeFirstSessionVerbose(trace.sessions);
  142. networkMetric.perfSessionsArray = [[NSMutableArray<FPRMSGPerfSession *> alloc] init];
  143. [orderedSessions enumerateObjectsUsingBlock:^(FPRSessionDetails *_Nonnull session,
  144. NSUInteger index, BOOL *_Nonnull stop) {
  145. FPRMSGPerfSession *perfSession = [FPRMSGPerfSession message];
  146. perfSession.sessionId = session.sessionId;
  147. perfSession.sessionVerbosityArray = [GPBEnumArray array];
  148. if ((session.options & FPRSessionOptionsEvents) ||
  149. (session.options & FPRSessionOptionsGauges)) {
  150. [perfSession.sessionVerbosityArray addValue:FPRMSGSessionVerbosity_GaugesAndSystemEvents];
  151. }
  152. [networkMetric.perfSessionsArray addObject:perfSession];
  153. }];
  154. return networkMetric;
  155. }
  156. FPRMSGGaugeMetric *FPRGetGaugeMetric(NSArray *gaugeData, NSString *sessionId) {
  157. if (gaugeData == nil || gaugeData.count == 0) {
  158. return nil;
  159. }
  160. if (sessionId == nil || sessionId.length == 0) {
  161. return nil;
  162. }
  163. FPRMSGGaugeMetric *gaugeMetric = [FPRMSGGaugeMetric message];
  164. gaugeMetric.sessionId = sessionId;
  165. NSMutableArray<FPRMSGCpuMetricReading *> *cpuReadings =
  166. [[NSMutableArray<FPRMSGCpuMetricReading *> alloc] init];
  167. NSMutableArray<FPRMSGIosMemoryReading *> *memoryReadings =
  168. [[NSMutableArray<FPRMSGIosMemoryReading *> alloc] init];
  169. [gaugeData enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  170. if ([obj isKindOfClass:[FPRCPUGaugeData class]]) {
  171. FPRCPUGaugeData *gaugeData = (FPRCPUGaugeData *)obj;
  172. FPRMSGCpuMetricReading *cpuReading = [FPRMSGCpuMetricReading message];
  173. cpuReading.clientTimeUs = gaugeData.collectionTime.timeIntervalSince1970 * USEC_PER_SEC;
  174. cpuReading.systemTimeUs = gaugeData.systemTime;
  175. cpuReading.userTimeUs = gaugeData.userTime;
  176. [cpuReadings addObject:cpuReading];
  177. }
  178. if ([obj isKindOfClass:[FPRMemoryGaugeData class]]) {
  179. FPRMemoryGaugeData *gaugeData = (FPRMemoryGaugeData *)obj;
  180. FPRMSGIosMemoryReading *memoryReading = [FPRMSGIosMemoryReading message];
  181. memoryReading.clientTimeUs = gaugeData.collectionTime.timeIntervalSince1970 * USEC_PER_SEC;
  182. memoryReading.usedAppHeapMemoryKb = (int32_t)BYTES_TO_KB(gaugeData.heapUsed);
  183. memoryReading.freeAppHeapMemoryKb = (int32_t)BYTES_TO_KB(gaugeData.heapAvailable);
  184. [memoryReadings addObject:memoryReading];
  185. }
  186. }];
  187. gaugeMetric.cpuMetricReadingsArray = cpuReadings;
  188. gaugeMetric.iosMemoryReadingsArray = memoryReadings;
  189. return gaugeMetric;
  190. }
  191. FPRMSGApplicationProcessState FPRApplicationProcessState(FPRTraceState state) {
  192. FPRMSGApplicationProcessState processState =
  193. FPRMSGApplicationProcessState_ApplicationProcessStateUnknown;
  194. switch (state) {
  195. case FPRTraceStateForegroundOnly:
  196. processState = FPRMSGApplicationProcessState_Foreground;
  197. break;
  198. case FPRTraceStateBackgroundOnly:
  199. processState = FPRMSGApplicationProcessState_Background;
  200. break;
  201. case FPRTraceStateBackgroundAndForeground:
  202. processState = FPRMSGApplicationProcessState_ForegroundBackground;
  203. break;
  204. default:
  205. break;
  206. }
  207. return processState;
  208. }
  209. CTTelephonyNetworkInfo *FPRNetworkInfo() {
  210. static CTTelephonyNetworkInfo *networkInfo;
  211. static dispatch_once_t onceToken;
  212. dispatch_once(&onceToken, ^{
  213. networkInfo = [[CTTelephonyNetworkInfo alloc] init];
  214. });
  215. return networkInfo;
  216. }
  217. #pragma mark - Proto creation utilities
  218. /** Converts a dictionary of <NSString *, NSNumber *> to a GPBStringInt64Dictionary proto object.
  219. * @return Reference to a GPBStringInt64Dictionary object.
  220. */
  221. static GPBStringInt64Dictionary *FPRGetProtoCounterForDictionary(
  222. NSDictionary<NSString *, NSNumber *> *dictionary) {
  223. GPBStringInt64Dictionary *counterDictionary = [[GPBStringInt64Dictionary alloc] init];
  224. [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, NSNumber *_Nonnull value,
  225. BOOL *_Nonnull stop) {
  226. [counterDictionary setInt64:[value longLongValue] forKey:key];
  227. }];
  228. return counterDictionary;
  229. }
  230. /** Converts the network method string to a value defined in the enum
  231. * FPRMSGNetworkRequestMetric_HttpMethod.
  232. * @return Enum value of the method string. If there is no mapping value defined for the method
  233. * string FPRMSGNetworkRequestMetric_HttpMethod_HTTPMethodUnknown is returned.
  234. */
  235. static FPRMSGNetworkRequestMetric_HttpMethod FPRHTTPMethodForString(NSString *methodString) {
  236. static NSDictionary<NSString *, NSNumber *> *HTTPToFPRNetworkTraceMethod;
  237. static dispatch_once_t onceToken = 0;
  238. dispatch_once(&onceToken, ^{
  239. HTTPToFPRNetworkTraceMethod = @{
  240. @"GET" : @(FPRMSGNetworkRequestMetric_HttpMethod_Get),
  241. @"POST" : @(FPRMSGNetworkRequestMetric_HttpMethod_Post),
  242. @"PUT" : @(FPRMSGNetworkRequestMetric_HttpMethod_Put),
  243. @"DELETE" : @(FPRMSGNetworkRequestMetric_HttpMethod_Delete),
  244. @"HEAD" : @(FPRMSGNetworkRequestMetric_HttpMethod_Head),
  245. @"PATCH" : @(FPRMSGNetworkRequestMetric_HttpMethod_Patch),
  246. @"OPTIONS" : @(FPRMSGNetworkRequestMetric_HttpMethod_Options),
  247. @"TRACE" : @(FPRMSGNetworkRequestMetric_HttpMethod_Trace),
  248. @"CONNECT" : @(FPRMSGNetworkRequestMetric_HttpMethod_Connect),
  249. };
  250. });
  251. NSNumber *HTTPMethod = HTTPToFPRNetworkTraceMethod[methodString];
  252. if (HTTPMethod == nil) {
  253. return FPRMSGNetworkRequestMetric_HttpMethod_HTTPMethodUnknown;
  254. }
  255. return HTTPMethod.intValue;
  256. }
  257. /** Get the current network connection type in NetworkConnectionInfo_NetworkType format.
  258. * @return Current network connection type.
  259. */
  260. static FPRMSGNetworkConnectionInfo_NetworkType FPRNetworkConnectionInfoNetworkType() {
  261. FPRMSGNetworkConnectionInfo_NetworkType networkType =
  262. FPRMSGNetworkConnectionInfo_NetworkType_None;
  263. static SCNetworkReachabilityRef reachabilityRef = 0;
  264. static dispatch_once_t onceToken;
  265. dispatch_once(&onceToken, ^{
  266. reachabilityRef = SCNetworkReachabilityCreateWithName(kCFAllocatorSystemDefault, "google.com");
  267. });
  268. SCNetworkReachabilityFlags reachabilityFlags = 0;
  269. SCNetworkReachabilityGetFlags(reachabilityRef, &reachabilityFlags);
  270. // Parse the network flags to set the network type.
  271. if (reachabilityFlags & kSCNetworkReachabilityFlagsReachable) {
  272. if (reachabilityFlags & kSCNetworkReachabilityFlagsIsWWAN) {
  273. networkType = FPRMSGNetworkConnectionInfo_NetworkType_Mobile;
  274. } else {
  275. networkType = FPRMSGNetworkConnectionInfo_NetworkType_Wifi;
  276. }
  277. }
  278. return networkType;
  279. }
  280. /** Get the current cellular network connection type in NetworkConnectionInfo_MobileSubtype format.
  281. * @return Current cellular network connection type.
  282. */
  283. static FPRMSGNetworkConnectionInfo_MobileSubtype FPRCellularNetworkType() {
  284. static NSDictionary<NSString *, NSNumber *> *cellularNetworkToMobileSubtype;
  285. static dispatch_once_t onceToken = 0;
  286. dispatch_once(&onceToken, ^{
  287. cellularNetworkToMobileSubtype = @{
  288. CTRadioAccessTechnologyGPRS : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Gprs),
  289. CTRadioAccessTechnologyEdge : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Edge),
  290. CTRadioAccessTechnologyWCDMA : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Cdma),
  291. CTRadioAccessTechnologyHSDPA : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Hsdpa),
  292. CTRadioAccessTechnologyHSUPA : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Hsupa),
  293. CTRadioAccessTechnologyCDMA1x : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Cdma),
  294. CTRadioAccessTechnologyCDMAEVDORev0 : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Evdo0),
  295. CTRadioAccessTechnologyCDMAEVDORevA : @(FPRMSGNetworkConnectionInfo_MobileSubtype_EvdoA),
  296. CTRadioAccessTechnologyCDMAEVDORevB : @(FPRMSGNetworkConnectionInfo_MobileSubtype_EvdoB),
  297. CTRadioAccessTechnologyeHRPD : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Ehrpd),
  298. CTRadioAccessTechnologyLTE : @(FPRMSGNetworkConnectionInfo_MobileSubtype_Lte)
  299. };
  300. });
  301. NSString *networkString = FPRNetworkInfo().currentRadioAccessTechnology;
  302. NSNumber *cellularNetworkType = cellularNetworkToMobileSubtype[networkString];
  303. return cellularNetworkType.intValue;
  304. }
  305. /** Reorders the list of sessions to make sure the first session is verbose if at least one session
  306. * in the list is verbose.
  307. * @return Ordered list of sessions.
  308. */
  309. NSArray<FPRSessionDetails *> *FPRMakeFirstSessionVerbose(NSArray<FPRSessionDetails *> *sessions) {
  310. NSMutableArray<FPRSessionDetails *> *orderedSessions =
  311. [[NSMutableArray<FPRSessionDetails *> alloc] initWithArray:sessions];
  312. __block NSInteger firstVerboseSessionIndex = -1;
  313. [sessions enumerateObjectsUsingBlock:^(FPRSessionDetails *session, NSUInteger idx, BOOL *stop) {
  314. if ([session isVerbose]) {
  315. firstVerboseSessionIndex = idx;
  316. *stop = YES;
  317. }
  318. }];
  319. if (firstVerboseSessionIndex > 0) {
  320. FPRSessionDetails *verboseSession = orderedSessions[firstVerboseSessionIndex];
  321. [orderedSessions removeObjectAtIndex:firstVerboseSessionIndex];
  322. [orderedSessions insertObject:verboseSession atIndex:0];
  323. }
  324. return [orderedSessions copy];
  325. }