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