github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/test/benchmarks/tools/fio.go (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tools
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"strconv"
    21  	"strings"
    22  	"testing"
    23  )
    24  
    25  // Fio makes 'fio' commands and parses their output.
    26  type Fio struct {
    27  	Test      string // test to run: read, write, randread, randwrite.
    28  	Size      int    // total size to be read/written in megabytes.
    29  	BlockSize int    // block size to be read/written in kilobytes.
    30  	IODepth   int    // I/O depth for reads/writes.
    31  }
    32  
    33  // MakeCmd makes a 'fio' command.
    34  func (f *Fio) MakeCmd(filename string) []string {
    35  	cmd := []string{"fio", "--output-format=json", "--ioengine=sync"}
    36  	cmd = append(cmd, fmt.Sprintf("--name=%s", f.Test))
    37  	cmd = append(cmd, fmt.Sprintf("--size=%dM", f.Size))
    38  	cmd = append(cmd, fmt.Sprintf("--blocksize=%dK", f.BlockSize))
    39  	cmd = append(cmd, fmt.Sprintf("--filename=%s", filename))
    40  	cmd = append(cmd, fmt.Sprintf("--iodepth=%d", f.IODepth))
    41  	cmd = append(cmd, fmt.Sprintf("--rw=%s", f.Test))
    42  	return cmd
    43  }
    44  
    45  // Report reports metrics based on output from an 'fio' command.
    46  func (f *Fio) Report(b *testing.B, output string) {
    47  	b.Helper()
    48  	// Parse the output and report the metrics.
    49  	isRead := strings.Contains(f.Test, "read")
    50  	bw, err := f.parseBandwidth(output, isRead)
    51  	if err != nil {
    52  		b.Fatalf("failed to parse bandwidth from %s with: %v", output, err)
    53  	}
    54  	ReportCustomMetric(b, bw, "bandwidth" /*metric name*/, "bytes_per_second" /*unit*/)
    55  
    56  	iops, err := f.parseIOps(output, isRead)
    57  	if err != nil {
    58  		b.Fatalf("failed to parse iops from %s with: %v", output, err)
    59  	}
    60  	ReportCustomMetric(b, iops, "io_ops" /*metric name*/, "ops_per_second" /*unit*/)
    61  }
    62  
    63  // parseBandwidth reports the bandwidth in b/s.
    64  func (f *Fio) parseBandwidth(data string, isRead bool) (float64, error) {
    65  	op := "write"
    66  	if isRead {
    67  		op = "read"
    68  	}
    69  	result, err := f.parseFioJSON(data, op, "bw")
    70  	if err != nil {
    71  		return 0, err
    72  	}
    73  	return result * 1024, nil
    74  }
    75  
    76  // parseIOps reports the write IO per second metric.
    77  func (f *Fio) parseIOps(data string, isRead bool) (float64, error) {
    78  	if isRead {
    79  		return f.parseFioJSON(data, "read", "iops")
    80  	}
    81  	return f.parseFioJSON(data, "write", "iops")
    82  }
    83  
    84  // fioResult is for parsing FioJSON.
    85  type fioResult struct {
    86  	Jobs []fioJob
    87  }
    88  
    89  // fioJob is for parsing FioJSON.
    90  type fioJob map[string]json.RawMessage
    91  
    92  // fioMetrics is for parsing FioJSON.
    93  type fioMetrics map[string]json.RawMessage
    94  
    95  // parseFioJSON parses data and grabs "op" (read or write) and "metric"
    96  // (bw or iops) from the JSON.
    97  func (f *Fio) parseFioJSON(data, op, metric string) (float64, error) {
    98  	var result fioResult
    99  	if err := json.Unmarshal([]byte(data), &result); err != nil {
   100  		return 0, fmt.Errorf("could not unmarshal data: %v", err)
   101  	}
   102  
   103  	if len(result.Jobs) < 1 {
   104  		return 0, fmt.Errorf("no jobs present to parse")
   105  	}
   106  
   107  	var metrics fioMetrics
   108  	if err := json.Unmarshal(result.Jobs[0][op], &metrics); err != nil {
   109  		return 0, fmt.Errorf("could not unmarshal jobs: %v", err)
   110  	}
   111  
   112  	if _, ok := metrics[metric]; !ok {
   113  		return 0, fmt.Errorf("no metric found for op: %s", op)
   114  	}
   115  	return strconv.ParseFloat(string(metrics[metric]), 64)
   116  }