mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
445 lines
18 KiB
Objective-C
445 lines
18 KiB
Objective-C
// Copyright 2020 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#import "PerfE2EViewController.h"
|
|
|
|
#import "PerfE2EScreenTracesViewController.h"
|
|
#import "PerfNetworkRequestMaker.h"
|
|
#import "PerfTraceDelegate.h"
|
|
#import "PerfTraceMaker.h"
|
|
|
|
#import "FirebasePerformance/FIRPerformance.h"
|
|
#import "PerfE2EUtils.h"
|
|
|
|
static NSString *const kURLbasePath = @"fireperf-echo.appspot.com";
|
|
static const float kTraceMeanDuration = 3.0;
|
|
static const float kTraceDurationDeviation = 0.3;
|
|
static const float kNetworkRequestDelayMean = 1.0;
|
|
static const float kNetworkRequestDelayDeviation = 0.3;
|
|
static const NSInteger kNetworkResponseSizeMean = 1024;
|
|
static const NSInteger kNetworkResponseSizeDeviation = 80;
|
|
static const NSInteger kNumberTraceLoopCount = 15;
|
|
static const NSInteger kNumberNetworkRequestLoopCount = 15;
|
|
|
|
static NSInteger numberOfPendingTraces = 0;
|
|
|
|
@interface PerfE2EViewController () <PerfTraceDelegate>
|
|
|
|
/** Button to initiate the traces and network requests */
|
|
@property(nonatomic) UIButton *startTracesButton;
|
|
|
|
/** Button to navigate to a new screen to generate slow and frozen frames. */
|
|
@property(nonatomic) UIButton *testScreenTracesButton;
|
|
|
|
/** Button to navigate to a new screen to generate slow and frozen frames. */
|
|
@property(nonatomic) UILabel *pendingTraceCoungLabel;
|
|
|
|
@end
|
|
|
|
@implementation PerfE2EViewController
|
|
|
|
- (void)loadView {
|
|
UIView *perfView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
|
perfView.backgroundColor = [UIColor whiteColor];
|
|
self.view = perfView;
|
|
|
|
[self createViewTree];
|
|
[self constrainViews];
|
|
}
|
|
|
|
/**
|
|
* Starts the custom trace generation and the network request generation.
|
|
*/
|
|
- (void)startTraces:(UIButton *)button {
|
|
for (int i = 0; i < kNumberTraceLoopCount; i++) {
|
|
[self createTraceWithInterval:1.0 numberOfTraces:32];
|
|
}
|
|
for (int i = 0; i < kNumberNetworkRequestLoopCount; i++) {
|
|
[self createNetworkRequestWithInterval:1.0 numberOfRequests:32];
|
|
}
|
|
}
|
|
|
|
/** Navigates to the screen that allows us to generate slow and forzen frames. */
|
|
- (void)navigateToSlowFramesTest:(UIButton *)button {
|
|
PerfE2EScreenTracesViewController *screenTracesViewController =
|
|
[[PerfE2EScreenTracesViewController alloc] init];
|
|
[self.navigationController pushViewController:screenTracesViewController animated:YES];
|
|
}
|
|
|
|
#pragma mark - Trace delegate methods
|
|
|
|
- (void)traceStarted {
|
|
numberOfPendingTraces++;
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self.pendingTraceCoungLabel
|
|
setText:[NSString stringWithFormat:@"Pending traces count - %ld", numberOfPendingTraces]];
|
|
});
|
|
}
|
|
|
|
- (void)traceCompleted {
|
|
numberOfPendingTraces--;
|
|
NSLog(@"Pending traces - %ld", numberOfPendingTraces);
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self.pendingTraceCoungLabel
|
|
setText:[NSString stringWithFormat:@"Pending traces count - %ld", numberOfPendingTraces]];
|
|
});
|
|
}
|
|
|
|
#pragma mark - Trace creation methods
|
|
|
|
/**
|
|
* Creates traces at regular intervals until the number of traces exceeds maxTraceCount.
|
|
*
|
|
* @param interval Interval at which the traces are created.
|
|
* @param maxTraceCount Maximum number of traces to be created.
|
|
*/
|
|
- (void)createTraceWithInterval:(NSTimeInterval)interval numberOfTraces:(NSInteger)maxTraceCount {
|
|
void (^traceCreationBlock)(NSInteger, NSTimeInterval) =
|
|
^(NSInteger maxTraceCount, NSTimeInterval interval) {
|
|
__block NSInteger traceCount = 0;
|
|
__block dispatch_source_t traceTimer =
|
|
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
|
|
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
|
|
dispatch_source_set_timer(traceTimer, DISPATCH_TIME_NOW, interval * NSEC_PER_SEC,
|
|
0.02 * NSEC_PER_SEC);
|
|
|
|
dispatch_source_set_event_handler(traceTimer, ^{
|
|
NSString *traceName = [NSString stringWithFormat:@"t%02ld", (long)traceCount];
|
|
CGFloat gaussianValue =
|
|
randomGaussianValueWithMeanAndDeviation(kTraceMeanDuration, kTraceDurationDeviation);
|
|
CGFloat traceDuration = traceCount + gaussianValue;
|
|
NSLog(@"Creating trace with name %@ for duration %0.2fs", traceName, traceDuration);
|
|
[PerfTraceMaker createTraceWithName:traceName duration:traceDuration delegate:self];
|
|
|
|
traceCount++;
|
|
if (traceCount >= maxTraceCount) {
|
|
dispatch_source_cancel(traceTimer);
|
|
traceTimer = nil;
|
|
traceCount = 0;
|
|
}
|
|
});
|
|
dispatch_resume(traceTimer);
|
|
};
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
traceCreationBlock(maxTraceCount, interval);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates network requests at regular intervals until the number of requests exceeds
|
|
* maxRequestCount.
|
|
*
|
|
* @param interval Interval at which the traces are created.
|
|
* @param maxRequestCount Maximum number of traces to be created.
|
|
*/
|
|
- (void)createNetworkRequestWithInterval:(NSTimeInterval)interval
|
|
numberOfRequests:(NSInteger)maxRequestCount {
|
|
void (^networkRequestBlock)(NSInteger, NSTimeInterval) =
|
|
^(NSInteger maxRequestCount, NSTimeInterval interval) {
|
|
__block NSInteger requestCount = 0;
|
|
__block dispatch_source_t networkTimer =
|
|
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
|
|
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
|
|
dispatch_source_set_timer(networkTimer, DISPATCH_TIME_NOW, interval * NSEC_PER_SEC,
|
|
0.02 * NSEC_PER_SEC);
|
|
dispatch_source_set_event_handler(networkTimer, ^{
|
|
requestCount++;
|
|
if (requestCount > maxRequestCount) {
|
|
dispatch_source_cancel(networkTimer);
|
|
networkTimer = nil;
|
|
} else {
|
|
NSURLRequest *request = [self generateURLRequestWithChangingProperties];
|
|
NSLog(@"Making network request - %@", request.URL.absoluteString);
|
|
[PerfNetworkRequestMaker performNetworkRequest:request delegate:self];
|
|
}
|
|
});
|
|
dispatch_resume(networkTimer);
|
|
};
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
networkRequestBlock(maxRequestCount, interval);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Generates a URL request with random scheme, random query path, and random query parameters with a
|
|
* random HTTP method.
|
|
*
|
|
* @return A valid NSURLRequest object.
|
|
*/
|
|
- (NSURLRequest *)generateURLRequestWithChangingProperties {
|
|
CGFloat delayTime = randomGaussianValueWithMeanAndDeviation(kNetworkRequestDelayMean,
|
|
kNetworkRequestDelayDeviation);
|
|
NSInteger responseSize = (NSInteger)randomGaussianValueWithMeanAndDeviation(
|
|
kNetworkResponseSizeMean, kNetworkResponseSizeDeviation);
|
|
|
|
NSString *baseURLString =
|
|
[NSString stringWithFormat:@"%@://%@/%@/?delay=%0.2fs&size=%ld&mime=%@&status=%ld",
|
|
[self getRandomURLScheme], kURLbasePath, [self getRandomQueryPath],
|
|
delayTime, responseSize, [self getRandomMIMEType],
|
|
[self getRandomStatusCode]];
|
|
|
|
NSURL *baseURL = [NSURL URLWithString:baseURLString];
|
|
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:baseURL];
|
|
[request setTimeoutInterval:5 * 60];
|
|
[request setHTTPMethod:[self getRandomHTTPMethod]];
|
|
return [request copy];
|
|
}
|
|
|
|
/**
|
|
* Generates a random query path.
|
|
*
|
|
* @return A query path.
|
|
*/
|
|
- (NSString *)getRandomQueryPath {
|
|
static NSArray<NSString *> *queryPaths;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
queryPaths = @[
|
|
@"some/random/path",
|
|
@"some/path",
|
|
@"some/path/which/keeps/growing",
|
|
];
|
|
});
|
|
int random = arc4random_uniform((int)queryPaths.count);
|
|
NSString *queryPath = queryPaths[random];
|
|
return queryPath;
|
|
}
|
|
|
|
/**
|
|
* Generates a random URL scheme.
|
|
*
|
|
* @return A URL scheme.
|
|
*/
|
|
- (NSString *)getRandomURLScheme {
|
|
static NSArray<NSString *> *URLSchemes;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
URLSchemes = @[
|
|
@"http",
|
|
@"https",
|
|
];
|
|
});
|
|
int random = arc4random_uniform((int)URLSchemes.count);
|
|
NSString *URLScheme = URLSchemes[random];
|
|
return URLScheme;
|
|
}
|
|
|
|
/**
|
|
* Generates a random MIME type.
|
|
*
|
|
* @return A MIME type.
|
|
*/
|
|
- (NSString *)getRandomMIMEType {
|
|
static NSArray<NSString *> *MIMETypes;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
MIMETypes = @[
|
|
@"text/html", @"application/octet-stream", @"application/postscript", @"video/avi",
|
|
@"image/png", @"text/plain"
|
|
];
|
|
});
|
|
int random = arc4random_uniform((int)MIMETypes.count);
|
|
NSString *MIMEString = MIMETypes[random];
|
|
return MIMEString;
|
|
}
|
|
|
|
/**
|
|
* Generates a random HTTP status code.
|
|
*
|
|
* @return A HTTP status code.
|
|
*/
|
|
- (NSInteger)getRandomStatusCode {
|
|
static NSArray<NSNumber *> *statusCodes;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
statusCodes = @[ @(200), @(201), @(202), @(300), @(400), @(502), @(503), @(504) ];
|
|
});
|
|
int random = arc4random_uniform((int)statusCodes.count);
|
|
NSNumber *statusCode = statusCodes[random];
|
|
return statusCode.integerValue;
|
|
}
|
|
|
|
/**
|
|
* Generates a random HTTP method.
|
|
*
|
|
* @return A HTTP method string.
|
|
*/
|
|
- (NSString *)getRandomHTTPMethod {
|
|
static NSArray<NSString *> *HTTPMethods;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
HTTPMethods = @[ @"GET", @"POST", @"PUT", @"DELETE", @"PATCH", @"OPTIONS" ];
|
|
});
|
|
int random = arc4random_uniform((int)HTTPMethods.count);
|
|
NSString *HTTPMethod = HTTPMethods[random];
|
|
return HTTPMethod;
|
|
}
|
|
|
|
#pragma mark - View hierarchy methods
|
|
|
|
/** Adds the relevant subviews to the hierarchy. */
|
|
- (void)createViewTree {
|
|
[self.view addSubview:self.startTracesButton];
|
|
[self.view addSubview:self.pendingTraceCoungLabel];
|
|
[self.view addSubview:self.testScreenTracesButton];
|
|
}
|
|
|
|
/** Applies constraints to the view elements. */
|
|
- (void)constrainViews {
|
|
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.startTracesButton
|
|
attribute:NSLayoutAttributeCenterX
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.view
|
|
attribute:NSLayoutAttributeCenterX
|
|
multiplier:1.0
|
|
constant:0.0]];
|
|
|
|
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.testScreenTracesButton
|
|
attribute:NSLayoutAttributeCenterX
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.view
|
|
attribute:NSLayoutAttributeCenterX
|
|
multiplier:1.0
|
|
constant:0.0]];
|
|
|
|
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.pendingTraceCoungLabel
|
|
attribute:NSLayoutAttributeCenterX
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.view
|
|
attribute:NSLayoutAttributeCenterX
|
|
multiplier:1.0
|
|
constant:0.0]];
|
|
|
|
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.startTracesButton
|
|
attribute:NSLayoutAttributeBottom
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.pendingTraceCoungLabel
|
|
attribute:NSLayoutAttributeTop
|
|
multiplier:1.0
|
|
constant:-20.0]];
|
|
|
|
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.pendingTraceCoungLabel
|
|
attribute:NSLayoutAttributeCenterY
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.view
|
|
attribute:NSLayoutAttributeCenterY
|
|
multiplier:1.0
|
|
constant:0.0]];
|
|
|
|
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.testScreenTracesButton
|
|
attribute:NSLayoutAttributeTop
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.pendingTraceCoungLabel
|
|
attribute:NSLayoutAttributeBottom
|
|
multiplier:1.0
|
|
constant:20.0]];
|
|
|
|
NSDictionary *viewBindings = NSDictionaryOfVariableBindings(_startTracesButton);
|
|
[self.view
|
|
addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_startTracesButton(100)]"
|
|
options:kNilOptions
|
|
metrics:nil
|
|
views:viewBindings]];
|
|
|
|
[self.view
|
|
addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_startTracesButton(50)]"
|
|
options:kNilOptions
|
|
metrics:nil
|
|
views:viewBindings]];
|
|
|
|
viewBindings = NSDictionaryOfVariableBindings(_pendingTraceCoungLabel);
|
|
[self.view addConstraints:[NSLayoutConstraint
|
|
constraintsWithVisualFormat:@"H:[_pendingTraceCoungLabel(200)]"
|
|
options:kNilOptions
|
|
metrics:nil
|
|
views:viewBindings]];
|
|
|
|
[self.view addConstraints:[NSLayoutConstraint
|
|
constraintsWithVisualFormat:@"V:[_pendingTraceCoungLabel(50)]"
|
|
options:kNilOptions
|
|
metrics:nil
|
|
views:viewBindings]];
|
|
|
|
viewBindings = NSDictionaryOfVariableBindings(_testScreenTracesButton);
|
|
[self.view addConstraints:[NSLayoutConstraint
|
|
constraintsWithVisualFormat:@"H:[_testScreenTracesButton(150)]"
|
|
options:kNilOptions
|
|
metrics:nil
|
|
views:viewBindings]];
|
|
|
|
[self.view addConstraints:[NSLayoutConstraint
|
|
constraintsWithVisualFormat:@"V:[_testScreenTracesButton(50)]"
|
|
options:kNilOptions
|
|
metrics:nil
|
|
views:viewBindings]];
|
|
}
|
|
|
|
#pragma mark - Lazy loaders
|
|
|
|
- (UIButton *)startTracesButton {
|
|
if (!_startTracesButton) {
|
|
_startTracesButton = [[UIButton alloc] init];
|
|
_startTracesButton.translatesAutoresizingMaskIntoConstraints = NO;
|
|
|
|
[_startTracesButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
|
|
_startTracesButton.titleLabel.font = [UIFont systemFontOfSize:12.0];
|
|
|
|
_startTracesButton.layer.cornerRadius = 3.0f;
|
|
_startTracesButton.layer.borderColor = [[UIColor blackColor] CGColor];
|
|
_startTracesButton.layer.borderWidth = 1.0f;
|
|
|
|
[_startTracesButton setTitle:@"Start traces" forState:UIControlStateNormal];
|
|
|
|
[_startTracesButton addTarget:self
|
|
action:@selector(startTraces:)
|
|
forControlEvents:UIControlEventTouchUpInside];
|
|
}
|
|
return _startTracesButton;
|
|
}
|
|
|
|
- (UILabel *)pendingTraceCoungLabel {
|
|
if (!_pendingTraceCoungLabel) {
|
|
_pendingTraceCoungLabel = [[UILabel alloc] init];
|
|
_pendingTraceCoungLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
|
_pendingTraceCoungLabel.textAlignment = NSTextAlignmentCenter;
|
|
}
|
|
|
|
return _pendingTraceCoungLabel;
|
|
}
|
|
|
|
- (UIButton *)testScreenTracesButton {
|
|
if (!_testScreenTracesButton) {
|
|
_testScreenTracesButton = [[UIButton alloc] init];
|
|
_testScreenTracesButton.translatesAutoresizingMaskIntoConstraints = NO;
|
|
|
|
[_testScreenTracesButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
|
|
_testScreenTracesButton.titleLabel.font = [UIFont systemFontOfSize:12.0];
|
|
|
|
_testScreenTracesButton.layer.cornerRadius = 3.0f;
|
|
_testScreenTracesButton.layer.borderColor = [[UIColor blackColor] CGColor];
|
|
_testScreenTracesButton.layer.borderWidth = 1.0f;
|
|
|
|
[_testScreenTracesButton setTitle:@"Test screen traces" forState:UIControlStateNormal];
|
|
|
|
[_testScreenTracesButton addTarget:self
|
|
action:@selector(navigateToSlowFramesTest:)
|
|
forControlEvents:UIControlEventTouchUpInside];
|
|
}
|
|
return _testScreenTracesButton;
|
|
}
|
|
|
|
@end
|