PerfE2EViewController.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  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 "PerfE2EViewController.h"
  15. #import "PerfE2EScreenTracesViewController.h"
  16. #import "PerfNetworkRequestMaker.h"
  17. #import "PerfTraceDelegate.h"
  18. #import "PerfTraceMaker.h"
  19. #import "FirebasePerformance/FIRPerformance.h"
  20. #import "PerfE2EUtils.h"
  21. static NSString *const kURLbasePath = @"fireperf-echo.appspot.com";
  22. static const float kTraceMeanDuration = 3.0;
  23. static const float kTraceDurationDeviation = 0.3;
  24. static const float kNetworkRequestDelayMean = 1.0;
  25. static const float kNetworkRequestDelayDeviation = 0.3;
  26. static const NSInteger kNetworkResponseSizeMean = 1024;
  27. static const NSInteger kNetworkResponseSizeDeviation = 80;
  28. static const NSInteger kNumberTraceLoopCount = 15;
  29. static const NSInteger kNumberNetworkRequestLoopCount = 15;
  30. static NSInteger numberOfPendingTraces = 0;
  31. @interface PerfE2EViewController () <PerfTraceDelegate>
  32. /** Button to initiate the traces and network requests */
  33. @property(nonatomic) UIButton *startTracesButton;
  34. /** Button to navigate to a new screen to generate slow and frozen frames. */
  35. @property(nonatomic) UIButton *testScreenTracesButton;
  36. /** Button to navigate to a new screen to generate slow and frozen frames. */
  37. @property(nonatomic) UILabel *pendingTraceCoungLabel;
  38. @end
  39. @implementation PerfE2EViewController
  40. - (void)loadView {
  41. UIView *perfView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  42. perfView.backgroundColor = [UIColor whiteColor];
  43. self.view = perfView;
  44. [self createViewTree];
  45. [self constrainViews];
  46. }
  47. /**
  48. * Starts the custom trace generation and the network request generation.
  49. */
  50. - (void)startTraces:(UIButton *)button {
  51. for (int i = 0; i < kNumberTraceLoopCount; i++) {
  52. [self createTraceWithInterval:1.0 numberOfTraces:32];
  53. }
  54. for (int i = 0; i < kNumberNetworkRequestLoopCount; i++) {
  55. [self createNetworkRequestWithInterval:1.0 numberOfRequests:32];
  56. }
  57. }
  58. /** Navigates to the screen that allows us to generate slow and forzen frames. */
  59. - (void)navigateToSlowFramesTest:(UIButton *)button {
  60. PerfE2EScreenTracesViewController *screenTracesViewController =
  61. [[PerfE2EScreenTracesViewController alloc] init];
  62. [self.navigationController pushViewController:screenTracesViewController animated:YES];
  63. }
  64. #pragma mark - Trace delegate methods
  65. - (void)traceStarted {
  66. numberOfPendingTraces++;
  67. dispatch_async(dispatch_get_main_queue(), ^{
  68. [self.pendingTraceCoungLabel
  69. setText:[NSString stringWithFormat:@"Pending traces count - %ld", numberOfPendingTraces]];
  70. });
  71. }
  72. - (void)traceCompleted {
  73. numberOfPendingTraces--;
  74. NSLog(@"Pending traces - %ld", numberOfPendingTraces);
  75. dispatch_async(dispatch_get_main_queue(), ^{
  76. [self.pendingTraceCoungLabel
  77. setText:[NSString stringWithFormat:@"Pending traces count - %ld", numberOfPendingTraces]];
  78. });
  79. }
  80. #pragma mark - Trace creation methods
  81. /**
  82. * Creates traces at regular intervals until the number of traces exceeds maxTraceCount.
  83. *
  84. * @param interval Interval at which the traces are created.
  85. * @param maxTraceCount Maximum number of traces to be created.
  86. */
  87. - (void)createTraceWithInterval:(NSTimeInterval)interval numberOfTraces:(NSInteger)maxTraceCount {
  88. void (^traceCreationBlock)(NSInteger, NSTimeInterval) =
  89. ^(NSInteger maxTraceCount, NSTimeInterval interval) {
  90. __block NSInteger traceCount = 0;
  91. __block dispatch_source_t traceTimer =
  92. dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
  93. dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
  94. dispatch_source_set_timer(traceTimer, DISPATCH_TIME_NOW, interval * NSEC_PER_SEC,
  95. 0.02 * NSEC_PER_SEC);
  96. dispatch_source_set_event_handler(traceTimer, ^{
  97. NSString *traceName = [NSString stringWithFormat:@"t%02ld", (long)traceCount];
  98. CGFloat gaussianValue =
  99. randomGaussianValueWithMeanAndDeviation(kTraceMeanDuration, kTraceDurationDeviation);
  100. CGFloat traceDuration = traceCount + gaussianValue;
  101. NSLog(@"Creating trace with name %@ for duration %0.2fs", traceName, traceDuration);
  102. [PerfTraceMaker createTraceWithName:traceName duration:traceDuration delegate:self];
  103. traceCount++;
  104. if (traceCount >= maxTraceCount) {
  105. dispatch_source_cancel(traceTimer);
  106. traceTimer = nil;
  107. traceCount = 0;
  108. }
  109. });
  110. dispatch_resume(traceTimer);
  111. };
  112. dispatch_async(dispatch_get_main_queue(), ^{
  113. traceCreationBlock(maxTraceCount, interval);
  114. });
  115. }
  116. /**
  117. * Creates network requests at regular intervals until the number of requests exceeds
  118. * maxRequestCount.
  119. *
  120. * @param interval Interval at which the traces are created.
  121. * @param maxRequestCount Maximum number of traces to be created.
  122. */
  123. - (void)createNetworkRequestWithInterval:(NSTimeInterval)interval
  124. numberOfRequests:(NSInteger)maxRequestCount {
  125. void (^networkRequestBlock)(NSInteger, NSTimeInterval) =
  126. ^(NSInteger maxRequestCount, NSTimeInterval interval) {
  127. __block NSInteger requestCount = 0;
  128. __block dispatch_source_t networkTimer =
  129. dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
  130. dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
  131. dispatch_source_set_timer(networkTimer, DISPATCH_TIME_NOW, interval * NSEC_PER_SEC,
  132. 0.02 * NSEC_PER_SEC);
  133. dispatch_source_set_event_handler(networkTimer, ^{
  134. requestCount++;
  135. if (requestCount > maxRequestCount) {
  136. dispatch_source_cancel(networkTimer);
  137. networkTimer = nil;
  138. } else {
  139. NSURLRequest *request = [self generateURLRequestWithChangingProperties];
  140. NSLog(@"Making network request - %@", request.URL.absoluteString);
  141. [PerfNetworkRequestMaker performNetworkRequest:request delegate:self];
  142. }
  143. });
  144. dispatch_resume(networkTimer);
  145. };
  146. dispatch_async(dispatch_get_main_queue(), ^{
  147. networkRequestBlock(maxRequestCount, interval);
  148. });
  149. }
  150. /**
  151. * Generates a URL request with random scheme, random query path, and random query parameters with a
  152. * random HTTP method.
  153. *
  154. * @return A valid NSURLRequest object.
  155. */
  156. - (NSURLRequest *)generateURLRequestWithChangingProperties {
  157. CGFloat delayTime = randomGaussianValueWithMeanAndDeviation(kNetworkRequestDelayMean,
  158. kNetworkRequestDelayDeviation);
  159. NSInteger responseSize = (NSInteger)randomGaussianValueWithMeanAndDeviation(
  160. kNetworkResponseSizeMean, kNetworkResponseSizeDeviation);
  161. NSString *baseURLString =
  162. [NSString stringWithFormat:@"%@://%@/%@/?delay=%0.2fs&size=%ld&mime=%@&status=%ld",
  163. [self getRandomURLScheme], kURLbasePath, [self getRandomQueryPath],
  164. delayTime, responseSize, [self getRandomMIMEType],
  165. [self getRandomStatusCode]];
  166. NSURL *baseURL = [NSURL URLWithString:baseURLString];
  167. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:baseURL];
  168. [request setTimeoutInterval:5 * 60];
  169. [request setHTTPMethod:[self getRandomHTTPMethod]];
  170. return [request copy];
  171. }
  172. /**
  173. * Generates a random query path.
  174. *
  175. * @return A query path.
  176. */
  177. - (NSString *)getRandomQueryPath {
  178. static NSArray<NSString *> *queryPaths;
  179. static dispatch_once_t onceToken;
  180. dispatch_once(&onceToken, ^{
  181. queryPaths = @[
  182. @"some/random/path",
  183. @"some/path",
  184. @"some/path/which/keeps/growing",
  185. ];
  186. });
  187. int random = arc4random_uniform((int)queryPaths.count);
  188. NSString *queryPath = queryPaths[random];
  189. return queryPath;
  190. }
  191. /**
  192. * Generates a random URL scheme.
  193. *
  194. * @return A URL scheme.
  195. */
  196. - (NSString *)getRandomURLScheme {
  197. static NSArray<NSString *> *URLSchemes;
  198. static dispatch_once_t onceToken;
  199. dispatch_once(&onceToken, ^{
  200. URLSchemes = @[
  201. @"http",
  202. @"https",
  203. ];
  204. });
  205. int random = arc4random_uniform((int)URLSchemes.count);
  206. NSString *URLScheme = URLSchemes[random];
  207. return URLScheme;
  208. }
  209. /**
  210. * Generates a random MIME type.
  211. *
  212. * @return A MIME type.
  213. */
  214. - (NSString *)getRandomMIMEType {
  215. static NSArray<NSString *> *MIMETypes;
  216. static dispatch_once_t onceToken;
  217. dispatch_once(&onceToken, ^{
  218. MIMETypes = @[
  219. @"text/html", @"application/octet-stream", @"application/postscript", @"video/avi",
  220. @"image/png", @"text/plain"
  221. ];
  222. });
  223. int random = arc4random_uniform((int)MIMETypes.count);
  224. NSString *MIMEString = MIMETypes[random];
  225. return MIMEString;
  226. }
  227. /**
  228. * Generates a random HTTP status code.
  229. *
  230. * @return A HTTP status code.
  231. */
  232. - (NSInteger)getRandomStatusCode {
  233. static NSArray<NSNumber *> *statusCodes;
  234. static dispatch_once_t onceToken;
  235. dispatch_once(&onceToken, ^{
  236. statusCodes = @[ @(200), @(201), @(202), @(300), @(400), @(502), @(503), @(504) ];
  237. });
  238. int random = arc4random_uniform((int)statusCodes.count);
  239. NSNumber *statusCode = statusCodes[random];
  240. return statusCode.integerValue;
  241. }
  242. /**
  243. * Generates a random HTTP method.
  244. *
  245. * @return A HTTP method string.
  246. */
  247. - (NSString *)getRandomHTTPMethod {
  248. static NSArray<NSString *> *HTTPMethods;
  249. static dispatch_once_t onceToken;
  250. dispatch_once(&onceToken, ^{
  251. HTTPMethods = @[ @"GET", @"POST", @"PUT", @"DELETE", @"PATCH", @"OPTIONS" ];
  252. });
  253. int random = arc4random_uniform((int)HTTPMethods.count);
  254. NSString *HTTPMethod = HTTPMethods[random];
  255. return HTTPMethod;
  256. }
  257. #pragma mark - View hierarchy methods
  258. /** Adds the relevant subviews to the hierarchy. */
  259. - (void)createViewTree {
  260. [self.view addSubview:self.startTracesButton];
  261. [self.view addSubview:self.pendingTraceCoungLabel];
  262. [self.view addSubview:self.testScreenTracesButton];
  263. }
  264. /** Applies constraints to the view elements. */
  265. - (void)constrainViews {
  266. [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.startTracesButton
  267. attribute:NSLayoutAttributeCenterX
  268. relatedBy:NSLayoutRelationEqual
  269. toItem:self.view
  270. attribute:NSLayoutAttributeCenterX
  271. multiplier:1.0
  272. constant:0.0]];
  273. [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.testScreenTracesButton
  274. attribute:NSLayoutAttributeCenterX
  275. relatedBy:NSLayoutRelationEqual
  276. toItem:self.view
  277. attribute:NSLayoutAttributeCenterX
  278. multiplier:1.0
  279. constant:0.0]];
  280. [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.pendingTraceCoungLabel
  281. attribute:NSLayoutAttributeCenterX
  282. relatedBy:NSLayoutRelationEqual
  283. toItem:self.view
  284. attribute:NSLayoutAttributeCenterX
  285. multiplier:1.0
  286. constant:0.0]];
  287. [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.startTracesButton
  288. attribute:NSLayoutAttributeBottom
  289. relatedBy:NSLayoutRelationEqual
  290. toItem:self.pendingTraceCoungLabel
  291. attribute:NSLayoutAttributeTop
  292. multiplier:1.0
  293. constant:-20.0]];
  294. [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.pendingTraceCoungLabel
  295. attribute:NSLayoutAttributeCenterY
  296. relatedBy:NSLayoutRelationEqual
  297. toItem:self.view
  298. attribute:NSLayoutAttributeCenterY
  299. multiplier:1.0
  300. constant:0.0]];
  301. [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.testScreenTracesButton
  302. attribute:NSLayoutAttributeTop
  303. relatedBy:NSLayoutRelationEqual
  304. toItem:self.pendingTraceCoungLabel
  305. attribute:NSLayoutAttributeBottom
  306. multiplier:1.0
  307. constant:20.0]];
  308. NSDictionary *viewBindings = NSDictionaryOfVariableBindings(_startTracesButton);
  309. [self.view
  310. addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_startTracesButton(100)]"
  311. options:kNilOptions
  312. metrics:nil
  313. views:viewBindings]];
  314. [self.view
  315. addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_startTracesButton(50)]"
  316. options:kNilOptions
  317. metrics:nil
  318. views:viewBindings]];
  319. viewBindings = NSDictionaryOfVariableBindings(_pendingTraceCoungLabel);
  320. [self.view addConstraints:[NSLayoutConstraint
  321. constraintsWithVisualFormat:@"H:[_pendingTraceCoungLabel(200)]"
  322. options:kNilOptions
  323. metrics:nil
  324. views:viewBindings]];
  325. [self.view addConstraints:[NSLayoutConstraint
  326. constraintsWithVisualFormat:@"V:[_pendingTraceCoungLabel(50)]"
  327. options:kNilOptions
  328. metrics:nil
  329. views:viewBindings]];
  330. viewBindings = NSDictionaryOfVariableBindings(_testScreenTracesButton);
  331. [self.view addConstraints:[NSLayoutConstraint
  332. constraintsWithVisualFormat:@"H:[_testScreenTracesButton(150)]"
  333. options:kNilOptions
  334. metrics:nil
  335. views:viewBindings]];
  336. [self.view addConstraints:[NSLayoutConstraint
  337. constraintsWithVisualFormat:@"V:[_testScreenTracesButton(50)]"
  338. options:kNilOptions
  339. metrics:nil
  340. views:viewBindings]];
  341. }
  342. #pragma mark - Lazy loaders
  343. - (UIButton *)startTracesButton {
  344. if (!_startTracesButton) {
  345. _startTracesButton = [[UIButton alloc] init];
  346. _startTracesButton.translatesAutoresizingMaskIntoConstraints = NO;
  347. [_startTracesButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  348. _startTracesButton.titleLabel.font = [UIFont systemFontOfSize:12.0];
  349. _startTracesButton.layer.cornerRadius = 3.0f;
  350. _startTracesButton.layer.borderColor = [[UIColor blackColor] CGColor];
  351. _startTracesButton.layer.borderWidth = 1.0f;
  352. [_startTracesButton setTitle:@"Start traces" forState:UIControlStateNormal];
  353. [_startTracesButton addTarget:self
  354. action:@selector(startTraces:)
  355. forControlEvents:UIControlEventTouchUpInside];
  356. }
  357. return _startTracesButton;
  358. }
  359. - (UILabel *)pendingTraceCoungLabel {
  360. if (!_pendingTraceCoungLabel) {
  361. _pendingTraceCoungLabel = [[UILabel alloc] init];
  362. _pendingTraceCoungLabel.translatesAutoresizingMaskIntoConstraints = NO;
  363. _pendingTraceCoungLabel.textAlignment = NSTextAlignmentCenter;
  364. }
  365. return _pendingTraceCoungLabel;
  366. }
  367. - (UIButton *)testScreenTracesButton {
  368. if (!_testScreenTracesButton) {
  369. _testScreenTracesButton = [[UIButton alloc] init];
  370. _testScreenTracesButton.translatesAutoresizingMaskIntoConstraints = NO;
  371. [_testScreenTracesButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  372. _testScreenTracesButton.titleLabel.font = [UIFont systemFontOfSize:12.0];
  373. _testScreenTracesButton.layer.cornerRadius = 3.0f;
  374. _testScreenTracesButton.layer.borderColor = [[UIColor blackColor] CGColor];
  375. _testScreenTracesButton.layer.borderWidth = 1.0f;
  376. [_testScreenTracesButton setTitle:@"Test screen traces" forState:UIControlStateNormal];
  377. [_testScreenTracesButton addTarget:self
  378. action:@selector(navigateToSlowFramesTest:)
  379. forControlEvents:UIControlEventTouchUpInside];
  380. }
  381. return _testScreenTracesButton;
  382. }
  383. @end