xref: /qemu/tests/qemu-iotests/136 (revision d656ec5e)
1#!/usr/bin/env python
2#
3# Tests for block device statistics
4#
5# Copyright (C) 2015 Igalia, S.L.
6# Author: Alberto Garcia <berto@igalia.com>
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20#
21
22import iotests
23import os
24
25interval_length = 10
26nsec_per_sec = 1000000000
27op_latency = nsec_per_sec / 1000 # See qtest_latency_ns in accounting.c
28bad_sector = 8192
29bad_offset = bad_sector * 512
30blkdebug_file = os.path.join(iotests.test_dir, 'blkdebug.conf')
31
32class BlockDeviceStatsTestCase(iotests.QMPTestCase):
33    test_img = "null-aio://"
34    total_rd_bytes = 0
35    total_rd_ops = 0
36    total_wr_bytes = 0
37    total_wr_ops = 0
38    total_wr_merged = 0
39    total_flush_ops = 0
40    failed_rd_ops = 0
41    failed_wr_ops = 0
42    invalid_rd_ops = 0
43    invalid_wr_ops = 0
44    wr_highest_offset = 0
45    account_invalid = False
46    account_failed = False
47
48    def blockstats(self, device):
49        result = self.vm.qmp("query-blockstats")
50        for r in result['return']:
51            if r['device'] == device:
52                return r['stats']
53        raise Exception("Device not found for blockstats: %s" % device)
54
55    def create_blkdebug_file(self):
56        file = open(blkdebug_file, 'w')
57        file.write('''
58[inject-error]
59event = "read_aio"
60errno = "5"
61sector = "%d"
62
63[inject-error]
64event = "write_aio"
65errno = "5"
66sector = "%d"
67''' % (bad_sector, bad_sector))
68        file.close()
69
70    def setUp(self):
71        drive_args = []
72        drive_args.append("stats-intervals.0=%d" % interval_length)
73        drive_args.append("stats-account-invalid=%s" %
74                          (self.account_invalid and "on" or "off"))
75        drive_args.append("stats-account-failed=%s" %
76                          (self.account_failed and "on" or "off"))
77        self.create_blkdebug_file()
78        self.vm = iotests.VM().add_drive('blkdebug:%s:%s ' %
79                                         (blkdebug_file, self.test_img),
80                                         ','.join(drive_args))
81        self.vm.launch()
82        # Set an initial value for the clock
83        self.vm.qtest("clock_step %d" % nsec_per_sec)
84
85    def tearDown(self):
86        self.vm.shutdown()
87        os.remove(blkdebug_file)
88
89    def accounted_ops(self, read = False, write = False, flush = False):
90        ops = 0
91        if write:
92            ops += self.total_wr_ops
93            if self.account_failed:
94                ops += self.failed_wr_ops
95            if self.account_invalid:
96                ops += self.invalid_wr_ops
97        if read:
98            ops += self.total_rd_ops
99            if self.account_failed:
100                ops += self.failed_rd_ops
101            if self.account_invalid:
102                ops += self.invalid_rd_ops
103        if flush:
104            ops += self.total_flush_ops
105        return ops
106
107    def accounted_latency(self, read = False, write = False, flush = False):
108        latency = 0
109        if write:
110            latency += self.total_wr_ops * op_latency
111            if self.account_failed:
112                latency += self.failed_wr_ops * op_latency
113        if read:
114            latency += self.total_rd_ops * op_latency
115            if self.account_failed:
116                latency += self.failed_rd_ops * op_latency
117        if flush:
118            latency += self.total_flush_ops * op_latency
119        return latency
120
121    def check_values(self):
122        stats = self.blockstats('drive0')
123
124        # Check that the totals match with what we have calculated
125        self.assertEqual(self.total_rd_bytes, stats['rd_bytes'])
126        self.assertEqual(self.total_wr_bytes, stats['wr_bytes'])
127        self.assertEqual(self.total_rd_ops, stats['rd_operations'])
128        self.assertEqual(self.total_wr_ops, stats['wr_operations'])
129        self.assertEqual(self.total_flush_ops, stats['flush_operations'])
130        self.assertEqual(self.wr_highest_offset, stats['wr_highest_offset'])
131        self.assertEqual(self.failed_rd_ops, stats['failed_rd_operations'])
132        self.assertEqual(self.failed_wr_ops, stats['failed_wr_operations'])
133        self.assertEqual(self.invalid_rd_ops, stats['invalid_rd_operations'])
134        self.assertEqual(self.invalid_wr_ops, stats['invalid_wr_operations'])
135        self.assertEqual(self.account_invalid, stats['account_invalid'])
136        self.assertEqual(self.account_failed, stats['account_failed'])
137        self.assertEqual(self.total_wr_merged, stats['wr_merged'])
138
139        # Check that there's exactly one interval with the length we defined
140        self.assertEqual(1, len(stats['timed_stats']))
141        timed_stats = stats['timed_stats'][0]
142        self.assertEqual(interval_length, timed_stats['interval_length'])
143
144        total_rd_latency = self.accounted_latency(read = True)
145        if (total_rd_latency != 0):
146            self.assertEqual(total_rd_latency, stats['rd_total_time_ns'])
147            self.assertEqual(op_latency, timed_stats['min_rd_latency_ns'])
148            self.assertEqual(op_latency, timed_stats['max_rd_latency_ns'])
149            self.assertEqual(op_latency, timed_stats['avg_rd_latency_ns'])
150            self.assertLess(0, timed_stats['avg_rd_queue_depth'])
151        else:
152            self.assertEqual(0, stats['rd_total_time_ns'])
153            self.assertEqual(0, timed_stats['min_rd_latency_ns'])
154            self.assertEqual(0, timed_stats['max_rd_latency_ns'])
155            self.assertEqual(0, timed_stats['avg_rd_latency_ns'])
156            self.assertEqual(0, timed_stats['avg_rd_queue_depth'])
157
158        # min read latency <= avg read latency <= max read latency
159        self.assertLessEqual(timed_stats['min_rd_latency_ns'],
160                             timed_stats['avg_rd_latency_ns'])
161        self.assertLessEqual(timed_stats['avg_rd_latency_ns'],
162                             timed_stats['max_rd_latency_ns'])
163
164        total_wr_latency = self.accounted_latency(write = True)
165        if (total_wr_latency != 0):
166            self.assertEqual(total_wr_latency, stats['wr_total_time_ns'])
167            self.assertEqual(op_latency, timed_stats['min_wr_latency_ns'])
168            self.assertEqual(op_latency, timed_stats['max_wr_latency_ns'])
169            self.assertEqual(op_latency, timed_stats['avg_wr_latency_ns'])
170            self.assertLess(0, timed_stats['avg_wr_queue_depth'])
171        else:
172            self.assertEqual(0, stats['wr_total_time_ns'])
173            self.assertEqual(0, timed_stats['min_wr_latency_ns'])
174            self.assertEqual(0, timed_stats['max_wr_latency_ns'])
175            self.assertEqual(0, timed_stats['avg_wr_latency_ns'])
176            self.assertEqual(0, timed_stats['avg_wr_queue_depth'])
177
178        # min write latency <= avg write latency <= max write latency
179        self.assertLessEqual(timed_stats['min_wr_latency_ns'],
180                             timed_stats['avg_wr_latency_ns'])
181        self.assertLessEqual(timed_stats['avg_wr_latency_ns'],
182                             timed_stats['max_wr_latency_ns'])
183
184        total_flush_latency = self.accounted_latency(flush = True)
185        if (total_flush_latency != 0):
186            self.assertEqual(total_flush_latency, stats['flush_total_time_ns'])
187            self.assertEqual(op_latency, timed_stats['min_flush_latency_ns'])
188            self.assertEqual(op_latency, timed_stats['max_flush_latency_ns'])
189            self.assertEqual(op_latency, timed_stats['avg_flush_latency_ns'])
190        else:
191            self.assertEqual(0, stats['flush_total_time_ns'])
192            self.assertEqual(0, timed_stats['min_flush_latency_ns'])
193            self.assertEqual(0, timed_stats['max_flush_latency_ns'])
194            self.assertEqual(0, timed_stats['avg_flush_latency_ns'])
195
196        # min flush latency <= avg flush latency <= max flush latency
197        self.assertLessEqual(timed_stats['min_flush_latency_ns'],
198                             timed_stats['avg_flush_latency_ns'])
199        self.assertLessEqual(timed_stats['avg_flush_latency_ns'],
200                             timed_stats['max_flush_latency_ns'])
201
202        # idle_time_ns must be > 0 if we have performed any operation
203        if (self.accounted_ops(read = True, write = True, flush = True) != 0):
204            self.assertLess(0, stats['idle_time_ns'])
205        else:
206            self.assertFalse(stats.has_key('idle_time_ns'))
207
208        # This test does not alter these, so they must be all 0
209        self.assertEqual(0, stats['rd_merged'])
210        self.assertEqual(0, stats['failed_flush_operations'])
211        self.assertEqual(0, stats['invalid_flush_operations'])
212
213    def do_test_stats(self, rd_size = 0, rd_ops = 0, wr_size = 0, wr_ops = 0,
214                      flush_ops = 0, invalid_rd_ops = 0, invalid_wr_ops = 0,
215                      failed_rd_ops = 0, failed_wr_ops = 0, wr_merged = 0):
216        # The 'ops' list will contain all the requested I/O operations
217        ops = []
218        for i in range(rd_ops):
219            ops.append("aio_read %d %d" % (i * rd_size, rd_size))
220
221        for i in range(wr_ops):
222            ops.append("aio_write %d %d" % (i * wr_size, wr_size))
223
224        for i in range(flush_ops):
225            ops.append("aio_flush")
226
227        highest_offset = wr_ops * wr_size
228
229        for i in range(invalid_rd_ops):
230            ops.append("aio_read -i 0 512")
231
232        for i in range(invalid_wr_ops):
233            ops.append("aio_write -i 0 512")
234
235        for i in range(failed_rd_ops):
236            ops.append("aio_read %d 512" % bad_offset)
237
238        for i in range(failed_wr_ops):
239            ops.append("aio_write %d 512" % bad_offset)
240
241        if failed_wr_ops > 0:
242            highest_offset = max(highest_offset, bad_offset + 512)
243
244        # Now perform all operations
245        for op in ops:
246            self.vm.hmp_qemu_io("drive0", op)
247
248        # Update the expected totals
249        self.total_rd_bytes += rd_ops * rd_size
250        self.total_rd_ops += rd_ops
251        self.total_wr_bytes += wr_ops * wr_size
252        self.total_wr_ops += wr_ops
253        self.total_wr_merged += wr_merged
254        self.total_flush_ops += flush_ops
255        self.invalid_rd_ops += invalid_rd_ops
256        self.invalid_wr_ops += invalid_wr_ops
257        self.failed_rd_ops += failed_rd_ops
258        self.failed_wr_ops += failed_wr_ops
259
260        self.wr_highest_offset = max(self.wr_highest_offset, highest_offset)
261
262        # Advance the clock so idle_time_ns has a meaningful value
263        self.vm.qtest("clock_step %d" % nsec_per_sec)
264
265        # And check that the actual statistics match the expected ones
266        self.check_values()
267
268    def test_read_only(self):
269        test_values = [[512,    1],
270                       [65536,  1],
271                       [512,   12],
272                       [65536, 12]]
273        for i in test_values:
274            self.do_test_stats(rd_size = i[0], rd_ops = i[1])
275
276    def test_write_only(self):
277        test_values = [[512,    1],
278                       [65536,  1],
279                       [512,   12],
280                       [65536, 12]]
281        for i in test_values:
282            self.do_test_stats(wr_size = i[0], wr_ops = i[1])
283
284    def test_invalid(self):
285        self.do_test_stats(invalid_rd_ops = 7)
286        self.do_test_stats(invalid_wr_ops = 3)
287        self.do_test_stats(invalid_rd_ops = 4, invalid_wr_ops = 5)
288
289    def test_failed(self):
290        self.do_test_stats(failed_rd_ops = 8)
291        self.do_test_stats(failed_wr_ops = 6)
292        self.do_test_stats(failed_rd_ops = 5, failed_wr_ops = 12)
293
294    def test_flush(self):
295        self.do_test_stats(flush_ops = 8)
296
297    def test_all(self):
298        # rd_size, rd_ops, wr_size, wr_ops, flush_ops
299        # invalid_rd_ops,  invalid_wr_ops,
300        # failed_rd_ops,   failed_wr_ops
301        # wr_merged
302        test_values = [[512,    1, 512,   1, 1, 4, 7, 5, 2, 0],
303                       [65536,  1, 2048, 12, 7, 7, 5, 2, 5, 0],
304                       [32768,  9, 8192,  1, 4, 3, 2, 4, 6, 0],
305                       [16384, 11, 3584, 16, 9, 8, 6, 7, 3, 0]]
306        for i in test_values:
307            self.do_test_stats(*i)
308
309    def test_no_op(self):
310        # All values must be sane before doing any I/O
311        self.check_values()
312
313
314class BlockDeviceStatsTestAccountInvalid(BlockDeviceStatsTestCase):
315    account_invalid = True
316    account_failed = False
317
318class BlockDeviceStatsTestAccountFailed(BlockDeviceStatsTestCase):
319    account_invalid = False
320    account_failed = True
321
322class BlockDeviceStatsTestAccountBoth(BlockDeviceStatsTestCase):
323    account_invalid = True
324    account_failed = True
325
326class BlockDeviceStatsTestCoroutine(BlockDeviceStatsTestCase):
327    test_img = "null-co://"
328
329if __name__ == '__main__':
330    iotests.main(supported_fmts=["raw"])
331