/* * * Copyright 2019 gRPC authors. * * 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 "PerfTests.h" #include #import #import #import #import #import #import #import #import #import #import #import #import "src/objective-c/tests/RemoteTestClient/Messages.pbobjc.h" #import "src/objective-c/tests/RemoteTestClient/Test.pbobjc.h" #import "src/objective-c/tests/RemoteTestClient/Test.pbrpc.h" #import "PerfTestsBlockCallbacks.h" #define TEST_TIMEOUT 128 extern const char *kCFStreamVarName; // Convenience constructors for the generated proto messages: @interface RMTStreamingOutputCallRequest (Constructors) + (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize requestedResponseSize:(NSNumber *)responseSize; @end @implementation RMTStreamingOutputCallRequest (Constructors) + (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize requestedResponseSize:(NSNumber *)responseSize { RMTStreamingOutputCallRequest *request = [self message]; RMTResponseParameters *parameters = [RMTResponseParameters message]; parameters.size = responseSize.intValue; [request.responseParametersArray addObject:parameters]; request.payload.body = [NSMutableData dataWithLength:payloadSize.unsignedIntegerValue]; return request; } @end @interface DefaultInterceptorFactory : NSObject - (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager; @end @implementation DefaultInterceptorFactory - (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager { dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); return [[GRPCInterceptor alloc] initWithInterceptorManager:interceptorManager dispatchQueue:queue]; } @end #pragma mark Tests @implementation PerfTests { RMTTestService *_service; } + (XCTestSuite *)defaultTestSuite { if (self == [PerfTests class]) { return [XCTestSuite testSuiteWithName:@"PerfTestsEmptySuite"]; } else { return super.defaultTestSuite; } } + (NSString *)host { return nil; } // This number indicates how many bytes of overhead does Protocol Buffers encoding add onto the // message. The number varies as different message.proto is used on different servers. The actual // number for each interop server is overridden in corresponding derived test classes. - (int32_t)encodingOverhead { return 0; } + (GRPCTransportID)transport { return NULL; } + (NSString *)PEMRootCertificates { return nil; } + (NSString *)hostNameOverride { return nil; } - (void)setUp { self.continueAfterFailure = NO; [GRPCCall resetHostSettings]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [GRPCCall closeOpenConnections]; #pragma clang diagnostic pop _service = [[self class] host] ? [RMTTestService serviceWithHost:[[self class] host]] : nil; } - (BOOL)isUsingCFStream { return [NSStringFromClass([self class]) isEqualToString:@"PerfTestsCFStreamSSL"]; } - (void)pingPongV2APIWithRequest:(RMTStreamingOutputCallRequest *)request numMessages:(int)numMessages options:(GRPCMutableCallOptions *)options { __weak XCTestExpectation *expectation = [self expectationWithDescription:@"PingPong"]; __block BOOL flowControlEnabled = options.flowControlEnabled; __block int index = 0; __block GRPCStreamingProtoCall *call = [self->_service fullDuplexCallWithResponseHandler:[[PerfTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil messageCallback:^(id message) { int indexCopy; @synchronized(self) { indexCopy = index; index += 1; } if (indexCopy < numMessages) { [call writeMessage:request]; if (flowControlEnabled) { [call receiveNextMessage]; } } else { [call finish]; } } closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { [expectation fulfill]; }] callOptions:options]; [call start]; if (flowControlEnabled) { [call receiveNextMessage]; } [call writeMessage:request]; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } - (void)testPingPongRPCWithV2API { GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; options.transport = [[self class] transport]; options.PEMRootCertificates = [[self class] PEMRootCertificates]; options.hostNameOverride = [[self class] hostNameOverride]; id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:@1 requestedResponseSize:@1]; // warm up [self pingPongV2APIWithRequest:request numMessages:1000 options:options]; [self measureBlock:^{ [self pingPongV2APIWithRequest:request numMessages:1000 options:options]; }]; } - (void)testPingPongRPCWithFlowControl { GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; options.transport = [[self class] transport]; options.PEMRootCertificates = [[self class] PEMRootCertificates]; options.hostNameOverride = [[self class] hostNameOverride]; options.flowControlEnabled = YES; id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:@1 requestedResponseSize:@1]; // warm up [self pingPongV2APIWithRequest:request numMessages:1000 options:options]; [self measureBlock:^{ [self pingPongV2APIWithRequest:request numMessages:1000 options:options]; }]; } - (void)testPingPongRPCWithInterceptor { GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; options.transport = [[self class] transport]; options.PEMRootCertificates = [[self class] PEMRootCertificates]; options.hostNameOverride = [[self class] hostNameOverride]; options.interceptorFactories = @[ [[DefaultInterceptorFactory alloc] init] ]; id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:@1 requestedResponseSize:@1]; // warm up [self pingPongV2APIWithRequest:request numMessages:1000 options:options]; [self measureBlock:^{ [self pingPongV2APIWithRequest:request numMessages:1000 options:options]; }]; } - (void)pingPongV1APIWithRequest:(RMTStreamingOutputCallRequest *)request numMessages:(int)numMessages { __block int index = 0; __weak XCTestExpectation *expectation = [self expectationWithDescription:@"PingPong"]; GRXBufferedPipe *requestsBuffer = [[GRXBufferedPipe alloc] init]; [requestsBuffer writeValue:request]; [_service fullDuplexCallWithRequestsWriter:requestsBuffer eventHandler:^(BOOL done, RMTStreamingOutputCallResponse *response, NSError *error) { if (response) { int indexCopy; @synchronized(self) { index += 1; indexCopy = index; } if (indexCopy < numMessages) { [requestsBuffer writeValue:request]; } else { [requestsBuffer writesFinishedWithError:nil]; } } if (done) { [expectation fulfill]; } }]; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } - (void)testPingPongRPCWithV1API { id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:@1 requestedResponseSize:@1]; [self pingPongV1APIWithRequest:request numMessages:1000]; [self measureBlock:^{ [self pingPongV1APIWithRequest:request numMessages:1000]; }]; } - (void)unaryRPCsWithServices:(NSArray *)services request:(RMTSimpleRequest *)request callsPerService:(int)callsPerService maxOutstandingCalls:(int)maxOutstandingCalls callOptions:(GRPCMutableCallOptions *)options { __weak XCTestExpectation *expectation = [self expectationWithDescription:@"unaryRPC"]; dispatch_semaphore_t sema = dispatch_semaphore_create(maxOutstandingCalls); __block int index = 0; for (RMTTestService *service in services) { for (int i = 0; i < callsPerService; ++i) { GRPCUnaryProtoCall *call = [service unaryCallWithMessage:request responseHandler:[[PerfTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil messageCallback:nil closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { dispatch_semaphore_signal(sema); @synchronized(self) { ++index; if (index == callsPerService * [services count]) { [expectation fulfill]; } } }] callOptions:options]; dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(TEST_TIMEOUT * NSEC_PER_SEC)); dispatch_semaphore_wait(sema, timeout); [call start]; } } [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } - (void)test1MBUnaryRPC { // Workaround Apple CFStream bug if ([self isUsingCFStream]) { return; } RMTSimpleRequest *request = [RMTSimpleRequest message]; request.responseSize = 1048576; request.payload.body = [NSMutableData dataWithLength:1048576]; GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; options.transport = [[self class] transport]; options.PEMRootCertificates = [[self class] PEMRootCertificates]; options.hostNameOverride = [[self class] hostNameOverride]; // warm up [self unaryRPCsWithServices:@[ self->_service ] request:request callsPerService:50 maxOutstandingCalls:10 callOptions:options]; [self measureBlock:^{ [self unaryRPCsWithServices:@[ self->_service ] request:request callsPerService:50 maxOutstandingCalls:10 callOptions:options]; }]; } - (void)test1KBUnaryRPC { RMTSimpleRequest *request = [RMTSimpleRequest message]; request.responseSize = 1024; request.payload.body = [NSMutableData dataWithLength:1024]; GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; options.transport = [[self class] transport]; options.PEMRootCertificates = [[self class] PEMRootCertificates]; options.hostNameOverride = [[self class] hostNameOverride]; // warm up [self unaryRPCsWithServices:@[ self->_service ] request:request callsPerService:1000 maxOutstandingCalls:100 callOptions:options]; [self measureBlock:^{ [self unaryRPCsWithServices:@[ self->_service ] request:request callsPerService:1000 maxOutstandingCalls:100 callOptions:options]; }]; } - (void)testMultipleChannels { NSString *port = [[[self class] host] componentsSeparatedByString:@":"][1]; int kNumAddrs = 10; NSMutableArray *addrs = [NSMutableArray arrayWithCapacity:kNumAddrs]; NSMutableArray *services = [NSMutableArray arrayWithCapacity:kNumAddrs]; for (int i = 0; i < kNumAddrs; ++i) { // http://readme.localtest.me/ addrs[i] = [NSString stringWithFormat:@"%d.localtest.me", (i + 1)]; NSString *hostWithPort = [NSString stringWithFormat:@"%@:%@", addrs[i], port]; services[i] = [RMTTestService serviceWithHost:hostWithPort]; } RMTSimpleRequest *request = [RMTSimpleRequest message]; request.responseSize = 0; request.payload.body = [NSMutableData dataWithLength:0]; GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; options.transport = [[self class] transport]; options.PEMRootCertificates = [[self class] PEMRootCertificates]; options.hostNameOverride = [[self class] hostNameOverride]; // warm up [self unaryRPCsWithServices:services request:request callsPerService:100 maxOutstandingCalls:100 callOptions:options]; [self measureBlock:^{ [self unaryRPCsWithServices:services request:request callsPerService:100 maxOutstandingCalls:100 callOptions:options]; }]; } @end