FIRTrace.m 14 KB

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