FIRTrace.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  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/Public/FirebasePerformance/FIRTrace.h"
  15. #import "FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.h"
  16. #import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
  17. #import "FirebasePerformance/Sources/Common/FPRConstants.h"
  18. #import "FirebasePerformance/Sources/Common/FPRDiagnostics.h"
  19. #import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
  20. #import "FirebasePerformance/Sources/FIRPerformance_Private.h"
  21. #import "FirebasePerformance/Sources/FPRClient.h"
  22. #import "FirebasePerformance/Sources/FPRConsoleLogger.h"
  23. #import "FirebasePerformance/Sources/FPRDataUtils.h"
  24. #import "FirebasePerformance/Sources/Gauges/FPRGaugeManager.h"
  25. #import "FirebasePerformance/Sources/Timer/FIRTrace+Internal.h"
  26. #import "FirebasePerformance/Sources/Timer/FIRTrace+Private.h"
  27. @interface FIRTrace ()
  28. @property(nonatomic, copy, readwrite) NSString *name;
  29. /** Custom attributes managed internally. */
  30. @property(nonatomic) NSMutableDictionary<NSString *, NSString *> *customAttributes;
  31. /** Serial queue to manage mutation of attributes. */
  32. @property(nonatomic, readwrite) dispatch_queue_t customAttributesSerialQueue;
  33. @property(nonatomic, readwrite) NSDate *startTime;
  34. @property(nonatomic, readwrite) NSDate *stopTime;
  35. /** Background activity tracker to know the background state of the trace. */
  36. @property(nonatomic) FPRTraceBackgroundActivityTracker *backgroundActivityTracker;
  37. /** Property that denotes if the trace is a stage. */
  38. @property(nonatomic) BOOL isStage;
  39. /** Stops an active stage that is currently active. */
  40. - (void)stopActiveStage;
  41. /**
  42. * Updates the current trace with the session id.
  43. * @param sessionDetails Updated session details of the currently active session.
  44. */
  45. - (void)updateTraceWithSessionId:(FPRSessionDetails *)sessionDetails;
  46. @end
  47. @implementation FIRTrace
  48. - (instancetype)initWithName:(NSString *)name {
  49. NSString *validatedName = FPRReservableName(name);
  50. FIRTrace *trace = [self initTraceWithName:validatedName];
  51. trace.internal = NO;
  52. return trace;
  53. }
  54. - (instancetype)initInternalTraceWithName:(NSString *)name {
  55. FIRTrace *trace = [self initTraceWithName:name];
  56. trace.internal = YES;
  57. return trace;
  58. }
  59. - (instancetype)initTraceWithName:(NSString *)name {
  60. BOOL tracingEnabled = [FPRConfigurations sharedInstance].isDataCollectionEnabled;
  61. if (!tracingEnabled) {
  62. FPRLogInfo(kFPRTraceDisabled, @"Trace feature is disabled.");
  63. return nil;
  64. }
  65. BOOL sdkEnabled = [[FPRConfigurations sharedInstance] sdkEnabled];
  66. if (!sdkEnabled) {
  67. FPRLogInfo(kFPRTraceDisabled, @"Dropping event since Performance SDK is disabled.");
  68. return nil;
  69. }
  70. FPRAssert(name != nil, @"Name cannot be nil");
  71. FPRAssert(name.length > 0, @"Name cannot be an empty string");
  72. if (name == nil || name.length == 0) {
  73. FPRLogError(kFPRTraceNoName, @"Failed to initialize because of a nil or zero length name.");
  74. return nil;
  75. }
  76. self = [super init];
  77. if (self) {
  78. _name = [name copy];
  79. _stages = [[NSMutableArray<FIRTrace *> alloc] init];
  80. _counterList = [[FPRCounterList alloc] init];
  81. _customAttributes = [[NSMutableDictionary<NSString *, NSString *> alloc] init];
  82. _customAttributesSerialQueue =
  83. dispatch_queue_create("com.google.perf.customAttributes.trace", DISPATCH_QUEUE_SERIAL);
  84. _sessionIdSerialQueue =
  85. dispatch_queue_create("com.google.perf.sessionIds.trace", DISPATCH_QUEUE_SERIAL);
  86. _activeSessions = [[NSMutableArray<FPRSessionDetails *> alloc] init];
  87. _isStage = NO;
  88. _fprClient = [FPRClient sharedInstance];
  89. }
  90. return self;
  91. }
  92. - (instancetype)init {
  93. FPRAssert(NO, @"Not a valid initializer.");
  94. return nil;
  95. }
  96. - (void)dealloc {
  97. // Track the number of traces that have started and not stopped.
  98. if (!self.isStage && [self isTraceStarted] && ![self isTraceStopped]) {
  99. FIRTrace *activeTrace = [FPRAppActivityTracker sharedInstance].activeTrace;
  100. [activeTrace incrementMetric:kFPRAppCounterNameTraceNotStopped byInt:1];
  101. FPRLogError(kFPRTraceStartedNotStopped, @"Trace name %@ started, not stopped", self.name);
  102. }
  103. FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
  104. [sessionManager.sessionNotificationCenter removeObserver:self
  105. name:kFPRSessionIdUpdatedNotification
  106. object:sessionManager];
  107. }
  108. #pragma mark - Public instance methods
  109. - (void)start {
  110. if (![self isTraceStarted]) {
  111. if (!self.isStage) {
  112. [[FPRGaugeManager sharedInstance] collectAllGauges];
  113. }
  114. self.startTime = [NSDate date];
  115. self.backgroundActivityTracker = [[FPRTraceBackgroundActivityTracker alloc] init];
  116. FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
  117. if (!self.isStage) {
  118. [self updateTraceWithSessionId:[sessionManager.sessionDetails copy]];
  119. [sessionManager.sessionNotificationCenter addObserver:self
  120. selector:@selector(sessionChanged:)
  121. name:kFPRSessionIdUpdatedNotification
  122. object:sessionManager];
  123. }
  124. if (!self.isStage) {
  125. NSString *infoString = [NSString stringWithFormat:@"Trace started - %@", self.name];
  126. // Send session id to crashlytics
  127. NSDictionary *crashlyticsTraceBreadcrumb = @{
  128. @"source" : @"FirebasePerformance",
  129. @"info" : infoString,
  130. };
  131. NSError *error;
  132. NSData *crashlyticsSessionJsonBreadcrumb =
  133. [NSJSONSerialization dataWithJSONObject:crashlyticsTraceBreadcrumb options:NSJSONWritingPrettyPrinted error:&error];
  134. if (!crashlyticsSessionJsonBreadcrumb) {
  135. NSLog(@"Got an error: %@", error);
  136. } else {
  137. NSString *jsonString = [[NSString alloc] initWithData:crashlyticsSessionJsonBreadcrumb
  138. encoding:NSUTF8StringEncoding];
  139. // Getting the FirePerf shared instance here. Is there a better way to do that internally?
  140. [[FIRPerformance sharedInstance].crashlytics log:jsonString];
  141. }
  142. }
  143. } else {
  144. FPRLogError(kFPRTraceAlreadyStopped,
  145. @"Failed to start trace %@ because it has already been started and stopped.",
  146. self.name);
  147. }
  148. }
  149. - (void)startWithStartTime:(NSDate *)startTime {
  150. [self start];
  151. if (startTime) {
  152. self.startTime = startTime;
  153. }
  154. }
  155. - (void)stop {
  156. [self stopActiveStage];
  157. if ([self isTraceActive]) {
  158. self.stopTime = [NSDate date];
  159. [self.fprClient logTrace:self];
  160. if (!self.isStage) {
  161. [[FPRGaugeManager sharedInstance] collectAllGauges];
  162. }
  163. } else {
  164. FPRLogError(kFPRTraceNotStarted,
  165. @"Failed to stop the trace %@ because it has not been started.", self.name);
  166. }
  167. FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
  168. [sessionManager.sessionNotificationCenter removeObserver:self
  169. name:kFPRSessionIdUpdatedNotification
  170. object:sessionManager];
  171. if (!self.isStage) {
  172. NSString *infoString = [NSString stringWithFormat:@"Trace complete - %@", self.name];
  173. // Send session id to crashlytics
  174. NSDictionary *crashlyticsTraceBreadcrumb = @{
  175. @"source" : @"FirebasePerformance",
  176. @"info" : infoString,
  177. };
  178. NSError *error;
  179. NSData *crashlyticsSessionJsonBreadcrumb =
  180. [NSJSONSerialization dataWithJSONObject:crashlyticsTraceBreadcrumb options:NSJSONWritingPrettyPrinted error:&error];
  181. if (!crashlyticsSessionJsonBreadcrumb) {
  182. NSLog(@"Got an error: %@", error);
  183. } else {
  184. NSString *jsonString = [[NSString alloc] initWithData:crashlyticsSessionJsonBreadcrumb
  185. encoding:NSUTF8StringEncoding];
  186. // Getting the FirePerf shared instance here. Is there a better way to do that internally?
  187. [[FIRPerformance sharedInstance].crashlytics log:jsonString];
  188. }
  189. }
  190. }
  191. - (void)cancel {
  192. [self stopActiveStage];
  193. if ([self isTraceActive]) {
  194. self.stopTime = [NSDate date];
  195. } else {
  196. FPRLogError(kFPRTraceNotStarted,
  197. @"Failed to stop the trace %@ because it has not been started.", self.name);
  198. }
  199. }
  200. - (NSTimeInterval)totalTraceTimeInterval {
  201. return [self.stopTime timeIntervalSinceDate:self.startTime];
  202. }
  203. - (NSTimeInterval)startTimeSinceEpoch {
  204. return [self.startTime timeIntervalSince1970];
  205. }
  206. - (BOOL)isCompleteAndValid {
  207. // Check if the trace time interval is valid.
  208. if (self.totalTraceTimeInterval <= 0) {
  209. return NO;
  210. }
  211. // Check if the counter list is valid.
  212. if (![self.counterList isValid]) {
  213. return NO;
  214. }
  215. // Check if the stages are valid.
  216. __block BOOL validTrace = YES;
  217. [self.stages enumerateObjectsUsingBlock:^(FIRTrace *stage, NSUInteger idx, BOOL *stop) {
  218. validTrace = [stage isCompleteAndValid];
  219. if (!validTrace) {
  220. *stop = YES;
  221. }
  222. }];
  223. return validTrace;
  224. }
  225. - (FPRTraceState)backgroundTraceState {
  226. FPRTraceBackgroundActivityTracker *backgroundActivityTracker = self.backgroundActivityTracker;
  227. if (backgroundActivityTracker) {
  228. return backgroundActivityTracker.traceBackgroundState;
  229. }
  230. return FPRTraceStateUnknown;
  231. }
  232. - (NSArray<FPRSessionDetails *> *)sessions {
  233. __block NSArray<FPRSessionDetails *> *sessionInfos = nil;
  234. dispatch_sync(self.sessionIdSerialQueue, ^{
  235. sessionInfos = [self.activeSessions copy];
  236. });
  237. return sessionInfos;
  238. }
  239. #pragma mark - Stage related methods
  240. - (void)startStageNamed:(NSString *)stageName startTime:(NSDate *)startTime {
  241. if ([self isTraceActive]) {
  242. [self stopActiveStage];
  243. if (self.isInternal) {
  244. self.activeStage = [[FIRTrace alloc] initInternalTraceWithName:stageName];
  245. [self.activeStage startWithStartTime:startTime];
  246. } else {
  247. NSString *validatedStageName = FPRReservableName(stageName);
  248. if (validatedStageName.length > 0) {
  249. self.activeStage = [[FIRTrace alloc] initWithName:validatedStageName];
  250. [self.activeStage startWithStartTime:startTime];
  251. } else {
  252. FPRLogError(kFPRTraceEmptyName, @"The stage name cannot be empty.");
  253. }
  254. }
  255. self.activeStage.isStage = YES;
  256. // Do not track background activity tracker for stages.
  257. self.activeStage.backgroundActivityTracker = nil;
  258. } else {
  259. FPRLogError(kFPRTraceNotStarted,
  260. @"Failed to create stage %@ because the trace has not been started.", stageName);
  261. }
  262. }
  263. - (void)startStageNamed:(NSString *)stageName {
  264. [self startStageNamed:stageName startTime:nil];
  265. }
  266. - (void)stopActiveStage {
  267. if (self.activeStage) {
  268. [self.activeStage cancel];
  269. [self.stages addObject:self.activeStage];
  270. self.activeStage = nil;
  271. }
  272. }
  273. #pragma mark - Counter related methods
  274. - (NSDictionary *)counters {
  275. return [self.counterList counters];
  276. }
  277. - (NSUInteger)numberOfCounters {
  278. return [self.counterList numberOfCounters];
  279. }
  280. #pragma mark - Metrics related methods
  281. - (int64_t)valueForIntMetric:(nonnull NSString *)metricName {
  282. return [self.counterList valueForIntMetric:metricName];
  283. }
  284. - (void)setIntValue:(int64_t)value forMetric:(nonnull NSString *)metricName {
  285. if ([self isTraceActive]) {
  286. NSString *validatedMetricName = self.isInternal ? metricName : FPRReservableName(metricName);
  287. if (validatedMetricName.length > 0) {
  288. [self.counterList setIntValue:value forMetric:validatedMetricName];
  289. [self.activeStage setIntValue:value forMetric:validatedMetricName];
  290. } else {
  291. FPRLogError(kFPRTraceInvalidName, @"The metric name is invalid.");
  292. }
  293. } else {
  294. FPRLogError(kFPRTraceNotStarted,
  295. @"Failed to set value for metric %@ because trace %@ has not been started.",
  296. metricName, self.name);
  297. }
  298. }
  299. - (void)incrementMetric:(nonnull NSString *)metricName byInt:(int64_t)incrementValue {
  300. if ([self isTraceActive]) {
  301. NSString *validatedMetricName = self.isInternal ? metricName : FPRReservableName(metricName);
  302. if (validatedMetricName.length > 0) {
  303. [self.counterList incrementMetric:validatedMetricName byInt:incrementValue];
  304. [self.activeStage incrementMetric:validatedMetricName byInt:incrementValue];
  305. FPRLogDebug(kFPRClientMetricLogged, @"Incrementing metric %@ to %lld on trace %@",
  306. validatedMetricName, [self valueForIntMetric:metricName], self.name);
  307. } else {
  308. FPRLogError(kFPRTraceInvalidName, @"The metric name is invalid.");
  309. }
  310. } else {
  311. FPRLogError(kFPRTraceNotStarted,
  312. @"Failed to increment the trace metric %@ because trace %@ has not been started.",
  313. metricName, self.name);
  314. }
  315. }
  316. - (void)deleteMetric:(nonnull NSString *)metricName {
  317. if ([self isTraceActive]) {
  318. [self.counterList deleteMetric:metricName];
  319. [self.activeStage deleteMetric:metricName];
  320. }
  321. }
  322. #pragma mark - Custom attributes related methods
  323. - (NSDictionary<NSString *, NSString *> *)attributes {
  324. return [self.customAttributes copy];
  325. }
  326. - (void)setValue:(NSString *)value forAttribute:(nonnull NSString *)attribute {
  327. BOOL canAddAttribute = YES;
  328. if ([self isTraceStopped]) {
  329. FPRLogError(kFPRTraceAlreadyStopped,
  330. @"Failed to set attribute %@ because trace %@ has already stopped.", attribute,
  331. self.name);
  332. canAddAttribute = NO;
  333. }
  334. NSString *validatedName = FPRReservableAttributeName(attribute);
  335. NSString *validatedValue = FPRValidatedAttributeValue(value);
  336. if (validatedName == nil) {
  337. FPRLogError(kFPRAttributeNoName,
  338. @"Failed to initialize because of a nil or zero length attribute name.");
  339. canAddAttribute = NO;
  340. }
  341. if (validatedValue == nil) {
  342. FPRLogError(kFPRAttributeNoValue,
  343. @"Failed to initialize because of a nil or zero length attribute value.");
  344. canAddAttribute = NO;
  345. }
  346. if (self.customAttributes.allKeys.count >= kFPRMaxTraceCustomAttributesCount) {
  347. FPRLogError(kFPRMaxAttributesReached,
  348. @"Only %d attributes allowed. Already reached maximum attribute count.",
  349. kFPRMaxTraceCustomAttributesCount);
  350. canAddAttribute = NO;
  351. }
  352. if (canAddAttribute) {
  353. // Ensure concurrency during update of attributes.
  354. dispatch_sync(self.customAttributesSerialQueue, ^{
  355. self.customAttributes[validatedName] = validatedValue;
  356. });
  357. }
  358. FPRLogDebug(kFPRClientMetricLogged, @"Setting attribute %@ to %@ on trace %@", validatedName,
  359. validatedValue, self.name);
  360. }
  361. - (NSString *)valueForAttribute:(NSString *)attribute {
  362. // TODO(b/175053654): Should this be happening on the serial queue for thread safety?
  363. return self.customAttributes[attribute];
  364. }
  365. - (void)removeAttribute:(NSString *)attribute {
  366. if ([self isTraceStopped]) {
  367. FPRLogError(kFPRTraceNotStarted,
  368. @"Failed to remove attribute %@ because trace %@ has already stopped.", attribute,
  369. self.name);
  370. return;
  371. }
  372. [self.customAttributes removeObjectForKey:attribute];
  373. }
  374. #pragma mark - Utility methods
  375. - (void)sessionChanged:(NSNotification *)notification {
  376. if ([self isTraceActive]) {
  377. NSDictionary<NSString *, FPRSessionDetails *> *userInfo = notification.userInfo;
  378. FPRSessionDetails *sessionDetails = [userInfo valueForKey:kFPRSessionIdNotificationKey];
  379. if (sessionDetails) {
  380. [self updateTraceWithSessionId:sessionDetails];
  381. }
  382. }
  383. }
  384. - (void)updateTraceWithSessionId:(FPRSessionDetails *)sessionDetails {
  385. dispatch_sync(self.sessionIdSerialQueue, ^{
  386. [self.activeSessions addObject:sessionDetails];
  387. });
  388. }
  389. - (BOOL)isTraceStarted {
  390. return self.startTime != nil;
  391. }
  392. - (BOOL)isTraceStopped {
  393. return (self.startTime != nil && self.stopTime != nil);
  394. }
  395. - (BOOL)isTraceActive {
  396. return (self.startTime != nil && self.stopTime == nil);
  397. }
  398. @end