aboutsummaryrefslogtreecommitdiffstats
path: root/yardstick/benchmark/scenarios/storage/fio.py
blob: c57c6edf2eef4a99c545a3d73626eedbe7e139c3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
##############################################################################
# Copyright (c) 2015 Huawei Technologies Co.,Ltd.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
from __future__ import absolute_import
from __future__ import print_function

import logging

import pkg_resources
from oslo_serialization import jsonutils

import yardstick.ssh as ssh
from yardstick.benchmark.scenarios import base

LOG = logging.getLogger(__name__)


class Fio(base.Scenario):
    """Execute fio benchmark in a host

  Parameters
    filename - file name for fio workload
        type:    string
        unit:    na
        default: /home/ubuntu/data.raw
    job_file - fio job configuration file
        type:    string
        unit:    na
        default: None
    job_file_config - content of job configuration file
        type:    list
        unit:    na
        default: None
    directory - mount directoey for test volume
        type:    string
        unit:    na
        default: None
    bs - block size used for the io units
        type:    int
        unit:    bytes
        default: 4k
    iodepth - number of iobuffers to keep in flight
        type:    int
        unit:    na
        default: 1
    rw - type of io pattern [read, write, randwrite, randread, rw, randrw]
        type:    string
        unit:    na
        default: write
    rwmixwrite - percentage of a mixed workload that should be writes
        type: int
        unit: percentage
        default: 50
    ramp_time - run time before logging any performance
        type:    int
        unit:    seconds
        default: 20
    direct - whether use non-buffered I/O or not
        type:    boolean
        unit:    na
        default: 1
    size - total size of I/O for this job.
        type:    string
        unit:    na
        default: 1g
    numjobs - number of clones (processes/threads performing the same workload) of this job
        type:    int
        unit:    na
        default: 1

    Read link below for more fio args description:
        http://www.bluestop.org/fio/HOWTO.txt
    """
    __scenario_type__ = "Fio"

    TARGET_SCRIPT = "fio_benchmark.bash"

    def __init__(self, scenario_cfg, context_cfg):
        self.scenario_cfg = scenario_cfg
        self.context_cfg = context_cfg
        self.options = self.scenario_cfg["options"]
        self.setup_done = False

    def setup(self):
        """scenario setup"""
        host = self.context_cfg["host"]

        self.client = ssh.SSH.from_node(host, defaults={"user": "root"})
        self.client.wait(timeout=600)

        self.job_file = self.options.get("job_file", None)
        config_lines = self.options.get("job_file_config", None)

        if self.job_file:
            self.job_file_script = pkg_resources.resource_filename(
                "yardstick.resources", 'files/' + self.job_file)

            # copy job file to host
            self.client._put_file_shell(self.job_file_script, '~/job_file.ini')
        elif config_lines:
            LOG.debug("Job file configuration received, Fio job file will be created.")
            self.job_file = 'tmp_job_file.ini'
            self.job_file_script = pkg_resources.resource_filename(
                "yardstick.resources", 'files/' + self.job_file)
            with open(self.job_file_script, 'w') as f:
                f.write('\n'.join(str(line) for line in config_lines))

            # copy job file to host
            self.client._put_file_shell(self.job_file_script, '~/job_file.ini')
        else:
            LOG.debug("No job file configuration received, Fio will use parameters.")
            self.target_script = pkg_resources.resource_filename(
                "yardstick.benchmark.scenarios.storage", Fio.TARGET_SCRIPT)

            # copy script to host
            self.client._put_file_shell(self.target_script, '~/fio.sh')

        mount_dir = self.options.get("directory", None)

        if mount_dir:
            LOG.debug("Formating volume...")
            _, stdout, _ = self.client.execute(
                "lsblk -dps | grep -m 1 disk | awk '{print $1}'")
            block_device = stdout.strip()
            if block_device:
                 self.client.execute("sudo mkfs.ext4 %s" % block_device)
                 cmd = "sudo mkdir %s" % mount_dir
                 self.client.execute(cmd)
                 LOG.debug("Mounting volume at: %s", mount_dir)
                 cmd = "sudo mount %s %s" % (block_device, mount_dir)
                 self.client.execute(cmd)

        self.setup_done = True

    def run(self, result):
        """execute the benchmark"""
        default_args = "-ioengine=libaio -group_reporting -time_based -time_based " \
            "--output-format=json"
        timeout = 3600

        if not self.setup_done:
            self.setup()

        if self.job_file:
            cmd = "sudo fio job_file.ini --output-format=json"
        else:
            filename = self.options.get("filename", "/home/ubuntu/data.raw")
            bs = self.options.get("bs", "4k")
            iodepth = self.options.get("iodepth", "1")
            rw = self.options.get("rw", "write")
            ramp_time = self.options.get("ramp_time", 20)
            size = self.options.get("size", "1g")
            direct = self.options.get("direct", "1")
            numjobs = self.options.get("numjobs", "1")
            rwmixwrite = self.options.get("rwmixwrite", 50)
            name = "yardstick-fio"
            # if run by a duration runner
            duration_time = self.scenario_cfg["runner"].get("duration", None) \
                if "runner" in self.scenario_cfg else None
            # if run by an arithmetic runner
            arithmetic_time = self.options.get("duration", None)
            if duration_time:
                runtime = duration_time
            elif arithmetic_time:
                runtime = arithmetic_time
            else:
                runtime = 30
            # Set timeout, so that the cmd execution does not exit incorrectly
            # when the test run time is last long
            timeout = int(ramp_time) + int(runtime) + 600

            cmd_args = "-filename=%s -direct=%s -bs=%s -iodepth=%s -rw=%s -rwmixwrite=%s " \
                       "-size=%s -ramp_time=%s -numjobs=%s -runtime=%s -name=%s %s" \
                       % (filename, direct, bs, iodepth, rw, rwmixwrite, size, ramp_time, numjobs,
                          runtime, name, default_args)
            cmd = "sudo bash fio.sh %s %s" % (filename, cmd_args)

        LOG.debug("Executing command: %s", cmd)
        status, stdout, stderr = self.client.execute(cmd, timeout=timeout)
        if status:
            raise RuntimeError(stderr)

        raw_data = jsonutils.loads(stdout)

        if self.job_file:
            result["read_bw"] = raw_data["jobs"][0]["read"]["bw"]
            result["read_iops"] = raw_data["jobs"][0]["read"]["iops"]
            result["read_lat"] = raw_data["jobs"][0]["read"]["lat"]["mean"]
            result["write_bw"] = raw_data["jobs"][0]["write"]["bw"]
            result["write_iops"] = raw_data["jobs"][0]["write"]["iops"]
            result["write_lat"] = raw_data["jobs"][0]["write"]["lat"]["mean"]
        else:
            # The bandwidth unit is KB/s, and latency unit is us
            if rw in ["read", "randread", "rw", "randrw"]:
                result["read_bw"] = raw_data["jobs"][0]["read"]["bw"]
                result["read_iops"] = raw_data["jobs"][0]["read"]["iops"]
                result["read_lat"] = raw_data["jobs"][0]["read"]["lat"]["mean"]
            if rw in ["write", "randwrite", "rw", "randrw"]:
                result["write_bw"] = raw_data["jobs"][0]["write"]["bw"]
                result["write_iops"] = raw_data["jobs"][0]["write"]["iops"]
                result["write_lat"] = raw_data["jobs"][0]["write"]["lat"]["mean"]

        if "sla" in self.scenario_cfg:
            sla_error = ""
            for k, v in result.items():
                if k not in self.scenario_cfg['sla']:
                    continue

                if "lat" in k:
                    # For lattency small value is better
                    max_v = float(self.scenario_cfg['sla'][k])
                    if v > max_v:
                        sla_error += "%s %f > sla:%s(%f); " % (k, v, k, max_v)
                else:
                    # For bandwidth and iops big value is better
                    min_v = int(self.scenario_cfg['sla'][k])
                    if v < min_v:
                        sla_error += "%s %d < " \
                            "sla:%s(%d); " % (k, v, k, min_v)

            self.verify_SLA(sla_error == "", sla_error)


def _test():
    """internal test function"""
    key_filename = pkg_resources.resource_filename("yardstick.resources",
                                                   "files/yardstick_key")
    ctx = {
        "host": {
            "ip": "10.229.47.137",
            "user": "root",
            "key_filename": key_filename
        }
    }

    logger = logging.getLogger("yardstick")
    logger.setLevel(logging.DEBUG)

    options = {
        "filename": "/home/ubuntu/data.raw",
        "bs": "4k",
        "iodepth": "1",
        "rw": "rw",
        "ramp_time": 1,
        "duration": 10
    }
    result = {}
    args = {"options": options}

    fio = Fio(args, ctx)
    fio.run(result)
    print(result)


if __name__ == '__main__':
    _test()