summaryrefslogtreecommitdiffhomepage
path: root/test/benchmarks/fs
diff options
context:
space:
mode:
authorZach Koopmans <zkoopmans@google.com>2020-07-30 21:15:34 -0700
committergVisor bot <gvisor-bot@google.com>2020-07-30 21:17:45 -0700
commit98f9527c04dfc4af242080b5ea29e6da09290098 (patch)
treea4fac06097579a7ce7ecf9a5c4a1756fbfcdf06d /test/benchmarks/fs
parent6a4bcbdb288842633528b10b17950d97d756b2e0 (diff)
Port nginx and move parsers to own package.
This change: - Ports the nginx benchmark. - Switches the Httpd benchmark to use 'hey' as a client. - Moves all parsers to their own package 'tools'. Parsers are moved to their own package because 1) parsing output of a command is often dependent on the format of the command (e.g. 'fio --json'), 2) to enable easier reuse, and 3) clean up and simplify actual running benchmarks (no TestParser functions and ugly sample output in benchmark files). PiperOrigin-RevId: 324144165
Diffstat (limited to 'test/benchmarks/fs')
-rw-r--r--test/benchmarks/fs/BUILD1
-rw-r--r--test/benchmarks/fs/fio_test.go257
2 files changed, 30 insertions, 228 deletions
diff --git a/test/benchmarks/fs/BUILD b/test/benchmarks/fs/BUILD
index 79327b57c..20654d88f 100644
--- a/test/benchmarks/fs/BUILD
+++ b/test/benchmarks/fs/BUILD
@@ -25,6 +25,7 @@ go_test(
deps = [
"//pkg/test/dockerutil",
"//test/benchmarks/harness",
+ "//test/benchmarks/tools",
"@com_github_docker_docker//api/types/mount:go_default_library",
],
)
diff --git a/test/benchmarks/fs/fio_test.go b/test/benchmarks/fs/fio_test.go
index 75d52726a..65874ed8b 100644
--- a/test/benchmarks/fs/fio_test.go
+++ b/test/benchmarks/fs/fio_test.go
@@ -15,72 +15,47 @@ package fs
import (
"context"
- "encoding/json"
"fmt"
"path/filepath"
- "strconv"
"strings"
"testing"
"github.com/docker/docker/api/types/mount"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
)
-type fioTestCase struct {
- test string // test to run: read, write, randread, randwrite.
- size string // total size to be read/written of format N[GMK] (e.g. 5G).
- blocksize string // blocksize to be read/write of format N[GMK] (e.g. 4K).
- iodepth int // iodepth for reads/writes.
- time int // time to run the test in seconds, usually for rand(read/write).
-}
-
-// makeCmdFromTestcase makes a fio command.
-func (f *fioTestCase) makeCmdFromTestcase(filename string) []string {
- cmd := []string{"fio", "--output-format=json", "--ioengine=sync"}
- cmd = append(cmd, fmt.Sprintf("--name=%s", f.test))
- cmd = append(cmd, fmt.Sprintf("--size=%s", f.size))
- cmd = append(cmd, fmt.Sprintf("--blocksize=%s", f.blocksize))
- cmd = append(cmd, fmt.Sprintf("--filename=%s", filename))
- cmd = append(cmd, fmt.Sprintf("--iodepth=%d", f.iodepth))
- cmd = append(cmd, fmt.Sprintf("--rw=%s", f.test))
- if f.time != 0 {
- cmd = append(cmd, "--time_based")
- cmd = append(cmd, fmt.Sprintf("--runtime=%d", f.time))
- }
- return cmd
-}
-
// BenchmarkFio runs fio on the runtime under test. There are 4 basic test
// cases each run on a tmpfs mount and a bind mount. Fio requires root so that
// caches can be dropped.
func BenchmarkFio(b *testing.B) {
- testCases := []fioTestCase{
- fioTestCase{
- test: "write",
- size: "5G",
- blocksize: "1M",
- iodepth: 4,
+ testCases := []tools.Fio{
+ tools.Fio{
+ Test: "write",
+ Size: "5G",
+ Blocksize: "1M",
+ Iodepth: 4,
},
- fioTestCase{
- test: "read",
- size: "5G",
- blocksize: "1M",
- iodepth: 4,
+ tools.Fio{
+ Test: "read",
+ Size: "5G",
+ Blocksize: "1M",
+ Iodepth: 4,
},
- fioTestCase{
- test: "randwrite",
- size: "5G",
- blocksize: "4K",
- iodepth: 4,
- time: 30,
+ tools.Fio{
+ Test: "randwrite",
+ Size: "5G",
+ Blocksize: "4K",
+ Iodepth: 4,
+ Time: 30,
},
- fioTestCase{
- test: "randread",
- size: "5G",
- blocksize: "4K",
- iodepth: 4,
- time: 30,
+ tools.Fio{
+ Test: "randread",
+ Size: "5G",
+ Blocksize: "4K",
+ Iodepth: 4,
+ Time: 30,
},
}
@@ -92,7 +67,7 @@ func BenchmarkFio(b *testing.B) {
for _, fsType := range []mount.Type{mount.TypeBind, mount.TypeTmpfs} {
for _, tc := range testCases {
- testName := strings.Title(tc.test) + strings.Title(string(fsType))
+ testName := strings.Title(tc.Test) + strings.Title(string(fsType))
b.Run(testName, func(b *testing.B) {
ctx := context.Background()
container := machine.GetContainer(ctx, b)
@@ -109,7 +84,6 @@ func BenchmarkFio(b *testing.B) {
b.Fatalf("failed to make mount: %v", err)
}
defer mountCleanup()
- cmd := tc.makeCmdFromTestcase(outfile)
// Start the container with the mount.
if err := container.Spawn(
@@ -127,8 +101,8 @@ func BenchmarkFio(b *testing.B) {
}
// For reads, we need a file to read so make one inside the container.
- if strings.Contains(tc.test, "read") {
- fallocateCmd := fmt.Sprintf("fallocate -l %s %s", tc.size, outfile)
+ if strings.Contains(tc.Test, "read") {
+ fallocateCmd := fmt.Sprintf("fallocate -l %s %s", tc.Size, outfile)
if out, err := container.Exec(ctx, dockerutil.ExecOpts{},
strings.Split(fallocateCmd, " ")...); err != nil {
b.Fatalf("failed to create readable file on mount: %v, %s", err, out)
@@ -139,6 +113,7 @@ func BenchmarkFio(b *testing.B) {
if err := harness.DropCaches(machine); err != nil {
b.Skipf("failed to drop caches with %v. You probably need root.", err)
}
+ cmd := tc.MakeCmd(outfile)
container.RestartProfiles()
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -148,19 +123,7 @@ func BenchmarkFio(b *testing.B) {
b.Fatalf("failed to run cmd %v: %v", cmd, err)
}
b.StopTimer()
- // Parse the output and report the metrics.
- isRead := strings.Contains(tc.test, "read")
- bw, err := parseBandwidth(data, isRead)
- if err != nil {
- b.Fatalf("failed to parse bandwidth from %s with: %v", data, err)
- }
- b.ReportMetric(bw, "bandwidth") // in b/s.
-
- iops, err := parseIOps(data, isRead)
- if err != nil {
- b.Fatalf("failed to parse iops from %s with: %v", data, err)
- }
- b.ReportMetric(iops, "iops")
+ tc.Report(b, data)
// If b.N is used (i.e. we run for an hour), we should drop caches
// after each run.
if err := harness.DropCaches(machine); err != nil {
@@ -205,165 +168,3 @@ func makeMount(machine harness.Machine, mountType mount.Type, target string) (mo
return mount.Mount{}, func() {}, fmt.Errorf("illegal mount time not supported: %v", mountType)
}
}
-
-// parseBandwidth reports the bandwidth in b/s.
-func parseBandwidth(data string, isRead bool) (float64, error) {
- if isRead {
- result, err := parseFioJSON(data, "read", "bw")
- if err != nil {
- return 0, err
- }
- return 1024 * result, nil
- }
- result, err := parseFioJSON(data, "write", "bw")
- if err != nil {
- return 0, err
- }
- return 1024 * result, nil
-}
-
-// parseIOps reports the write IO per second metric.
-func parseIOps(data string, isRead bool) (float64, error) {
- if isRead {
- return parseFioJSON(data, "read", "iops")
- }
- return parseFioJSON(data, "write", "iops")
-}
-
-// fioResult is for parsing FioJSON.
-type fioResult struct {
- Jobs []fioJob
-}
-
-// fioJob is for parsing FioJSON.
-type fioJob map[string]json.RawMessage
-
-// fioMetrics is for parsing FioJSON.
-type fioMetrics map[string]json.RawMessage
-
-// parseFioJSON parses data and grabs "op" (read or write) and "metric"
-// (bw or iops) from the JSON.
-func parseFioJSON(data, op, metric string) (float64, error) {
- var result fioResult
- if err := json.Unmarshal([]byte(data), &result); err != nil {
- return 0, fmt.Errorf("could not unmarshal data: %v", err)
- }
-
- if len(result.Jobs) < 1 {
- return 0, fmt.Errorf("no jobs present to parse")
- }
-
- var metrics fioMetrics
- if err := json.Unmarshal(result.Jobs[0][op], &metrics); err != nil {
- return 0, fmt.Errorf("could not unmarshal jobs: %v", err)
- }
-
- if _, ok := metrics[metric]; !ok {
- return 0, fmt.Errorf("no metric found for op: %s", op)
- }
- return strconv.ParseFloat(string(metrics[metric]), 64)
-}
-
-// TestParsers tests that the parsers work on sampleData.
-func TestParsers(t *testing.T) {
- sampleData := `
-{
- "fio version" : "fio-3.1",
- "timestamp" : 1554837456,
- "timestamp_ms" : 1554837456621,
- "time" : "Tue Apr 9 19:17:36 2019",
- "jobs" : [
- {
- "jobname" : "test",
- "groupid" : 0,
- "error" : 0,
- "eta" : 2147483647,
- "elapsed" : 1,
- "job options" : {
- "name" : "test",
- "ioengine" : "sync",
- "size" : "1073741824",
- "filename" : "/disk/file.dat",
- "iodepth" : "4",
- "bs" : "4096",
- "rw" : "write"
- },
- "read" : {
- "io_bytes" : 0,
- "io_kbytes" : 0,
- "bw" : 123456,
- "iops" : 1234.5678,
- "runtime" : 0,
- "total_ios" : 0,
- "short_ios" : 0,
- "bw_min" : 0,
- "bw_max" : 0,
- "bw_agg" : 0.000000,
- "bw_mean" : 0.000000,
- "bw_dev" : 0.000000,
- "bw_samples" : 0,
- "iops_min" : 0,
- "iops_max" : 0,
- "iops_mean" : 0.000000,
- "iops_stddev" : 0.000000,
- "iops_samples" : 0
- },
- "write" : {
- "io_bytes" : 1073741824,
- "io_kbytes" : 1048576,
- "bw" : 1753471,
- "iops" : 438367.892977,
- "runtime" : 598,
- "total_ios" : 262144,
- "bw_min" : 1731120,
- "bw_max" : 1731120,
- "bw_agg" : 98.725328,
- "bw_mean" : 1731120.000000,
- "bw_dev" : 0.000000,
- "bw_samples" : 1,
- "iops_min" : 432780,
- "iops_max" : 432780,
- "iops_mean" : 432780.000000,
- "iops_stddev" : 0.000000,
- "iops_samples" : 1
- }
- }
- ]
-}
-`
- // WriteBandwidth.
- got, err := parseBandwidth(sampleData, false)
- var want float64 = 1753471.0 * 1024
- if err != nil {
- t.Fatalf("parse failed with err: %v", err)
- } else if got != want {
- t.Fatalf("got: %f, want: %f", got, want)
- }
-
- // ReadBandwidth.
- got, err = parseBandwidth(sampleData, true)
- want = 123456 * 1024
- if err != nil {
- t.Fatalf("parse failed with err: %v", err)
- } else if got != want {
- t.Fatalf("got: %f, want: %f", got, want)
- }
-
- // WriteIOps.
- got, err = parseIOps(sampleData, false)
- want = 438367.892977
- if err != nil {
- t.Fatalf("parse failed with err: %v", err)
- } else if got != want {
- t.Fatalf("got: %f, want: %f", got, want)
- }
-
- // ReadIOps.
- got, err = parseIOps(sampleData, true)
- want = 1234.5678
- if err != nil {
- t.Fatalf("parse failed with err: %v", err)
- } else if got != want {
- t.Fatalf("got: %f, want: %f", got, want)
- }
-}