PerfTraceView.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  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 "PerfTraceView.h"
  15. #import "PerfTraceView+Accessibility.h"
  16. @interface PerfTraceView ()
  17. // Views in the view hierarchy.
  18. /** A label for the trace name. */
  19. @property(nonatomic) UILabel *nameLabel;
  20. /** A button that will stop the currently running trace. */
  21. @property(nonatomic) UIButton *stopTraceButton;
  22. /** A button to start a new stage on the currently running trace. */
  23. @property(nonatomic) UIButton *stageButton;
  24. /** A button to increment a metric on the currently running trace. */
  25. @property(nonatomic) UIButton *metricOneButton;
  26. /** A button to increment a second metric on the currently running trace. */
  27. @property(nonatomic) UIButton *metricTwoButton;
  28. /** A button to add a custom attribute to the currrently running trace. */
  29. @property(nonatomic) UIButton *customAttributeButton;
  30. /** A label for the current stage state. */
  31. @property(nonatomic) UILabel *recentStageLabel;
  32. // State preserving properties.
  33. /** The current trace. */
  34. @property(nonatomic, readwrite, copy) FIRTrace *trace;
  35. /** The current stage number. */
  36. @property(nonatomic, assign) NSInteger stageNumber;
  37. /** The current value of metric one. */
  38. @property(nonatomic, assign) NSInteger metricOneValue;
  39. /** The current value of metric two. */
  40. @property(nonatomic, assign) NSInteger metricTwoValue;
  41. /** The current custom attribute counter used for their names. */
  42. @property(nonatomic, assign) NSInteger customAttributeCounter;
  43. /** The current custom attribute value to be added to the trade. */
  44. @property(nonatomic) NSString *customAttributeValue;
  45. /** The timestamp of when the trace started for UI labeling purposes. */
  46. @property(nonatomic) NSDate *traceDate;
  47. // Helper properties.
  48. @property(nonatomic) NSLayoutConstraint *bottomConstraint;
  49. /**
  50. * Picks a custom attribute value from a constant list of values and sets the property.
  51. * It also auto increments the custom attribute name and updates the button's title.
  52. */
  53. - (void)chooseCustomAttribute;
  54. @end
  55. @implementation PerfTraceView
  56. #pragma mark - Initialization
  57. - (instancetype)initWithFrame:(CGRect)frame {
  58. NSAssert(NO, @"Not a valid initializer.");
  59. return nil;
  60. }
  61. - (instancetype)initWithCoder:(NSCoder *)coder {
  62. NSAssert(NO, @"Not a valid initializer.");
  63. return nil;
  64. }
  65. - (instancetype)initWithTrace:(FIRTrace *)trace frame:(CGRect)frame {
  66. self = [super initWithFrame:frame];
  67. if (self) {
  68. _trace = trace;
  69. _traceDate = [NSDate date];
  70. }
  71. return self;
  72. }
  73. - (void)updateConstraints {
  74. [super updateConstraints];
  75. if (self.constraints.count == 0) {
  76. [self constrainViews];
  77. }
  78. }
  79. - (void)didMoveToSuperview {
  80. [super didMoveToSuperview];
  81. if (self.superview != nil && self.subviews.count == 0) {
  82. [self createViewTree];
  83. [_metricOneButton setTitle:@"metric1 - 0" forState:UIControlStateNormal];
  84. [_metricTwoButton setTitle:@"metric2 - 0" forState:UIControlStateNormal];
  85. _nameLabel.text = _trace.name;
  86. [self chooseCustomAttribute];
  87. }
  88. }
  89. #pragma mark - Control methods
  90. - (void)stopTrace:(UIButton *)button {
  91. self.nameLabel.text = [NSString stringWithFormat:@"%@ - %0.2fs", self.trace.name,
  92. (-1 * [self.traceDate timeIntervalSinceNow])];
  93. [self.delegate perfTraceViewTraceStopped:self];
  94. self.trace = nil;
  95. [self disableActions];
  96. }
  97. /** @brief: Disables all the actionable elements on the view. */
  98. - (void)disableActions {
  99. self.stopTraceButton.enabled = NO;
  100. self.metricOneButton.enabled = NO;
  101. self.metricTwoButton.enabled = NO;
  102. self.stageButton.enabled = NO;
  103. }
  104. /**
  105. * Creates new Stage in the trace and adds it to the visual list of stages created.
  106. *
  107. * @param button Button that initiated the request.
  108. */
  109. - (void)createStage:(UIButton *)button {
  110. NSString *stageName = [NSString stringWithFormat:@"Stage%zd", self.stageNumber];
  111. NSString *stageNameLabelText = [NSString
  112. stringWithFormat:@"%@ - %0.2fs", stageName, (-1 * [self.traceDate timeIntervalSinceNow])];
  113. UILabel *currentLabel = self.recentStageLabel;
  114. self.recentStageLabel = [self newStageLabel];
  115. self.recentStageLabel.text = stageNameLabelText;
  116. [self addSubview:self.recentStageLabel];
  117. // Stage feature is currently disabled.
  118. // [self.trace startStageNamed:stageName];
  119. if (currentLabel) {
  120. [NSLayoutConstraint activateConstraints:@[
  121. [self.recentStageLabel.topAnchor constraintEqualToAnchor:currentLabel.bottomAnchor
  122. constant:10.0f],
  123. [self.recentStageLabel.leftAnchor constraintEqualToAnchor:currentLabel.leftAnchor],
  124. ]];
  125. } else {
  126. [NSLayoutConstraint activateConstraints:@[
  127. [self.recentStageLabel.topAnchor
  128. constraintEqualToAnchor:self.customAttributeButton.bottomAnchor
  129. constant:10.0f],
  130. [self.recentStageLabel.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
  131. ]];
  132. }
  133. if (self.bottomConstraint) {
  134. [self removeConstraint:self.bottomConstraint];
  135. }
  136. self.bottomConstraint =
  137. [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.recentStageLabel.bottomAnchor
  138. constant:5.0f];
  139. [self addConstraint:self.bottomConstraint];
  140. self.stageNumber++;
  141. }
  142. /**
  143. * Action for incrementing metric one.
  144. *
  145. * @param button The button object where the action took place.
  146. */
  147. - (void)incrementMetricOne:(UIButton *)button {
  148. self.metricOneValue++;
  149. [self.trace incrementMetric:@"metric1" byInt:1];
  150. NSString *stringValue = [NSString stringWithFormat:@"metric1 - %zd", self.metricOneValue];
  151. [self.metricOneButton setTitle:stringValue forState:UIControlStateNormal];
  152. }
  153. /**
  154. * Action for incrementing metric two.
  155. *
  156. * @param button The button object where the action took place.
  157. */
  158. - (void)incrementMetricTwo:(UIButton *)button {
  159. self.metricTwoValue++;
  160. [self.trace incrementMetric:@"metric2" byInt:1];
  161. NSString *stringValue = [NSString stringWithFormat:@"metric2 - %zd", self.metricTwoValue];
  162. [self.metricTwoButton setTitle:stringValue forState:UIControlStateNormal];
  163. }
  164. /**
  165. * Action for adding a custom attribute onto the trace.
  166. *
  167. * @param button The button object where the action took place.
  168. */
  169. - (void)addCustomAttribute:(UIButton *)button {
  170. NSString *attrName = [NSString stringWithFormat:@"attr%d", (int)_customAttributeCounter];
  171. [self.trace setValue:_customAttributeValue forAttribute:attrName];
  172. ++_customAttributeCounter;
  173. [self chooseCustomAttribute];
  174. }
  175. #pragma mark - View hierarchy
  176. /** @brief Creates the view hierarchy for the PerfTraceView. */
  177. - (void)createViewTree {
  178. [self addSubview:self.nameLabel];
  179. [self addSubview:self.stopTraceButton];
  180. [self addSubview:self.stageButton];
  181. [self addSubview:self.metricOneButton];
  182. [self addSubview:self.metricTwoButton];
  183. [self addSubview:self.customAttributeButton];
  184. }
  185. /** @brief Constrains for the views inside PerfTraceView. */
  186. - (void)constrainViews {
  187. [NSLayoutConstraint activateConstraints:@[
  188. [self.nameLabel.topAnchor constraintEqualToAnchor:self.topAnchor constant:5.0f],
  189. [self.nameLabel.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
  190. // Position adding a stage button and stop trace button.
  191. [self.stopTraceButton.topAnchor constraintEqualToAnchor:self.nameLabel.bottomAnchor
  192. constant:10.0f],
  193. [self.stageButton.centerYAnchor constraintEqualToAnchor:self.stopTraceButton.centerYAnchor],
  194. [self.stopTraceButton.widthAnchor constraintEqualToAnchor:self.stageButton.widthAnchor],
  195. [self.stageButton.leftAnchor constraintEqualToAnchor:self.stopTraceButton.rightAnchor
  196. constant:50.0f],
  197. [self.stopTraceButton.rightAnchor constraintEqualToAnchor:self.centerXAnchor constant:-25.0f],
  198. // Position metric button below stage button.
  199. [self.metricOneButton.topAnchor constraintEqualToAnchor:self.stageButton.bottomAnchor
  200. constant:5.0f],
  201. [self.metricTwoButton.centerYAnchor constraintEqualToAnchor:self.metricOneButton.centerYAnchor],
  202. [self.metricOneButton.widthAnchor constraintEqualToAnchor:self.metricTwoButton.widthAnchor],
  203. [self.metricTwoButton.leftAnchor constraintEqualToAnchor:self.metricOneButton.rightAnchor
  204. constant:50.0f],
  205. [self.metricOneButton.rightAnchor constraintEqualToAnchor:self.centerXAnchor constant:-25.0f],
  206. // Position Custom Attribute button below metric buttons.
  207. [self.customAttributeButton.topAnchor constraintEqualToAnchor:self.metricOneButton.bottomAnchor
  208. constant:5.0f],
  209. [self.customAttributeButton.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
  210. // Bottom constraint is required to determine height.
  211. [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.customAttributeButton.bottomAnchor
  212. constant:10.0f],
  213. ]];
  214. }
  215. #pragma mark - Lazy creation of views.
  216. - (UILabel *)nameLabel {
  217. if (!_nameLabel) {
  218. _nameLabel = [[UILabel alloc] init];
  219. _nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
  220. }
  221. return _nameLabel;
  222. }
  223. - (UILabel *)newStageLabel {
  224. UILabel *label = [[UILabel alloc] init];
  225. label.translatesAutoresizingMaskIntoConstraints = NO;
  226. return label;
  227. }
  228. - (UIButton *)stopTraceButton {
  229. if (!_stopTraceButton) {
  230. _stopTraceButton = [self newButton];
  231. AccessibilityItem *item = [[self class] stopAccessibilityItemWithTraceName:self.trace.name];
  232. _stopTraceButton.accessibilityLabel = item.accessibilityLabel;
  233. _stopTraceButton.accessibilityIdentifier = item.accessibilityID;
  234. [_stopTraceButton setTitle:@"Stop trace" forState:UIControlStateNormal];
  235. [_stopTraceButton addTarget:self
  236. action:@selector(stopTrace:)
  237. forControlEvents:UIControlEventTouchUpInside];
  238. }
  239. return _stopTraceButton;
  240. }
  241. - (UIButton *)stageButton {
  242. if (!_stageButton) {
  243. _stageButton = [self newButton];
  244. AccessibilityItem *item = [[self class] stageAccessibilityItemWithTraceName:self.trace.name];
  245. _stageButton.accessibilityLabel = item.accessibilityLabel;
  246. _stageButton.accessibilityIdentifier = item.accessibilityID;
  247. [_stageButton setTitle:@"Stage" forState:UIControlStateNormal];
  248. [_stageButton addTarget:self
  249. action:@selector(createStage:)
  250. forControlEvents:UIControlEventTouchUpInside];
  251. }
  252. return _stageButton;
  253. }
  254. - (UIButton *)metricOneButton {
  255. if (!_metricOneButton) {
  256. _metricOneButton = [self newButton];
  257. AccessibilityItem *item =
  258. [[self class] metricOneAccessibilityItemWithTraceName:self.trace.name];
  259. _metricOneButton.accessibilityLabel = item.accessibilityLabel;
  260. _metricOneButton.accessibilityIdentifier = item.accessibilityID;
  261. [_metricOneButton setTitle:@"Metric 1" forState:UIControlStateNormal];
  262. [_metricOneButton addTarget:self
  263. action:@selector(incrementMetricOne:)
  264. forControlEvents:UIControlEventTouchUpInside];
  265. }
  266. return _metricOneButton;
  267. }
  268. - (UIButton *)metricTwoButton {
  269. if (!_metricTwoButton) {
  270. _metricTwoButton = [self newButton];
  271. AccessibilityItem *item =
  272. [[self class] metricTwoAccessibilityItemWithTraceName:self.trace.name];
  273. _metricTwoButton.accessibilityLabel = item.accessibilityLabel;
  274. _metricTwoButton.accessibilityIdentifier = item.accessibilityID;
  275. [_metricTwoButton setTitle:@"Metric 2" forState:UIControlStateNormal];
  276. [_metricTwoButton addTarget:self
  277. action:@selector(incrementMetricTwo:)
  278. forControlEvents:UIControlEventTouchUpInside];
  279. }
  280. return _metricTwoButton;
  281. }
  282. - (UIButton *)customAttributeButton {
  283. if (!_customAttributeButton) {
  284. _customAttributeButton = [self newButton];
  285. AccessibilityItem *item =
  286. [[self class] customAttributeAccessibilityItemWithTraceName:self.trace.name];
  287. _customAttributeButton.accessibilityLabel = item.accessibilityLabel;
  288. _customAttributeButton.accessibilityIdentifier = item.accessibilityID;
  289. [_customAttributeButton addTarget:self
  290. action:@selector(addCustomAttribute:)
  291. forControlEvents:UIControlEventTouchUpInside];
  292. }
  293. return _customAttributeButton;
  294. }
  295. #pragma mark - View utility functions
  296. /**
  297. * Creates a new button with default UI properties.
  298. *
  299. * @return button The button that was created.
  300. */
  301. - (UIButton *)newButton {
  302. UIButton *button = [[UIButton alloc] init];
  303. button.translatesAutoresizingMaskIntoConstraints = NO;
  304. [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  305. [button setTitleColor:[UIColor lightGrayColor] forState:UIControlStateDisabled];
  306. button.titleLabel.font = [UIFont systemFontOfSize:12.0];
  307. button.contentEdgeInsets = UIEdgeInsetsMake(10.0f, 20.0f, 10.0f, 20.0f);
  308. button.layer.cornerRadius = 3.0f;
  309. button.layer.borderColor = [[UIColor blackColor] CGColor];
  310. button.layer.borderWidth = 1.0f;
  311. return button;
  312. }
  313. - (void)chooseCustomAttribute {
  314. NSArray<NSString *> *const values = @[
  315. @"apple", @"pear", @"plum", @"orange", @"purple", @"red", @"blue", @"1", @"2", @"3", @"4", @"5",
  316. @"6", @"7", @"8", @"9", @"0"
  317. ];
  318. _customAttributeValue = values[arc4random_uniform((uint32_t)values.count)];
  319. NSString *buttonTitle;
  320. buttonTitle = [NSString stringWithFormat:@"Add Attribute: attr%d with Value: %@",
  321. (int)_customAttributeCounter, _customAttributeValue];
  322. [_customAttributeButton setTitle:buttonTitle forState:UIControlStateNormal];
  323. }
  324. @end