xref: /aosp_15_r20/external/autotest/client/bin/fio_util.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Li
6*9c5db199SXin Li"""Library to run fio scripts.
7*9c5db199SXin Li
8*9c5db199SXin Lifio_runner launch fio and collect results.
9*9c5db199SXin LiThe output dictionary can be add to autotest keyval:
10*9c5db199SXin Li        results = {}
11*9c5db199SXin Li        results.update(fio_util.fio_runner(job_file, env_vars))
12*9c5db199SXin Li        self.write_perf_keyval(results)
13*9c5db199SXin Li
14*9c5db199SXin LiDecoding class can be invoked independently.
15*9c5db199SXin Li
16*9c5db199SXin Li"""
17*9c5db199SXin Li
18*9c5db199SXin Lifrom __future__ import absolute_import
19*9c5db199SXin Lifrom __future__ import division
20*9c5db199SXin Lifrom __future__ import print_function
21*9c5db199SXin Li
22*9c5db199SXin Liimport json
23*9c5db199SXin Liimport logging
24*9c5db199SXin Liimport re
25*9c5db199SXin Li
26*9c5db199SXin Liimport six
27*9c5db199SXin Lifrom six.moves import range
28*9c5db199SXin Li
29*9c5db199SXin Liimport common
30*9c5db199SXin Lifrom autotest_lib.client.bin import utils
31*9c5db199SXin Li
32*9c5db199SXin Li
33*9c5db199SXin Liclass fio_graph_generator():
34*9c5db199SXin Li    """
35*9c5db199SXin Li    Generate graph from fio log that created when specified these options.
36*9c5db199SXin Li    - write_bw_log
37*9c5db199SXin Li    - write_iops_log
38*9c5db199SXin Li    - write_lat_log
39*9c5db199SXin Li
40*9c5db199SXin Li    The following limitations apply
41*9c5db199SXin Li    - Log file name must be in format jobname_testpass
42*9c5db199SXin Li    - Graph is generate using Google graph api -> Internet require to view.
43*9c5db199SXin Li    """
44*9c5db199SXin Li
45*9c5db199SXin Li    html_head = """
46*9c5db199SXin Li<html>
47*9c5db199SXin Li  <head>
48*9c5db199SXin Li    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
49*9c5db199SXin Li    <script type="text/javascript">
50*9c5db199SXin Li      google.load("visualization", "1", {packages:["corechart"]});
51*9c5db199SXin Li      google.setOnLoadCallback(drawChart);
52*9c5db199SXin Li      function drawChart() {
53*9c5db199SXin Li"""
54*9c5db199SXin Li
55*9c5db199SXin Li    html_tail = """
56*9c5db199SXin Li        var chart_div = document.getElementById('chart_div');
57*9c5db199SXin Li        var chart = new google.visualization.ScatterChart(chart_div);
58*9c5db199SXin Li        chart.draw(data, options);
59*9c5db199SXin Li      }
60*9c5db199SXin Li    </script>
61*9c5db199SXin Li  </head>
62*9c5db199SXin Li  <body>
63*9c5db199SXin Li    <div id="chart_div" style="width: 100%; height: 100%;"></div>
64*9c5db199SXin Li  </body>
65*9c5db199SXin Li</html>
66*9c5db199SXin Li"""
67*9c5db199SXin Li
68*9c5db199SXin Li    h_title = { True: 'Percentile', False: 'Time (s)' }
69*9c5db199SXin Li    v_title = { 'bw'  : 'Bandwidth (KB/s)',
70*9c5db199SXin Li                'iops': 'IOPs',
71*9c5db199SXin Li                'lat' : 'Total latency (us)',
72*9c5db199SXin Li                'clat': 'Completion latency (us)',
73*9c5db199SXin Li                'slat': 'Submission latency (us)' }
74*9c5db199SXin Li    graph_title = { 'bw'  : 'bandwidth',
75*9c5db199SXin Li                    'iops': 'IOPs',
76*9c5db199SXin Li                    'lat' : 'total latency',
77*9c5db199SXin Li                    'clat': 'completion latency',
78*9c5db199SXin Li                    'slat': 'submission latency' }
79*9c5db199SXin Li
80*9c5db199SXin Li    test_name = ''
81*9c5db199SXin Li    test_type = ''
82*9c5db199SXin Li    pass_list = ''
83*9c5db199SXin Li
84*9c5db199SXin Li    @classmethod
85*9c5db199SXin Li    def _parse_log_file(cls, file_name, pass_index, pass_count, percentile):
86*9c5db199SXin Li        """
87*9c5db199SXin Li        Generate row for google.visualization.DataTable from one log file.
88*9c5db199SXin Li        Log file is the one that generated using write_{bw,lat,iops}_log
89*9c5db199SXin Li        option in the FIO job file.
90*9c5db199SXin Li
91*9c5db199SXin Li        The fio log file format is  timestamp, value, direction, blocksize
92*9c5db199SXin Li        The output format for each row is { c: list of { v: value} }
93*9c5db199SXin Li
94*9c5db199SXin Li        @param file_name:  log file name to read data from
95*9c5db199SXin Li        @param pass_index: index of current run pass
96*9c5db199SXin Li        @param pass_count: number of all test run passes
97*9c5db199SXin Li        @param percentile: flag to use percentile as key instead of timestamp
98*9c5db199SXin Li
99*9c5db199SXin Li        @return: list of data rows in google.visualization.DataTable format
100*9c5db199SXin Li        """
101*9c5db199SXin Li        # Read data from log
102*9c5db199SXin Li        with open(file_name, 'r') as f:
103*9c5db199SXin Li            data = []
104*9c5db199SXin Li
105*9c5db199SXin Li            for line in f.readlines():
106*9c5db199SXin Li                if not line:
107*9c5db199SXin Li                    break
108*9c5db199SXin Li                t, v, _, _ = [int(x) for x in line.split(', ')]
109*9c5db199SXin Li                data.append([t / 1000.0, v])
110*9c5db199SXin Li
111*9c5db199SXin Li        # Sort & calculate percentile
112*9c5db199SXin Li        if percentile:
113*9c5db199SXin Li            data.sort(key=lambda x: x[1])
114*9c5db199SXin Li            l = len(data)
115*9c5db199SXin Li            for i in range(l):
116*9c5db199SXin Li                data[i][0] = 100 * (i + 0.5) / l
117*9c5db199SXin Li
118*9c5db199SXin Li        # Generate the data row
119*9c5db199SXin Li        all_row = []
120*9c5db199SXin Li        row = [None] * (pass_count + 1)
121*9c5db199SXin Li        for d in data:
122*9c5db199SXin Li            row[0] = {'v' : '%.3f' % d[0]}
123*9c5db199SXin Li            row[pass_index + 1] = {'v': d[1]}
124*9c5db199SXin Li            all_row.append({'c': row[:]})
125*9c5db199SXin Li
126*9c5db199SXin Li        return all_row
127*9c5db199SXin Li
128*9c5db199SXin Li    @classmethod
129*9c5db199SXin Li    def _gen_data_col(cls, pass_list, percentile):
130*9c5db199SXin Li        """
131*9c5db199SXin Li        Generate col for google.visualization.DataTable
132*9c5db199SXin Li
133*9c5db199SXin Li        The output format is list of dict of label and type. In this case,
134*9c5db199SXin Li        type is always number.
135*9c5db199SXin Li
136*9c5db199SXin Li        @param pass_list:  list of test run passes
137*9c5db199SXin Li        @param percentile: flag to use percentile as key instead of timestamp
138*9c5db199SXin Li
139*9c5db199SXin Li        @return: list of column in google.visualization.DataTable format
140*9c5db199SXin Li        """
141*9c5db199SXin Li        if percentile:
142*9c5db199SXin Li            col_name_list = ['percentile'] + [p[0] for p in pass_list]
143*9c5db199SXin Li        else:
144*9c5db199SXin Li            col_name_list = ['time'] + [p[0] for p in pass_list]
145*9c5db199SXin Li
146*9c5db199SXin Li        return [{'label': name, 'type': 'number'} for name in col_name_list]
147*9c5db199SXin Li
148*9c5db199SXin Li    @classmethod
149*9c5db199SXin Li    def _gen_data_row(cls, test_type, pass_list, percentile):
150*9c5db199SXin Li        """
151*9c5db199SXin Li        Generate row for google.visualization.DataTable by generate all log
152*9c5db199SXin Li        file name and call _parse_log_file for each file
153*9c5db199SXin Li
154*9c5db199SXin Li        @param test_type: type of value collected for current test. i.e. IOPs
155*9c5db199SXin Li        @param pass_list: list of run passes for current test
156*9c5db199SXin Li        @param percentile: flag to use percentile as key instead of timestamp
157*9c5db199SXin Li
158*9c5db199SXin Li        @return: list of data rows in google.visualization.DataTable format
159*9c5db199SXin Li        """
160*9c5db199SXin Li        all_row = []
161*9c5db199SXin Li        pass_count = len(pass_list)
162*9c5db199SXin Li        for pass_index, log_file_name in enumerate([p[1] for p in pass_list]):
163*9c5db199SXin Li            all_row.extend(cls._parse_log_file(log_file_name, pass_index,
164*9c5db199SXin Li                                                pass_count, percentile))
165*9c5db199SXin Li        return all_row
166*9c5db199SXin Li
167*9c5db199SXin Li    @classmethod
168*9c5db199SXin Li    def _write_data(cls, f, test_type, pass_list, percentile):
169*9c5db199SXin Li        """
170*9c5db199SXin Li        Write google.visualization.DataTable object to output file.
171*9c5db199SXin Li        https://developers.google.com/chart/interactive/docs/reference
172*9c5db199SXin Li
173*9c5db199SXin Li        @param f: html file to update
174*9c5db199SXin Li        @param test_type: type of value collected for current test. i.e. IOPs
175*9c5db199SXin Li        @param pass_list: list of run passes for current test
176*9c5db199SXin Li        @param percentile: flag to use percentile as key instead of timestamp
177*9c5db199SXin Li        """
178*9c5db199SXin Li        col = cls._gen_data_col(pass_list, percentile)
179*9c5db199SXin Li        row = cls._gen_data_row(test_type, pass_list, percentile)
180*9c5db199SXin Li        data_dict = {'cols' : col, 'rows' : row}
181*9c5db199SXin Li
182*9c5db199SXin Li        f.write('var data = new google.visualization.DataTable(')
183*9c5db199SXin Li        json.dump(data_dict, f)
184*9c5db199SXin Li        f.write(');\n')
185*9c5db199SXin Li
186*9c5db199SXin Li    @classmethod
187*9c5db199SXin Li    def _write_option(cls, f, test_name, test_type, percentile):
188*9c5db199SXin Li        """
189*9c5db199SXin Li        Write option to render scatter graph to output file.
190*9c5db199SXin Li        https://google-developers.appspot.com/chart/interactive/docs/gallery/scatterchart
191*9c5db199SXin Li
192*9c5db199SXin Li        @param test_name: name of current workload. i.e. randwrite
193*9c5db199SXin Li        @param test_type: type of value collected for current test. i.e. IOPs
194*9c5db199SXin Li        @param percentile: flag to use percentile as key instead of timestamp
195*9c5db199SXin Li        """
196*9c5db199SXin Li        option = {'pointSize': 1}
197*9c5db199SXin Li        if percentile:
198*9c5db199SXin Li            option['title'] = ('Percentile graph of %s for %s workload' %
199*9c5db199SXin Li                               (cls.graph_title[test_type], test_name))
200*9c5db199SXin Li        else:
201*9c5db199SXin Li            option['title'] = ('Graph of %s for %s workload over time' %
202*9c5db199SXin Li                               (cls.graph_title[test_type], test_name))
203*9c5db199SXin Li
204*9c5db199SXin Li        option['hAxis'] = {'title': cls.h_title[percentile]}
205*9c5db199SXin Li        option['vAxis'] = {'title': cls.v_title[test_type]}
206*9c5db199SXin Li
207*9c5db199SXin Li        f.write('var options = ')
208*9c5db199SXin Li        json.dump(option, f)
209*9c5db199SXin Li        f.write(';\n')
210*9c5db199SXin Li
211*9c5db199SXin Li    @classmethod
212*9c5db199SXin Li    def _write_graph(cls, test_name, test_type, pass_list, percentile=False):
213*9c5db199SXin Li        """
214*9c5db199SXin Li        Generate graph for test name / test type
215*9c5db199SXin Li
216*9c5db199SXin Li        @param test_name: name of current workload. i.e. randwrite
217*9c5db199SXin Li        @param test_type: type of value collected for current test. i.e. IOPs
218*9c5db199SXin Li        @param pass_list: list of run passes for current test
219*9c5db199SXin Li        @param percentile: flag to use percentile as key instead of timestamp
220*9c5db199SXin Li        """
221*9c5db199SXin Li        logging.info('fio_graph_generator._write_graph %s %s %s',
222*9c5db199SXin Li                     test_name, test_type, str(pass_list))
223*9c5db199SXin Li
224*9c5db199SXin Li
225*9c5db199SXin Li        if percentile:
226*9c5db199SXin Li            out_file_name = '%s_%s_percentile.html' % (test_name, test_type)
227*9c5db199SXin Li        else:
228*9c5db199SXin Li            out_file_name = '%s_%s.html' % (test_name, test_type)
229*9c5db199SXin Li
230*9c5db199SXin Li        with open(out_file_name, 'w') as f:
231*9c5db199SXin Li            f.write(cls.html_head)
232*9c5db199SXin Li            cls._write_data(f, test_type, pass_list, percentile)
233*9c5db199SXin Li            cls._write_option(f, test_name, test_type, percentile)
234*9c5db199SXin Li            f.write(cls.html_tail)
235*9c5db199SXin Li
236*9c5db199SXin Li    def __init__(self, test_name, test_type, pass_list):
237*9c5db199SXin Li        """
238*9c5db199SXin Li        @param test_name: name of current workload. i.e. randwrite
239*9c5db199SXin Li        @param test_type: type of value collected for current test. i.e. IOPs
240*9c5db199SXin Li        @param pass_list: list of run passes for current test
241*9c5db199SXin Li        """
242*9c5db199SXin Li        self.test_name = test_name
243*9c5db199SXin Li        self.test_type = test_type
244*9c5db199SXin Li        self.pass_list = pass_list
245*9c5db199SXin Li
246*9c5db199SXin Li    def run(self):
247*9c5db199SXin Li        """
248*9c5db199SXin Li        Run the graph generator.
249*9c5db199SXin Li        """
250*9c5db199SXin Li        self._write_graph(self.test_name, self.test_type, self.pass_list, False)
251*9c5db199SXin Li        self._write_graph(self.test_name, self.test_type, self.pass_list, True)
252*9c5db199SXin Li
253*9c5db199SXin Li
254*9c5db199SXin Lidef fio_parse_dict(d, prefix):
255*9c5db199SXin Li    """
256*9c5db199SXin Li    Parse fio json dict
257*9c5db199SXin Li
258*9c5db199SXin Li    Recursively flaten json dict to generate autotest perf dict
259*9c5db199SXin Li
260*9c5db199SXin Li    @param d: input dict
261*9c5db199SXin Li    @param prefix: name prefix of the key
262*9c5db199SXin Li    """
263*9c5db199SXin Li
264*9c5db199SXin Li    # No need to parse something that didn't run such as read stat in write job.
265*9c5db199SXin Li    if 'io_bytes' in d and d['io_bytes'] == 0:
266*9c5db199SXin Li        return {}
267*9c5db199SXin Li
268*9c5db199SXin Li    results = {}
269*9c5db199SXin Li    for k, v in d.items():
270*9c5db199SXin Li
271*9c5db199SXin Li        # remove >, >=, <, <=
272*9c5db199SXin Li        for c in '>=<':
273*9c5db199SXin Li            k = k.replace(c, '')
274*9c5db199SXin Li
275*9c5db199SXin Li        key = prefix + '_' + k
276*9c5db199SXin Li
277*9c5db199SXin Li        if type(v) is dict:
278*9c5db199SXin Li            results.update(fio_parse_dict(v, key))
279*9c5db199SXin Li        else:
280*9c5db199SXin Li            results[key] = v
281*9c5db199SXin Li    return results
282*9c5db199SXin Li
283*9c5db199SXin Li
284*9c5db199SXin Lidef fio_parser(lines, prefix=None):
285*9c5db199SXin Li    """
286*9c5db199SXin Li    Parse the json fio output
287*9c5db199SXin Li
288*9c5db199SXin Li    This collects all metrics given by fio and labels them according to unit
289*9c5db199SXin Li    of measurement and test case name.
290*9c5db199SXin Li
291*9c5db199SXin Li    @param lines: text output of json fio output.
292*9c5db199SXin Li    @param prefix: prefix for result keys.
293*9c5db199SXin Li    """
294*9c5db199SXin Li    results = {}
295*9c5db199SXin Li    fio_dict = json.loads(lines)
296*9c5db199SXin Li
297*9c5db199SXin Li    if prefix:
298*9c5db199SXin Li        prefix = prefix + '_'
299*9c5db199SXin Li    else:
300*9c5db199SXin Li        prefix = ''
301*9c5db199SXin Li
302*9c5db199SXin Li    results[prefix + 'fio_version'] = fio_dict['fio version']
303*9c5db199SXin Li
304*9c5db199SXin Li    if 'disk_util' in fio_dict:
305*9c5db199SXin Li        results.update(fio_parse_dict(fio_dict['disk_util'][0],
306*9c5db199SXin Li                                      prefix + 'disk'))
307*9c5db199SXin Li
308*9c5db199SXin Li    for job in fio_dict['jobs']:
309*9c5db199SXin Li        job_prefix = '_' + prefix + job['jobname']
310*9c5db199SXin Li        job.pop('jobname')
311*9c5db199SXin Li
312*9c5db199SXin Li
313*9c5db199SXin Li        for k, v in six.iteritems(job):
314*9c5db199SXin Li            # Igonre "job options", its alphanumerc keys confuses tko.
315*9c5db199SXin Li            # Besides, these keys are redundant.
316*9c5db199SXin Li            if k == 'job options':
317*9c5db199SXin Li                continue
318*9c5db199SXin Li            results.update(fio_parse_dict({k:v}, job_prefix))
319*9c5db199SXin Li
320*9c5db199SXin Li    return results
321*9c5db199SXin Li
322*9c5db199SXin Lidef fio_generate_graph():
323*9c5db199SXin Li    """
324*9c5db199SXin Li    Scan for fio log file in output directory and send data to generate each
325*9c5db199SXin Li    graph to fio_graph_generator class.
326*9c5db199SXin Li    """
327*9c5db199SXin Li    log_types = ['bw', 'iops', 'lat', 'clat', 'slat']
328*9c5db199SXin Li
329*9c5db199SXin Li    # move fio log to result dir
330*9c5db199SXin Li    for log_type in log_types:
331*9c5db199SXin Li        logging.info('log_type %s', log_type)
332*9c5db199SXin Li        logs = utils.system_output('ls *_%s.*log' % log_type, ignore_status=True)
333*9c5db199SXin Li        if not logs:
334*9c5db199SXin Li            continue
335*9c5db199SXin Li
336*9c5db199SXin Li        pattern = r"""(?P<jobname>.*)_                    # jobname
337*9c5db199SXin Li                      ((?P<runpass>p\d+)_|)               # pass
338*9c5db199SXin Li                      (?P<type>bw|iops|lat|clat|slat)     # type
339*9c5db199SXin Li                      (.(?P<thread>\d+)|)                 # thread id for newer fio.
340*9c5db199SXin Li                      .log
341*9c5db199SXin Li                   """
342*9c5db199SXin Li        matcher = re.compile(pattern, re.X)
343*9c5db199SXin Li
344*9c5db199SXin Li        pass_list = []
345*9c5db199SXin Li        current_job = ''
346*9c5db199SXin Li
347*9c5db199SXin Li        for log in logs.split():
348*9c5db199SXin Li            match = matcher.match(log)
349*9c5db199SXin Li            if not match:
350*9c5db199SXin Li                logging.warning('Unknown log file %s', log)
351*9c5db199SXin Li                continue
352*9c5db199SXin Li
353*9c5db199SXin Li            jobname = match.group('jobname')
354*9c5db199SXin Li            runpass = match.group('runpass') or '1'
355*9c5db199SXin Li            if match.group('thread'):
356*9c5db199SXin Li                runpass += '_' +  match.group('thread')
357*9c5db199SXin Li
358*9c5db199SXin Li            # All files for particular job name are group together for create
359*9c5db199SXin Li            # graph that can compare performance between result from each pass.
360*9c5db199SXin Li            if jobname != current_job:
361*9c5db199SXin Li                if pass_list:
362*9c5db199SXin Li                    fio_graph_generator(current_job, log_type, pass_list).run()
363*9c5db199SXin Li                current_job = jobname
364*9c5db199SXin Li                pass_list = []
365*9c5db199SXin Li            pass_list.append((runpass, log))
366*9c5db199SXin Li
367*9c5db199SXin Li        if pass_list:
368*9c5db199SXin Li            fio_graph_generator(current_job, log_type, pass_list).run()
369*9c5db199SXin Li
370*9c5db199SXin Li
371*9c5db199SXin Li        cmd = 'mv *_%s.*log results' % log_type
372*9c5db199SXin Li        utils.run(cmd, ignore_status=True)
373*9c5db199SXin Li        utils.run('mv *.html results', ignore_status=True)
374*9c5db199SXin Li
375*9c5db199SXin Li
376*9c5db199SXin Lidef fio_runner(test, job, env_vars,
377*9c5db199SXin Li               name_prefix=None,
378*9c5db199SXin Li               graph_prefix=None):
379*9c5db199SXin Li    """
380*9c5db199SXin Li    Runs fio.
381*9c5db199SXin Li
382*9c5db199SXin Li    Build a result keyval and performence json.
383*9c5db199SXin Li    The JSON would look like:
384*9c5db199SXin Li    {"description": "<name_prefix>_<modle>_<size>G",
385*9c5db199SXin Li     "graph": "<graph_prefix>_1m_write_wr_lat_99.00_percent_usec",
386*9c5db199SXin Li     "higher_is_better": false, "units": "us", "value": "xxxx"}
387*9c5db199SXin Li    {...
388*9c5db199SXin Li
389*9c5db199SXin Li
390*9c5db199SXin Li    @param test: test to upload perf value
391*9c5db199SXin Li    @param job: fio config file to use
392*9c5db199SXin Li    @param env_vars: environment variable fio will substituete in the fio
393*9c5db199SXin Li        config file.
394*9c5db199SXin Li    @param name_prefix: prefix of the descriptions to use in chrome perfi
395*9c5db199SXin Li        dashboard.
396*9c5db199SXin Li    @param graph_prefix: prefix of the graph name in chrome perf dashboard
397*9c5db199SXin Li        and result keyvals.
398*9c5db199SXin Li    @return fio results.
399*9c5db199SXin Li
400*9c5db199SXin Li    """
401*9c5db199SXin Li
402*9c5db199SXin Li    # running fio with ionice -c 3 so it doesn't lock out other
403*9c5db199SXin Li    # processes from the disk while it is running.
404*9c5db199SXin Li    # If you want to run the fio test for performance purposes,
405*9c5db199SXin Li    # take out the ionice and disable hung process detection:
406*9c5db199SXin Li    # "echo 0 > /proc/sys/kernel/hung_task_timeout_secs"
407*9c5db199SXin Li    # -c 3 = Idle
408*9c5db199SXin Li    # Tried lowest priority for "best effort" but still failed
409*9c5db199SXin Li    ionice = 'ionice -c 3'
410*9c5db199SXin Li    options = ['--output-format=json']
411*9c5db199SXin Li    fio_cmd_line = ' '.join([env_vars, ionice, 'fio',
412*9c5db199SXin Li                             ' '.join(options),
413*9c5db199SXin Li                             '"' + job + '"'])
414*9c5db199SXin Li    fio = utils.run(fio_cmd_line)
415*9c5db199SXin Li
416*9c5db199SXin Li    logging.debug(fio.stdout)
417*9c5db199SXin Li
418*9c5db199SXin Li    fio_generate_graph()
419*9c5db199SXin Li
420*9c5db199SXin Li    filename = re.match('.*FILENAME=(?P<f>[^ ]*)', env_vars).group('f')
421*9c5db199SXin Li    diskname = utils.get_disk_from_filename(filename)
422*9c5db199SXin Li
423*9c5db199SXin Li    if diskname:
424*9c5db199SXin Li        model = utils.get_disk_model(diskname)
425*9c5db199SXin Li        size = utils.get_disk_size_gb(diskname)
426*9c5db199SXin Li        perfdb_name = '%s_%dG' % (model, size)
427*9c5db199SXin Li    else:
428*9c5db199SXin Li        perfdb_name = filename.replace('/', '_')
429*9c5db199SXin Li
430*9c5db199SXin Li    if name_prefix:
431*9c5db199SXin Li        perfdb_name = name_prefix + '_' + perfdb_name
432*9c5db199SXin Li
433*9c5db199SXin Li    result = fio_parser(fio.stdout, prefix=name_prefix)
434*9c5db199SXin Li    if not graph_prefix:
435*9c5db199SXin Li        graph_prefix = ''
436*9c5db199SXin Li
437*9c5db199SXin Li    for k, v in six.iteritems(result):
438*9c5db199SXin Li        # Remove the prefix for value, and replace it the graph prefix.
439*9c5db199SXin Li        if name_prefix:
440*9c5db199SXin Li            k = k.replace('_' + name_prefix, graph_prefix)
441*9c5db199SXin Li
442*9c5db199SXin Li        # Make graph name to be same as the old code.
443*9c5db199SXin Li        if k.endswith('bw'):
444*9c5db199SXin Li            test.output_perf_value(description=perfdb_name, graph=k, value=v,
445*9c5db199SXin Li                                   units='KB_per_sec', higher_is_better=True)
446*9c5db199SXin Li        elif 'clat_percentile_' in k:
447*9c5db199SXin Li            test.output_perf_value(description=perfdb_name, graph=k, value=v,
448*9c5db199SXin Li                                   units='us', higher_is_better=False)
449*9c5db199SXin Li        elif 'clat_ns_percentile_' in k:
450*9c5db199SXin Li            test.output_perf_value(description=perfdb_name, graph=k, value=v,
451*9c5db199SXin Li                                   units='ns', higher_is_better=False)
452*9c5db199SXin Li    return result
453