FIRTrace.m 14 KB

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