summaryrefslogtreecommitdiffhomepage
path: root/test/benchmarks/network
diff options
context:
space:
mode:
authorIan Lewis <ianmlewis@gmail.com>2020-08-17 21:44:31 -0400
committerIan Lewis <ianmlewis@gmail.com>2020-08-17 21:44:31 -0400
commitac324f646ee3cb7955b0b45a7453aeb9671cbdf1 (patch)
tree0cbc5018e8807421d701d190dc20525726c7ca76 /test/benchmarks/network
parent352ae1022ce19de28fc72e034cc469872ad79d06 (diff)
parent6d0c5803d557d453f15ac6f683697eeb46dab680 (diff)
Merge branch 'master' into ip-forwarding
- Merges aleksej-paschenko's with HEAD - Adds vfs2 support for ip_forward
Diffstat (limited to 'test/benchmarks/network')
-rw-r--r--test/benchmarks/network/BUILD35
-rw-r--r--test/benchmarks/network/httpd_test.go181
-rw-r--r--test/benchmarks/network/iperf_test.go113
-rw-r--r--test/benchmarks/network/network.go31
-rw-r--r--test/benchmarks/network/nginx_test.go104
-rw-r--r--test/benchmarks/network/node_test.go127
-rw-r--r--test/benchmarks/network/ruby_test.go134
7 files changed, 725 insertions, 0 deletions
diff --git a/test/benchmarks/network/BUILD b/test/benchmarks/network/BUILD
new file mode 100644
index 000000000..bd3f6245c
--- /dev/null
+++ b/test/benchmarks/network/BUILD
@@ -0,0 +1,35 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "network",
+ testonly = 1,
+ srcs = ["network.go"],
+ deps = ["//test/benchmarks/harness"],
+)
+
+go_test(
+ name = "network_test",
+ size = "large",
+ srcs = [
+ "httpd_test.go",
+ "iperf_test.go",
+ "nginx_test.go",
+ "node_test.go",
+ "ruby_test.go",
+ ],
+ library = ":network",
+ tags = [
+ # Requires docker and runsc to be configured before test runs.
+ "manual",
+ "local",
+ ],
+ visibility = ["//:sandbox"],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//pkg/test/testutil",
+ "//test/benchmarks/harness",
+ "//test/benchmarks/tools",
+ ],
+)
diff --git a/test/benchmarks/network/httpd_test.go b/test/benchmarks/network/httpd_test.go
new file mode 100644
index 000000000..336e04c91
--- /dev/null
+++ b/test/benchmarks/network/httpd_test.go
@@ -0,0 +1,181 @@
+// Copyright 2020 The gVisor 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.
+package network
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+// see Dockerfile '//images/benchmarks/httpd'.
+var docs = map[string]string{
+ "notfound": "notfound",
+ "1Kb": "latin1k.txt",
+ "10Kb": "latin10k.txt",
+ "100Kb": "latin100k.txt",
+ "1000Kb": "latin1000k.txt",
+ "1Mb": "latin1024k.txt",
+ "10Mb": "latin10240k.txt",
+}
+
+// BenchmarkHttpdConcurrency iterates the concurrency argument and tests
+// how well the runtime under test handles requests in parallel.
+func BenchmarkHttpdConcurrency(b *testing.B) {
+ // Grab a machine for the client and server.
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get client: %v", err)
+ }
+ defer clientMachine.CleanUp()
+
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get server: %v", err)
+ }
+ defer serverMachine.CleanUp()
+
+ // The test iterates over client concurrency, so set other parameters.
+ concurrency := []int{1, 25, 50, 100, 1000}
+
+ for _, c := range concurrency {
+ b.Run(fmt.Sprintf("%d", c), func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: 10000,
+ Concurrency: c,
+ Doc: docs["10Kb"],
+ }
+ runHttpd(b, clientMachine, serverMachine, hey, false /* reverse */)
+ })
+ }
+}
+
+// BenchmarkHttpdDocSize iterates over different sized payloads, testing how
+// well the runtime handles sending different payload sizes.
+func BenchmarkHttpdDocSize(b *testing.B) {
+ benchmarkHttpdDocSize(b, false /* reverse */)
+}
+
+// BenchmarkReverseHttpdDocSize iterates over different sized payloads, testing
+// how well the runtime handles receiving different payload sizes.
+func BenchmarkReverseHttpdDocSize(b *testing.B) {
+ benchmarkHttpdDocSize(b, true /* reverse */)
+}
+
+func benchmarkHttpdDocSize(b *testing.B, reverse bool) {
+ b.Helper()
+
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer clientMachine.CleanUp()
+
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer serverMachine.CleanUp()
+
+ for name, filename := range docs {
+ concurrency := []int{1, 25, 50, 100, 1000}
+ for _, c := range concurrency {
+ b.Run(fmt.Sprintf("%s_%d", name, c), func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: 10000,
+ Concurrency: c,
+ Doc: filename,
+ }
+ runHttpd(b, clientMachine, serverMachine, hey, reverse)
+ })
+ }
+ }
+}
+
+// runHttpd runs a single test run.
+func runHttpd(b *testing.B, clientMachine, serverMachine harness.Machine, hey *tools.Hey, reverse bool) {
+ b.Helper()
+
+ // Grab a container from the server.
+ ctx := context.Background()
+ var server *dockerutil.Container
+ if reverse {
+ server = serverMachine.GetNativeContainer(ctx, b)
+ } else {
+ server = serverMachine.GetContainer(ctx, b)
+ }
+
+ defer server.CleanUp(ctx)
+
+ // Copy the docs to /tmp and serve from there.
+ cmd := "mkdir -p /tmp/html; cp -r /local/* /tmp/html/.; apache2 -X"
+ port := 80
+
+ // Start the server.
+ if err := server.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/httpd",
+ Ports: []int{port},
+ Env: []string{
+ // Standard environmental variables for httpd.
+ "APACHE_RUN_DIR=/tmp",
+ "APACHE_RUN_USER=nobody",
+ "APACHE_RUN_GROUP=nogroup",
+ "APACHE_LOG_DIR=/tmp",
+ "APACHE_PID_FILE=/tmp/apache.pid",
+ },
+ }, "sh", "-c", cmd); err != nil {
+ b.Fatalf("failed to start server: %v", err)
+ }
+
+ ip, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatalf("failed to find server ip: %v", err)
+ }
+
+ servingPort, err := server.FindPort(ctx, port)
+ if err != nil {
+ b.Fatalf("failed to find server port %d: %v", port, err)
+ }
+
+ // Check the server is serving.
+ harness.WaitUntilServing(ctx, clientMachine, ip, servingPort)
+
+ var client *dockerutil.Container
+ // Grab a client.
+ if reverse {
+ client = clientMachine.GetContainer(ctx, b)
+ } else {
+ client = clientMachine.GetNativeContainer(ctx, b)
+ }
+ defer client.CleanUp(ctx)
+
+ b.ResetTimer()
+ server.RestartProfiles()
+ for i := 0; i < b.N; i++ {
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/hey",
+ }, hey.MakeCmd(ip, servingPort)...)
+ if err != nil {
+ b.Fatalf("run failed with: %v", err)
+ }
+
+ b.StopTimer()
+ hey.Report(b, out)
+ b.StartTimer()
+ }
+}
diff --git a/test/benchmarks/network/iperf_test.go b/test/benchmarks/network/iperf_test.go
new file mode 100644
index 000000000..b8ab7dfb8
--- /dev/null
+++ b/test/benchmarks/network/iperf_test.go
@@ -0,0 +1,113 @@
+// Copyright 2020 The gVisor 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.
+package network
+
+import (
+ "context"
+ "testing"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/pkg/test/testutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+func BenchmarkIperf(b *testing.B) {
+ iperf := tools.Iperf{
+ Time: 10, // time in seconds to run client.
+ }
+
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer clientMachine.CleanUp()
+
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer serverMachine.CleanUp()
+ ctx := context.Background()
+ for _, bm := range []struct {
+ name string
+ clientFunc func(context.Context, testutil.Logger) *dockerutil.Container
+ serverFunc func(context.Context, testutil.Logger) *dockerutil.Container
+ }{
+ // We are either measuring the server or the client. The other should be
+ // runc. e.g. Upload sees how fast the runtime under test uploads to a native
+ // server.
+ {
+ name: "Upload",
+ clientFunc: clientMachine.GetContainer,
+ serverFunc: serverMachine.GetNativeContainer,
+ },
+ {
+ name: "Download",
+ clientFunc: clientMachine.GetNativeContainer,
+ serverFunc: serverMachine.GetContainer,
+ },
+ } {
+ b.Run(bm.name, func(b *testing.B) {
+ // Set up the containers.
+ server := bm.serverFunc(ctx, b)
+ defer server.CleanUp(ctx)
+ client := bm.clientFunc(ctx, b)
+ defer client.CleanUp(ctx)
+
+ // iperf serves on port 5001 by default.
+ port := 5001
+
+ // Start the server.
+ if err := server.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/iperf",
+ Ports: []int{port},
+ }, "iperf", "-s"); err != nil {
+ b.Fatalf("failed to start server with: %v", err)
+ }
+
+ ip, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatalf("failed to find server ip: %v", err)
+ }
+
+ servingPort, err := server.FindPort(ctx, port)
+ if err != nil {
+ b.Fatalf("failed to find port %d: %v", port, err)
+ }
+
+ // Make sure the server is up and serving before we run.
+ if err := harness.WaitUntilServing(ctx, clientMachine, ip, servingPort); err != nil {
+ b.Fatalf("failed to wait for server: %v", err)
+ }
+ // Run the client.
+ b.ResetTimer()
+
+ // Restart the server profiles. If the server isn't being profiled
+ // this does nothing.
+ server.RestartProfiles()
+ for i := 0; i < b.N; i++ {
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/iperf",
+ }, iperf.MakeCmd(ip, servingPort)...)
+ if err != nil {
+ b.Fatalf("failed to run client: %v", err)
+ }
+ b.StopTimer()
+ iperf.Report(b, out)
+ b.StartTimer()
+ }
+ })
+ }
+}
diff --git a/test/benchmarks/network/network.go b/test/benchmarks/network/network.go
new file mode 100644
index 000000000..ce17ddb94
--- /dev/null
+++ b/test/benchmarks/network/network.go
@@ -0,0 +1,31 @@
+// Copyright 2020 The gVisor 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.
+
+// Package network holds benchmarks around raw network performance.
+package network
+
+import (
+ "os"
+ "testing"
+
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+)
+
+var h harness.Harness
+
+// TestMain is the main method for package network.
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/network/nginx_test.go b/test/benchmarks/network/nginx_test.go
new file mode 100644
index 000000000..2bf1a3624
--- /dev/null
+++ b/test/benchmarks/network/nginx_test.go
@@ -0,0 +1,104 @@
+// Copyright 2020 The gVisor 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.
+package network
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+// BenchmarkNginxConcurrency iterates the concurrency argument and tests
+// how well the runtime under test handles requests in parallel.
+// TODO(gvisor.dev/issue/3536): Update with different doc sizes like Httpd.
+func BenchmarkNginxConcurrency(b *testing.B) {
+ // Grab a machine for the client and server.
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get client: %v", err)
+ }
+ defer clientMachine.CleanUp()
+
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get server: %v", err)
+ }
+ defer serverMachine.CleanUp()
+
+ concurrency := []int{1, 5, 10, 25}
+ for _, c := range concurrency {
+ b.Run(fmt.Sprintf("%d", c), func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: 10000,
+ Concurrency: c,
+ }
+ runNginx(b, clientMachine, serverMachine, hey)
+ })
+ }
+}
+
+// runHttpd runs a single test run.
+func runNginx(b *testing.B, clientMachine, serverMachine harness.Machine, hey *tools.Hey) {
+ b.Helper()
+
+ // Grab a container from the server.
+ ctx := context.Background()
+ server := serverMachine.GetContainer(ctx, b)
+ defer server.CleanUp(ctx)
+
+ port := 80
+ // Start the server.
+ if err := server.Spawn(ctx,
+ dockerutil.RunOpts{
+ Image: "benchmarks/nginx",
+ Ports: []int{port},
+ }); err != nil {
+ b.Fatalf("server failed to start: %v", err)
+ }
+
+ ip, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatalf("failed to find server ip: %v", err)
+ }
+
+ servingPort, err := server.FindPort(ctx, port)
+ if err != nil {
+ b.Fatalf("failed to find server port %d: %v", port, err)
+ }
+
+ // Check the server is serving.
+ harness.WaitUntilServing(ctx, clientMachine, ip, servingPort)
+
+ // Grab a client.
+ client := clientMachine.GetNativeContainer(ctx, b)
+ defer client.CleanUp(ctx)
+
+ b.ResetTimer()
+ server.RestartProfiles()
+ for i := 0; i < b.N; i++ {
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/hey",
+ }, hey.MakeCmd(ip, servingPort)...)
+ if err != nil {
+ b.Fatalf("run failed with: %v", err)
+ }
+ b.StopTimer()
+ hey.Report(b, out)
+ b.StartTimer()
+ }
+}
diff --git a/test/benchmarks/network/node_test.go b/test/benchmarks/network/node_test.go
new file mode 100644
index 000000000..52eb794c4
--- /dev/null
+++ b/test/benchmarks/network/node_test.go
@@ -0,0 +1,127 @@
+// Copyright 2020 The gVisor 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.
+package network
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+// BenchmarkNode runs requests using 'hey' against a Node server run on
+// 'runtime'. The server responds to requests by grabbing some data in a
+// redis instance and returns the data in its reponse. The test loops through
+// increasing amounts of concurency for requests.
+func BenchmarkNode(b *testing.B) {
+ concurrency := []int{1, 5, 10, 25}
+ for _, c := range concurrency {
+ b.Run(fmt.Sprintf("Concurrency%d", c), func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: b.N * c, // Requests b.N requests per thread.
+ Concurrency: c,
+ }
+ runNode(b, hey)
+ })
+ }
+}
+
+// runNode runs the test for a given # of requests and concurrency.
+func runNode(b *testing.B, hey *tools.Hey) {
+ b.Helper()
+
+ // The machine to hold Redis and the Node Server.
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatal("failed to get machine with: %v", err)
+ }
+ defer serverMachine.CleanUp()
+
+ // The machine to run 'hey'.
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatal("failed to get machine with: %v", err)
+ }
+ defer clientMachine.CleanUp()
+
+ ctx := context.Background()
+
+ // Spawn a redis instance for the app to use.
+ redis := serverMachine.GetNativeContainer(ctx, b)
+ if err := redis.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/redis",
+ }); err != nil {
+ b.Fatalf("failed to spwan redis instance: %v", err)
+ }
+ defer redis.CleanUp(ctx)
+
+ if out, err := redis.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil {
+ b.Fatalf("failed to start redis server: %v %s", err, out)
+ }
+ redisIP, err := redis.FindIP(ctx, false)
+ if err != nil {
+ b.Fatalf("failed to get IP from redis instance: %v", err)
+ }
+
+ // Node runs on port 8080.
+ port := 8080
+
+ // Start-up the Node server.
+ nodeApp := serverMachine.GetContainer(ctx, b)
+ if err := nodeApp.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/node",
+ WorkDir: "/usr/src/app",
+ Links: []string{redis.MakeLink("redis")},
+ Ports: []int{port},
+ }, "node", "index.js", redisIP.String()); err != nil {
+ b.Fatalf("failed to spawn node instance: %v", err)
+ }
+ defer nodeApp.CleanUp(ctx)
+
+ servingIP, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatalf("failed to get ip from server: %v", err)
+ }
+
+ servingPort, err := nodeApp.FindPort(ctx, port)
+ if err != nil {
+ b.Fatalf("failed to port from node instance: %v", err)
+ }
+
+ // Wait until the Client sees the server as up.
+ harness.WaitUntilServing(ctx, clientMachine, servingIP, servingPort)
+
+ heyCmd := hey.MakeCmd(servingIP, servingPort)
+
+ nodeApp.RestartProfiles()
+ b.ResetTimer()
+
+ // the client should run on Native.
+ client := clientMachine.GetNativeContainer(ctx, b)
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/hey",
+ }, heyCmd...)
+ if err != nil {
+ b.Fatalf("hey container failed: %v logs: %s", err, out)
+ }
+
+ // Stop the timer to parse the data and report stats.
+ b.StopTimer()
+ hey.Report(b, out)
+ b.StartTimer()
+}
diff --git a/test/benchmarks/network/ruby_test.go b/test/benchmarks/network/ruby_test.go
new file mode 100644
index 000000000..5e0b2b724
--- /dev/null
+++ b/test/benchmarks/network/ruby_test.go
@@ -0,0 +1,134 @@
+// Copyright 2020 The gVisor 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.
+package network
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+// BenchmarkRuby runs requests using 'hey' against a ruby application server.
+// On start, ruby app generates some random data and pushes it to a redis
+// instance. On a request, the app grabs for random entries from the redis
+// server, publishes it to a document, and returns the doc to the request.
+func BenchmarkRuby(b *testing.B) {
+ concurrency := []int{1, 5, 10, 25}
+ for _, c := range concurrency {
+ b.Run(fmt.Sprintf("Concurrency%d", c), func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: b.N * c, // b.N requests per thread.
+ Concurrency: c,
+ }
+ runRuby(b, hey)
+ })
+ }
+}
+
+// runRuby runs the test for a given # of requests and concurrency.
+func runRuby(b *testing.B, hey *tools.Hey) {
+ b.Helper()
+ // The machine to hold Redis and the Ruby Server.
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatal("failed to get machine with: %v", err)
+ }
+ defer serverMachine.CleanUp()
+
+ // The machine to run 'hey'.
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatal("failed to get machine with: %v", err)
+ }
+ defer clientMachine.CleanUp()
+ ctx := context.Background()
+
+ // Spawn a redis instance for the app to use.
+ redis := serverMachine.GetNativeContainer(ctx, b)
+ if err := redis.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/redis",
+ }); err != nil {
+ b.Fatalf("failed to spwan redis instance: %v", err)
+ }
+ defer redis.CleanUp(ctx)
+
+ if out, err := redis.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil {
+ b.Fatalf("failed to start redis server: %v %s", err, out)
+ }
+ redisIP, err := redis.FindIP(ctx, false)
+ if err != nil {
+ b.Fatalf("failed to get IP from redis instance: %v", err)
+ }
+
+ // Ruby runs on port 9292.
+ const port = 9292
+
+ // Start-up the Ruby server.
+ rubyApp := serverMachine.GetContainer(ctx, b)
+ if err := rubyApp.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/ruby",
+ WorkDir: "/app",
+ Links: []string{redis.MakeLink("redis")},
+ Ports: []int{port},
+ Env: []string{
+ fmt.Sprintf("PORT=%d", port),
+ "WEB_CONCURRENCY=20",
+ "WEB_MAX_THREADS=20",
+ "RACK_ENV=production",
+ fmt.Sprintf("HOST=%s", redisIP),
+ },
+ User: "nobody",
+ }, "sh", "-c", "/usr/bin/puma"); err != nil {
+ b.Fatalf("failed to spawn node instance: %v", err)
+ }
+ defer rubyApp.CleanUp(ctx)
+
+ servingIP, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatalf("failed to get ip from server: %v", err)
+ }
+
+ servingPort, err := rubyApp.FindPort(ctx, port)
+ if err != nil {
+ b.Fatalf("failed to port from node instance: %v", err)
+ }
+
+ // Wait until the Client sees the server as up.
+ if err := harness.WaitUntilServing(ctx, clientMachine, servingIP, servingPort); err != nil {
+ b.Fatalf("failed to wait until serving: %v", err)
+ }
+ heyCmd := hey.MakeCmd(servingIP, servingPort)
+ rubyApp.RestartProfiles()
+ b.ResetTimer()
+
+ // the client should run on Native.
+ client := clientMachine.GetNativeContainer(ctx, b)
+ defer client.CleanUp(ctx)
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/hey",
+ }, heyCmd...)
+ if err != nil {
+ b.Fatalf("hey container failed: %v logs: %s", err, out)
+ }
+
+ // Stop the timer to parse the data and report stats.
+ b.StopTimer()
+ hey.Report(b, out)
+ b.StartTimer()
+}