1fb13bbf2SFam Zheng#!/usr/bin/env python 2fb13bbf2SFam Zheng# 3fb13bbf2SFam Zheng# Tests for IO throttling 4fb13bbf2SFam Zheng# 5fb13bbf2SFam Zheng# Copyright (C) 2015 Red Hat, Inc. 6a90cade0SAlberto Garcia# Copyright (C) 2015-2016 Igalia, S.L. 7fb13bbf2SFam Zheng# 8fb13bbf2SFam Zheng# This program is free software; you can redistribute it and/or modify 9fb13bbf2SFam Zheng# it under the terms of the GNU General Public License as published by 10fb13bbf2SFam Zheng# the Free Software Foundation; either version 2 of the License, or 11fb13bbf2SFam Zheng# (at your option) any later version. 12fb13bbf2SFam Zheng# 13fb13bbf2SFam Zheng# This program is distributed in the hope that it will be useful, 14fb13bbf2SFam Zheng# but WITHOUT ANY WARRANTY; without even the implied warranty of 15fb13bbf2SFam Zheng# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16fb13bbf2SFam Zheng# GNU General Public License for more details. 17fb13bbf2SFam Zheng# 18fb13bbf2SFam Zheng# You should have received a copy of the GNU General Public License 19fb13bbf2SFam Zheng# along with this program. If not, see <http://www.gnu.org/licenses/>. 20fb13bbf2SFam Zheng# 21fb13bbf2SFam Zheng 22fb13bbf2SFam Zhengimport iotests 23fb13bbf2SFam Zheng 24a90cade0SAlberto Garciansec_per_sec = 1000000000 25a90cade0SAlberto Garcia 26fb13bbf2SFam Zhengclass ThrottleTestCase(iotests.QMPTestCase): 27fb13bbf2SFam Zheng test_img = "null-aio://" 282db33f88SAlberto Garcia max_drives = 3 29fb13bbf2SFam Zheng 30fb13bbf2SFam Zheng def blockstats(self, device): 31fb13bbf2SFam Zheng result = self.vm.qmp("query-blockstats") 32fb13bbf2SFam Zheng for r in result['return']: 33fb13bbf2SFam Zheng if r['device'] == device: 34fb13bbf2SFam Zheng stat = r['stats'] 35fb13bbf2SFam Zheng return stat['rd_bytes'], stat['rd_operations'], stat['wr_bytes'], stat['wr_operations'] 36fb13bbf2SFam Zheng raise Exception("Device not found for blockstats: %s" % device) 37fb13bbf2SFam Zheng 38fb13bbf2SFam Zheng def setUp(self): 392db33f88SAlberto Garcia self.vm = iotests.VM() 402db33f88SAlberto Garcia for i in range(0, self.max_drives): 412db33f88SAlberto Garcia self.vm.add_drive(self.test_img) 42fb13bbf2SFam Zheng self.vm.launch() 43fb13bbf2SFam Zheng 44fb13bbf2SFam Zheng def tearDown(self): 45fb13bbf2SFam Zheng self.vm.shutdown() 46fb13bbf2SFam Zheng 47a90cade0SAlberto Garcia def configure_throttle(self, ndrives, params): 482db33f88SAlberto Garcia params['group'] = 'test' 49fb13bbf2SFam Zheng 502db33f88SAlberto Garcia # Set the I/O throttling parameters to all drives 512db33f88SAlberto Garcia for i in range(0, ndrives): 522db33f88SAlberto Garcia params['device'] = 'drive%d' % i 53fb13bbf2SFam Zheng result = self.vm.qmp("block_set_io_throttle", conv_keys=False, **params) 54fb13bbf2SFam Zheng self.assert_qmp(result, 'return', {}) 55fb13bbf2SFam Zheng 56a90cade0SAlberto Garcia def do_test_throttle(self, ndrives, seconds, params): 57a90cade0SAlberto Garcia def check_limit(limit, num): 58a90cade0SAlberto Garcia # IO throttling algorithm is discrete, allow 10% error so the test 59a90cade0SAlberto Garcia # is more robust 60a90cade0SAlberto Garcia return limit == 0 or \ 61a90cade0SAlberto Garcia (num < seconds * limit * 1.1 / ndrives 62a90cade0SAlberto Garcia and num > seconds * limit * 0.9 / ndrives) 63a90cade0SAlberto Garcia 64fb13bbf2SFam Zheng # Set vm clock to a known value 65fb13bbf2SFam Zheng ns = seconds * nsec_per_sec 66fb13bbf2SFam Zheng self.vm.qtest("clock_step %d" % ns) 67fb13bbf2SFam Zheng 68a90cade0SAlberto Garcia # Submit enough requests so the throttling mechanism kicks 69a90cade0SAlberto Garcia # in. The throttled requests won't be executed until we 70a90cade0SAlberto Garcia # advance the virtual clock. 71fb13bbf2SFam Zheng rq_size = 512 72fb13bbf2SFam Zheng rd_nr = max(params['bps'] / rq_size / 2, 73fb13bbf2SFam Zheng params['bps_rd'] / rq_size, 74fb13bbf2SFam Zheng params['iops'] / 2, 75fb13bbf2SFam Zheng params['iops_rd']) 76fb13bbf2SFam Zheng rd_nr *= seconds * 2 772db33f88SAlberto Garcia rd_nr /= ndrives 78fb13bbf2SFam Zheng wr_nr = max(params['bps'] / rq_size / 2, 79fb13bbf2SFam Zheng params['bps_wr'] / rq_size, 80fb13bbf2SFam Zheng params['iops'] / 2, 81fb13bbf2SFam Zheng params['iops_wr']) 82fb13bbf2SFam Zheng wr_nr *= seconds * 2 832db33f88SAlberto Garcia wr_nr /= ndrives 84fb13bbf2SFam Zheng 852db33f88SAlberto Garcia # Send I/O requests to all drives 862db33f88SAlberto Garcia for i in range(rd_nr): 872db33f88SAlberto Garcia for drive in range(0, ndrives): 882db33f88SAlberto Garcia self.vm.hmp_qemu_io("drive%d" % drive, "aio_read %d %d" % 892db33f88SAlberto Garcia (i * rq_size, rq_size)) 902db33f88SAlberto Garcia 912db33f88SAlberto Garcia for i in range(wr_nr): 922db33f88SAlberto Garcia for drive in range(0, ndrives): 932db33f88SAlberto Garcia self.vm.hmp_qemu_io("drive%d" % drive, "aio_write %d %d" % 942db33f88SAlberto Garcia (i * rq_size, rq_size)) 952db33f88SAlberto Garcia 962db33f88SAlberto Garcia # We'll store the I/O stats for each drive in these arrays 972db33f88SAlberto Garcia start_rd_bytes = [0] * ndrives 982db33f88SAlberto Garcia start_rd_iops = [0] * ndrives 992db33f88SAlberto Garcia start_wr_bytes = [0] * ndrives 1002db33f88SAlberto Garcia start_wr_iops = [0] * ndrives 1012db33f88SAlberto Garcia end_rd_bytes = [0] * ndrives 1022db33f88SAlberto Garcia end_rd_iops = [0] * ndrives 1032db33f88SAlberto Garcia end_wr_bytes = [0] * ndrives 1042db33f88SAlberto Garcia end_wr_iops = [0] * ndrives 1052db33f88SAlberto Garcia 1062db33f88SAlberto Garcia # Read the stats before advancing the clock 1072db33f88SAlberto Garcia for i in range(0, ndrives): 1082db33f88SAlberto Garcia start_rd_bytes[i], start_rd_iops[i], start_wr_bytes[i], \ 1092db33f88SAlberto Garcia start_wr_iops[i] = self.blockstats('drive%d' % i) 110fb13bbf2SFam Zheng 111fb13bbf2SFam Zheng self.vm.qtest("clock_step %d" % ns) 112fb13bbf2SFam Zheng 1132db33f88SAlberto Garcia # Read the stats after advancing the clock 1142db33f88SAlberto Garcia for i in range(0, ndrives): 1152db33f88SAlberto Garcia end_rd_bytes[i], end_rd_iops[i], end_wr_bytes[i], \ 1162db33f88SAlberto Garcia end_wr_iops[i] = self.blockstats('drive%d' % i) 1172db33f88SAlberto Garcia 1182db33f88SAlberto Garcia # Check that the I/O is within the limits and evenly distributed 1192db33f88SAlberto Garcia for i in range(0, ndrives): 1202db33f88SAlberto Garcia rd_bytes = end_rd_bytes[i] - start_rd_bytes[i] 1212db33f88SAlberto Garcia rd_iops = end_rd_iops[i] - start_rd_iops[i] 1222db33f88SAlberto Garcia wr_bytes = end_wr_bytes[i] - start_wr_bytes[i] 1232db33f88SAlberto Garcia wr_iops = end_wr_iops[i] - start_wr_iops[i] 124fb13bbf2SFam Zheng 125fb13bbf2SFam Zheng self.assertTrue(check_limit(params['bps'], rd_bytes + wr_bytes)) 126fb13bbf2SFam Zheng self.assertTrue(check_limit(params['bps_rd'], rd_bytes)) 127fb13bbf2SFam Zheng self.assertTrue(check_limit(params['bps_wr'], wr_bytes)) 128fb13bbf2SFam Zheng self.assertTrue(check_limit(params['iops'], rd_iops + wr_iops)) 129fb13bbf2SFam Zheng self.assertTrue(check_limit(params['iops_rd'], rd_iops)) 130fb13bbf2SFam Zheng self.assertTrue(check_limit(params['iops_wr'], wr_iops)) 131fb13bbf2SFam Zheng 132fb13bbf2SFam Zheng def test_all(self): 133fb13bbf2SFam Zheng params = {"bps": 4096, 134fb13bbf2SFam Zheng "bps_rd": 4096, 135fb13bbf2SFam Zheng "bps_wr": 4096, 136fb13bbf2SFam Zheng "iops": 10, 137fb13bbf2SFam Zheng "iops_rd": 10, 138fb13bbf2SFam Zheng "iops_wr": 10, 139fb13bbf2SFam Zheng } 1402db33f88SAlberto Garcia # Repeat the test with different numbers of drives 1412db33f88SAlberto Garcia for ndrives in range(1, self.max_drives + 1): 142fb13bbf2SFam Zheng # Pick each out of all possible params and test 143fb13bbf2SFam Zheng for tk in params: 144fb13bbf2SFam Zheng limits = dict([(k, 0) for k in params]) 1452db33f88SAlberto Garcia limits[tk] = params[tk] * ndrives 146a90cade0SAlberto Garcia self.configure_throttle(ndrives, limits) 147a90cade0SAlberto Garcia self.do_test_throttle(ndrives, 5, limits) 148a90cade0SAlberto Garcia 149a90cade0SAlberto Garcia def test_burst(self): 150a90cade0SAlberto Garcia params = {"bps": 4096, 151a90cade0SAlberto Garcia "bps_rd": 4096, 152a90cade0SAlberto Garcia "bps_wr": 4096, 153a90cade0SAlberto Garcia "iops": 10, 154a90cade0SAlberto Garcia "iops_rd": 10, 155a90cade0SAlberto Garcia "iops_wr": 10, 156a90cade0SAlberto Garcia } 157a90cade0SAlberto Garcia ndrives = 1 158a90cade0SAlberto Garcia # Pick each out of all possible params and test 159a90cade0SAlberto Garcia for tk in params: 160a90cade0SAlberto Garcia rate = params[tk] * ndrives 161a90cade0SAlberto Garcia burst_rate = rate * 7 162a90cade0SAlberto Garcia burst_length = 4 163a90cade0SAlberto Garcia 164a90cade0SAlberto Garcia # Configure the throttling settings 165a90cade0SAlberto Garcia settings = dict([(k, 0) for k in params]) 166a90cade0SAlberto Garcia settings[tk] = rate 167a90cade0SAlberto Garcia settings['%s_max' % tk] = burst_rate 168a90cade0SAlberto Garcia settings['%s_max_length' % tk] = burst_length 169a90cade0SAlberto Garcia self.configure_throttle(ndrives, settings) 170a90cade0SAlberto Garcia 171a90cade0SAlberto Garcia # Wait for the bucket to empty so we can do bursts 172a90cade0SAlberto Garcia wait_ns = nsec_per_sec * burst_length * burst_rate / rate 173a90cade0SAlberto Garcia self.vm.qtest("clock_step %d" % wait_ns) 174a90cade0SAlberto Garcia 175a90cade0SAlberto Garcia # Test I/O at the max burst rate 176a90cade0SAlberto Garcia limits = dict([(k, 0) for k in params]) 177a90cade0SAlberto Garcia limits[tk] = burst_rate 178a90cade0SAlberto Garcia self.do_test_throttle(ndrives, burst_length, limits) 179a90cade0SAlberto Garcia 180a90cade0SAlberto Garcia # Now test I/O at the normal rate 181a90cade0SAlberto Garcia limits[tk] = rate 1822db33f88SAlberto Garcia self.do_test_throttle(ndrives, 5, limits) 183fb13bbf2SFam Zheng 184fb13bbf2SFam Zhengclass ThrottleTestCoroutine(ThrottleTestCase): 185fb13bbf2SFam Zheng test_img = "null-co://" 186fb13bbf2SFam Zheng 187*435d5ee6SAlberto Garciaclass ThrottleTestGroupNames(iotests.QMPTestCase): 188*435d5ee6SAlberto Garcia test_img = "null-aio://" 189*435d5ee6SAlberto Garcia max_drives = 3 190*435d5ee6SAlberto Garcia 191*435d5ee6SAlberto Garcia def setUp(self): 192*435d5ee6SAlberto Garcia self.vm = iotests.VM() 193*435d5ee6SAlberto Garcia for i in range(0, self.max_drives): 194*435d5ee6SAlberto Garcia self.vm.add_drive(self.test_img, "throttling.iops-total=100") 195*435d5ee6SAlberto Garcia self.vm.launch() 196*435d5ee6SAlberto Garcia 197*435d5ee6SAlberto Garcia def tearDown(self): 198*435d5ee6SAlberto Garcia self.vm.shutdown() 199*435d5ee6SAlberto Garcia 200*435d5ee6SAlberto Garcia def set_io_throttle(self, device, params): 201*435d5ee6SAlberto Garcia params["device"] = device 202*435d5ee6SAlberto Garcia result = self.vm.qmp("block_set_io_throttle", conv_keys=False, **params) 203*435d5ee6SAlberto Garcia self.assert_qmp(result, 'return', {}) 204*435d5ee6SAlberto Garcia 205*435d5ee6SAlberto Garcia def verify_name(self, device, name): 206*435d5ee6SAlberto Garcia result = self.vm.qmp("query-block") 207*435d5ee6SAlberto Garcia for r in result["return"]: 208*435d5ee6SAlberto Garcia if r["device"] == device: 209*435d5ee6SAlberto Garcia info = r["inserted"] 210*435d5ee6SAlberto Garcia if name: 211*435d5ee6SAlberto Garcia self.assertEqual(info["group"], name) 212*435d5ee6SAlberto Garcia else: 213*435d5ee6SAlberto Garcia self.assertFalse(info.has_key('group')) 214*435d5ee6SAlberto Garcia return 215*435d5ee6SAlberto Garcia 216*435d5ee6SAlberto Garcia raise Exception("No group information found for '%s'" % device) 217*435d5ee6SAlberto Garcia 218*435d5ee6SAlberto Garcia def test_group_naming(self): 219*435d5ee6SAlberto Garcia params = {"bps": 0, 220*435d5ee6SAlberto Garcia "bps_rd": 0, 221*435d5ee6SAlberto Garcia "bps_wr": 0, 222*435d5ee6SAlberto Garcia "iops": 0, 223*435d5ee6SAlberto Garcia "iops_rd": 0, 224*435d5ee6SAlberto Garcia "iops_wr": 0} 225*435d5ee6SAlberto Garcia 226*435d5ee6SAlberto Garcia # Check the drives added using the command line. 227*435d5ee6SAlberto Garcia # The default throttling group name is the device name. 228*435d5ee6SAlberto Garcia for i in range(self.max_drives): 229*435d5ee6SAlberto Garcia devname = "drive%d" % i 230*435d5ee6SAlberto Garcia self.verify_name(devname, devname) 231*435d5ee6SAlberto Garcia 232*435d5ee6SAlberto Garcia # Clear throttling settings => the group name is gone. 233*435d5ee6SAlberto Garcia for i in range(self.max_drives): 234*435d5ee6SAlberto Garcia devname = "drive%d" % i 235*435d5ee6SAlberto Garcia self.set_io_throttle(devname, params) 236*435d5ee6SAlberto Garcia self.verify_name(devname, None) 237*435d5ee6SAlberto Garcia 238*435d5ee6SAlberto Garcia # Set throttling settings using block_set_io_throttle and 239*435d5ee6SAlberto Garcia # check the default group names. 240*435d5ee6SAlberto Garcia params["iops"] = 10 241*435d5ee6SAlberto Garcia for i in range(self.max_drives): 242*435d5ee6SAlberto Garcia devname = "drive%d" % i 243*435d5ee6SAlberto Garcia self.set_io_throttle(devname, params) 244*435d5ee6SAlberto Garcia self.verify_name(devname, devname) 245*435d5ee6SAlberto Garcia 246*435d5ee6SAlberto Garcia # Set a custom group name for each device 247*435d5ee6SAlberto Garcia for i in range(3): 248*435d5ee6SAlberto Garcia devname = "drive%d" % i 249*435d5ee6SAlberto Garcia groupname = "group%d" % i 250*435d5ee6SAlberto Garcia params['group'] = groupname 251*435d5ee6SAlberto Garcia self.set_io_throttle(devname, params) 252*435d5ee6SAlberto Garcia self.verify_name(devname, groupname) 253*435d5ee6SAlberto Garcia 254*435d5ee6SAlberto Garcia # Put drive0 in group1 and check that all other devices remain 255*435d5ee6SAlberto Garcia # unchanged 256*435d5ee6SAlberto Garcia params['group'] = 'group1' 257*435d5ee6SAlberto Garcia self.set_io_throttle('drive0', params) 258*435d5ee6SAlberto Garcia self.verify_name('drive0', 'group1') 259*435d5ee6SAlberto Garcia for i in range(1, self.max_drives): 260*435d5ee6SAlberto Garcia devname = "drive%d" % i 261*435d5ee6SAlberto Garcia groupname = "group%d" % i 262*435d5ee6SAlberto Garcia self.verify_name(devname, groupname) 263*435d5ee6SAlberto Garcia 264*435d5ee6SAlberto Garcia # Put drive0 in group2 and check that all other devices remain 265*435d5ee6SAlberto Garcia # unchanged 266*435d5ee6SAlberto Garcia params['group'] = 'group2' 267*435d5ee6SAlberto Garcia self.set_io_throttle('drive0', params) 268*435d5ee6SAlberto Garcia self.verify_name('drive0', 'group2') 269*435d5ee6SAlberto Garcia for i in range(1, self.max_drives): 270*435d5ee6SAlberto Garcia devname = "drive%d" % i 271*435d5ee6SAlberto Garcia groupname = "group%d" % i 272*435d5ee6SAlberto Garcia self.verify_name(devname, groupname) 273*435d5ee6SAlberto Garcia 274*435d5ee6SAlberto Garcia # Clear throttling settings from drive0 check that all other 275*435d5ee6SAlberto Garcia # devices remain unchanged 276*435d5ee6SAlberto Garcia params["iops"] = 0 277*435d5ee6SAlberto Garcia self.set_io_throttle('drive0', params) 278*435d5ee6SAlberto Garcia self.verify_name('drive0', None) 279*435d5ee6SAlberto Garcia for i in range(1, self.max_drives): 280*435d5ee6SAlberto Garcia devname = "drive%d" % i 281*435d5ee6SAlberto Garcia groupname = "group%d" % i 282*435d5ee6SAlberto Garcia self.verify_name(devname, groupname) 283*435d5ee6SAlberto Garcia 284*435d5ee6SAlberto Garcia 285fb13bbf2SFam Zhengif __name__ == '__main__': 286fb13bbf2SFam Zheng iotests.main(supported_fmts=["raw"]) 287