Răsfoiți Sursa

Allocation counting tests for full RPC flows (#1155)

Motivation:

Now that we have an allocation counting test framework we should add
tests to it!

Modifications:

- Add some common boilerplate for making an echo server and client
- Add an allocation test for doing 1000 unary RPCs sequentially
- Add allocation tests for doing 1000 bidi streaming RPCs sequentially,
  one where each RPC sends 1 request and another where each RPC sends 10
  request.

Result:

More test coverage for allocations.
George Barnett 4 ani în urmă
părinte
comite
fa2735091d

+ 8 - 0
.travis.yml

@@ -37,25 +37,33 @@ jobs:
       script: ./.travis-script.sh -t -a # tests with tsan, run allocation tests
       env:
         - SWIFT_VERSION=5.3.3
+        - MAX_ALLOCS_ALLOWED_bidi_1k_rpcs_10_requests=531000
+        - MAX_ALLOCS_ALLOWED_bidi_1k_rpcs_1_request=243000
         - MAX_ALLOCS_ALLOWED_embedded_server_bidi_1k_rpcs_10_small_requests=112000
         - MAX_ALLOCS_ALLOWED_embedded_server_bidi_1k_rpcs_1_small_request=67000
         - MAX_ALLOCS_ALLOWED_embedded_server_unary_1k_rpcs_1_small_request=63000
+        - MAX_ALLOCS_ALLOWED_unary_1k_ping_pong=234000
     - <<: *tests
       name: "Unit Tests: Ubuntu 18.04 (Swift 5.2)"
       script: ./.travis-script.sh -a # run allocation tests
       env:
         - SWIFT_VERSION=5.2.5
+        - MAX_ALLOCS_ALLOWED_bidi_1k_rpcs_10_requests=542000
+        - MAX_ALLOCS_ALLOWED_bidi_1k_rpcs_1_request=245000
         - MAX_ALLOCS_ALLOWED_embedded_server_bidi_1k_rpcs_10_small_requests=112000
         - MAX_ALLOCS_ALLOWED_embedded_server_bidi_1k_rpcs_1_small_request=67000
         - MAX_ALLOCS_ALLOWED_embedded_server_unary_1k_rpcs_1_small_request=63000
+        - MAX_ALLOCS_ALLOWED_unary_1k_ping_pong=235000
     - <<: *tests
       name: "Unit Tests: Xcode 12.2"
       os: osx
       osx_image: xcode12.2
+      script: ./.travis-script.sh
     - <<: *tests
       name: "Unit Tests: Xcode 11.6"
       os: osx
       osx_image: xcode11.6
+      script: ./.travis-script.sh
     # Interop Tests.
     - &interop_tests
       stage: "Interoperability Tests"

+ 1 - 0
Performance/allocations/tests/run-allocation-counter-tests.sh

@@ -67,6 +67,7 @@ fi
   -p "$here/../../.." \
   -m GRPC \
   -t "$tmp_dir" \
+  -s "$here/shared/Common.swift" \
   -s "$here/shared/Benchmark.swift" \
   -s "$here/shared/echo.pb.swift" \
   -s "$here/shared/echo.grpc.swift" \

+ 36 - 0
Performance/allocations/tests/shared/Common.swift

@@ -0,0 +1,36 @@
+/*
+ * Copyright 2021, gRPC Authors All rights reserved.
+ *
+ * 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 GRPC
+import NIO
+
+func makeEchoServer(
+  group: EventLoopGroup,
+  host: String = "127.0.0.1",
+  port: Int = 0
+) -> EventLoopFuture<Server> {
+  return Server.insecure(group: group)
+    .withServiceProviders([MinimalEchoProvider()])
+    .bind(host: host, port: port)
+}
+
+func makeClientConnection(
+  group: EventLoopGroup,
+  host: String = "127.0.0.1",
+  port: Int
+) -> ClientConnection {
+  return ClientConnection.insecure(group: group)
+    .connect(host: host, port: port)
+}

+ 90 - 0
Performance/allocations/tests/test_bidi_1k_rpcs.swift

@@ -0,0 +1,90 @@
+/*
+ * Copyright 2021, gRPC Authors All rights reserved.
+ *
+ * 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 Dispatch
+import GRPC
+import NIO
+
+class BidiPingPongBenchmark: Benchmark {
+  let rpcs: Int
+  let requests: Int
+  let request: Echo_EchoRequest
+
+  private var group: EventLoopGroup!
+  private var server: Server!
+  private var client: ClientConnection!
+
+  init(rpcs: Int, requests: Int, request: String) {
+    self.rpcs = rpcs
+    self.requests = requests
+    self.request = .with { $0.text = request }
+  }
+
+  func setUp() throws {
+    self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
+    self.server = try makeEchoServer(group: self.group).wait()
+    self.client = makeClientConnection(
+      group: self.group,
+      port: self.server.channel.localAddress!.port!
+    )
+  }
+
+  func tearDown() throws {
+    try self.client.close().wait()
+    try self.server.close().wait()
+    try self.group.syncShutdownGracefully()
+  }
+
+  func run() throws -> Int {
+    let echo = Echo_EchoClient(channel: self.client)
+    var statusCodeSum = 0
+
+    // We'll use this semaphore to make sure we're ping-ponging request-response
+    // pairs on the RPC. Doing so makes the number of allocations much more
+    // stable.
+    let waiter = DispatchSemaphore(value: 1)
+
+    for _ in 0 ..< self.rpcs {
+      let update = echo.update { _ in
+        waiter.signal()
+      }
+
+      for _ in 0 ..< self.requests {
+        waiter.wait()
+        update.sendMessage(self.request, promise: nil)
+      }
+      waiter.wait()
+      update.sendEnd(promise: nil)
+
+      let status = try update.status.wait()
+      statusCodeSum += status.code.rawValue
+      waiter.signal()
+    }
+
+    return statusCodeSum
+  }
+}
+
+func run(identifier: String) {
+  measure(identifier: identifier + "_10_requests") {
+    let benchmark = BidiPingPongBenchmark(rpcs: 1000, requests: 10, request: "")
+    return try! benchmark.runOnce()
+  }
+
+  measure(identifier: identifier + "_1_request") {
+    let benchmark = BidiPingPongBenchmark(rpcs: 1000, requests: 1, request: "")
+    return try! benchmark.runOnce()
+  }
+}

+ 66 - 0
Performance/allocations/tests/test_unary_1k_ping_pong.swift

@@ -0,0 +1,66 @@
+/*
+ * Copyright 2021, gRPC Authors All rights reserved.
+ *
+ * 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 GRPC
+import NIO
+
+class UnaryPingPongBenchmark: Benchmark {
+  let rpcs: Int
+  let request: Echo_EchoRequest
+
+  private var group: EventLoopGroup!
+  private var server: Server!
+  private var client: ClientConnection!
+
+  init(rpcs: Int, request: String) {
+    self.rpcs = rpcs
+    self.request = .with { $0.text = request }
+  }
+
+  func setUp() throws {
+    self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
+    self.server = try makeEchoServer(group: self.group).wait()
+    self.client = makeClientConnection(
+      group: self.group,
+      port: self.server.channel.localAddress!.port!
+    )
+  }
+
+  func tearDown() throws {
+    try self.client.close().wait()
+    try self.server.close().wait()
+    try self.group.syncShutdownGracefully()
+  }
+
+  func run() throws -> Int {
+    let echo = Echo_EchoClient(channel: self.client)
+    var responseLength = 0
+
+    for _ in 0 ..< self.rpcs {
+      let get = echo.get(self.request)
+      let response = try get.response.wait()
+      responseLength += response.text.count
+    }
+
+    return responseLength
+  }
+}
+
+func run(identifier: String) {
+  measure(identifier: identifier) {
+    let benchmark = UnaryPingPongBenchmark(rpcs: 1000, request: "")
+    return try! benchmark.runOnce()
+  }
+}