3 # Tests for block device statistics
5 # Copyright (C) 2015 Igalia, S.L.
6 # Author: Alberto Garcia <berto@igalia.com>
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.
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.
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/>.
26 nsec_per_sec
= 1000000000
27 op_latency
= nsec_per_sec
// 1000 # See qtest_latency_ns in accounting.c
29 bad_offset
= bad_sector
* 512
30 blkdebug_file
= os
.path
.join(iotests
.test_dir
, 'blkdebug.conf')
32 class BlockDeviceStatsTestCase(iotests
.QMPTestCase
):
33 test_driver
= "null-aio"
45 account_invalid
= False
46 account_failed
= False
48 def blockstats(self
, device
):
49 result
= self
.vm
.qmp("query-blockstats")
50 for r
in result
['return']:
51 if r
['device'] == device
:
53 raise Exception("Device not found for blockstats: %s" % device
)
55 def create_blkdebug_file(self
):
56 file = open(blkdebug_file
, 'w')
67 ''' % (bad_sector
, bad_sector
))
70 def required_drivers(self
):
71 return [self
.test_driver
]
73 @iotests.skip_if_unsupported(required_drivers
)
76 drive_args
.append("stats-intervals.0=%d" % interval_length
)
77 drive_args
.append("stats-account-invalid=%s" %
78 (self
.account_invalid
and "on" or "off"))
79 drive_args
.append("stats-account-failed=%s" %
80 (self
.account_failed
and "on" or "off"))
81 drive_args
.append("file.image.read-zeroes=on")
82 self
.create_blkdebug_file()
83 self
.vm
= iotests
.VM().add_drive('blkdebug:%s:%s://' %
84 (blkdebug_file
, self
.test_driver
),
87 # Set an initial value for the clock
88 self
.vm
.qtest("clock_step %d" % nsec_per_sec
)
92 os
.remove(blkdebug_file
)
94 def accounted_ops(self
, read
= False, write
= False, flush
= False):
97 ops
+= self
.total_wr_ops
98 if self
.account_failed
:
99 ops
+= self
.failed_wr_ops
100 if self
.account_invalid
:
101 ops
+= self
.invalid_wr_ops
103 ops
+= self
.total_rd_ops
104 if self
.account_failed
:
105 ops
+= self
.failed_rd_ops
106 if self
.account_invalid
:
107 ops
+= self
.invalid_rd_ops
109 ops
+= self
.total_flush_ops
112 def accounted_latency(self
, read
= False, write
= False, flush
= False):
115 latency
+= self
.total_wr_ops
* op_latency
116 if self
.account_failed
:
117 latency
+= self
.failed_wr_ops
* op_latency
119 latency
+= self
.total_rd_ops
* op_latency
120 if self
.account_failed
:
121 latency
+= self
.failed_rd_ops
* op_latency
123 latency
+= self
.total_flush_ops
* op_latency
126 def check_values(self
):
127 stats
= self
.blockstats('drive0')
129 # Check that the totals match with what we have calculated
130 self
.assertEqual(self
.total_rd_bytes
, stats
['rd_bytes'])
131 self
.assertEqual(self
.total_wr_bytes
, stats
['wr_bytes'])
132 self
.assertEqual(self
.total_rd_ops
, stats
['rd_operations'])
133 self
.assertEqual(self
.total_wr_ops
, stats
['wr_operations'])
134 self
.assertEqual(self
.total_flush_ops
, stats
['flush_operations'])
135 self
.assertEqual(self
.wr_highest_offset
, stats
['wr_highest_offset'])
136 self
.assertEqual(self
.failed_rd_ops
, stats
['failed_rd_operations'])
137 self
.assertEqual(self
.failed_wr_ops
, stats
['failed_wr_operations'])
138 self
.assertEqual(self
.invalid_rd_ops
, stats
['invalid_rd_operations'])
139 self
.assertEqual(self
.invalid_wr_ops
, stats
['invalid_wr_operations'])
140 self
.assertEqual(self
.account_invalid
, stats
['account_invalid'])
141 self
.assertEqual(self
.account_failed
, stats
['account_failed'])
142 self
.assertEqual(self
.total_wr_merged
, stats
['wr_merged'])
144 # Check that there's exactly one interval with the length we defined
145 self
.assertEqual(1, len(stats
['timed_stats']))
146 timed_stats
= stats
['timed_stats'][0]
147 self
.assertEqual(interval_length
, timed_stats
['interval_length'])
149 total_rd_latency
= self
.accounted_latency(read
= True)
150 if (total_rd_latency
!= 0):
151 self
.assertEqual(total_rd_latency
, stats
['rd_total_time_ns'])
152 self
.assertEqual(op_latency
, timed_stats
['min_rd_latency_ns'])
153 self
.assertEqual(op_latency
, timed_stats
['max_rd_latency_ns'])
154 self
.assertEqual(op_latency
, timed_stats
['avg_rd_latency_ns'])
155 self
.assertLess(0, timed_stats
['avg_rd_queue_depth'])
157 self
.assertEqual(0, stats
['rd_total_time_ns'])
158 self
.assertEqual(0, timed_stats
['min_rd_latency_ns'])
159 self
.assertEqual(0, timed_stats
['max_rd_latency_ns'])
160 self
.assertEqual(0, timed_stats
['avg_rd_latency_ns'])
161 self
.assertEqual(0, timed_stats
['avg_rd_queue_depth'])
163 # min read latency <= avg read latency <= max read latency
164 self
.assertLessEqual(timed_stats
['min_rd_latency_ns'],
165 timed_stats
['avg_rd_latency_ns'])
166 self
.assertLessEqual(timed_stats
['avg_rd_latency_ns'],
167 timed_stats
['max_rd_latency_ns'])
169 total_wr_latency
= self
.accounted_latency(write
= True)
170 if (total_wr_latency
!= 0):
171 self
.assertEqual(total_wr_latency
, stats
['wr_total_time_ns'])
172 self
.assertEqual(op_latency
, timed_stats
['min_wr_latency_ns'])
173 self
.assertEqual(op_latency
, timed_stats
['max_wr_latency_ns'])
174 self
.assertEqual(op_latency
, timed_stats
['avg_wr_latency_ns'])
175 self
.assertLess(0, timed_stats
['avg_wr_queue_depth'])
177 self
.assertEqual(0, stats
['wr_total_time_ns'])
178 self
.assertEqual(0, timed_stats
['min_wr_latency_ns'])
179 self
.assertEqual(0, timed_stats
['max_wr_latency_ns'])
180 self
.assertEqual(0, timed_stats
['avg_wr_latency_ns'])
181 self
.assertEqual(0, timed_stats
['avg_wr_queue_depth'])
183 # min write latency <= avg write latency <= max write latency
184 self
.assertLessEqual(timed_stats
['min_wr_latency_ns'],
185 timed_stats
['avg_wr_latency_ns'])
186 self
.assertLessEqual(timed_stats
['avg_wr_latency_ns'],
187 timed_stats
['max_wr_latency_ns'])
189 total_flush_latency
= self
.accounted_latency(flush
= True)
190 if (total_flush_latency
!= 0):
191 self
.assertEqual(total_flush_latency
, stats
['flush_total_time_ns'])
192 self
.assertEqual(op_latency
, timed_stats
['min_flush_latency_ns'])
193 self
.assertEqual(op_latency
, timed_stats
['max_flush_latency_ns'])
194 self
.assertEqual(op_latency
, timed_stats
['avg_flush_latency_ns'])
196 self
.assertEqual(0, stats
['flush_total_time_ns'])
197 self
.assertEqual(0, timed_stats
['min_flush_latency_ns'])
198 self
.assertEqual(0, timed_stats
['max_flush_latency_ns'])
199 self
.assertEqual(0, timed_stats
['avg_flush_latency_ns'])
201 # min flush latency <= avg flush latency <= max flush latency
202 self
.assertLessEqual(timed_stats
['min_flush_latency_ns'],
203 timed_stats
['avg_flush_latency_ns'])
204 self
.assertLessEqual(timed_stats
['avg_flush_latency_ns'],
205 timed_stats
['max_flush_latency_ns'])
207 # idle_time_ns must be > 0 if we have performed any operation
208 if (self
.accounted_ops(read
= True, write
= True, flush
= True) != 0):
209 self
.assertLess(0, stats
['idle_time_ns'])
211 self
.assertFalse('idle_time_ns' in stats
)
213 # This test does not alter these, so they must be all 0
214 self
.assertEqual(0, stats
['rd_merged'])
215 self
.assertEqual(0, stats
['failed_flush_operations'])
216 self
.assertEqual(0, stats
['invalid_flush_operations'])
218 def do_test_stats(self
, rd_size
= 0, rd_ops
= 0, wr_size
= 0, wr_ops
= 0,
219 flush_ops
= 0, invalid_rd_ops
= 0, invalid_wr_ops
= 0,
220 failed_rd_ops
= 0, failed_wr_ops
= 0, wr_merged
= 0):
221 # The 'ops' list will contain all the requested I/O operations
223 for i
in range(rd_ops
):
224 ops
.append("aio_read %d %d" % (i
* rd_size
, rd_size
))
226 for i
in range(wr_ops
):
227 ops
.append("aio_write %d %d" % (i
* wr_size
, wr_size
))
229 for i
in range(flush_ops
):
230 ops
.append("aio_flush")
232 highest_offset
= wr_ops
* wr_size
234 for i
in range(invalid_rd_ops
):
235 ops
.append("aio_read -i 0 512")
237 for i
in range(invalid_wr_ops
):
238 ops
.append("aio_write -i 0 512")
240 for i
in range(failed_rd_ops
):
241 ops
.append("aio_read %d 512" % bad_offset
)
243 for i
in range(failed_wr_ops
):
244 ops
.append("aio_write %d 512" % bad_offset
)
246 # We need an extra aio_flush to settle all outstanding AIO
247 # operations before we can advance the virtual clock, so that
248 # the last access happens before clock_step and idle_time_ns
249 # will be greater than 0
251 if rd_ops
+ wr_ops
+ invalid_rd_ops
+ invalid_wr_ops
+ \
252 failed_rd_ops
+ failed_wr_ops
> 0:
256 ops
.append("aio_flush")
258 if failed_wr_ops
> 0:
259 highest_offset
= max(highest_offset
, bad_offset
+ 512)
261 # Now perform all operations
263 self
.vm
.hmp_qemu_io("drive0", op
)
265 # Update the expected totals
266 self
.total_rd_bytes
+= rd_ops
* rd_size
267 self
.total_rd_ops
+= rd_ops
268 self
.total_wr_bytes
+= wr_ops
* wr_size
269 self
.total_wr_ops
+= wr_ops
270 self
.total_wr_merged
+= wr_merged
271 self
.total_flush_ops
+= flush_ops
+ extra_flush
272 self
.invalid_rd_ops
+= invalid_rd_ops
273 self
.invalid_wr_ops
+= invalid_wr_ops
274 self
.failed_rd_ops
+= failed_rd_ops
275 self
.failed_wr_ops
+= failed_wr_ops
277 self
.wr_highest_offset
= max(self
.wr_highest_offset
, highest_offset
)
279 # Advance the clock so idle_time_ns has a meaningful value
280 self
.vm
.qtest("clock_step %d" % nsec_per_sec
)
282 # And check that the actual statistics match the expected ones
285 def test_read_only(self
):
286 test_values
= [[512, 1],
290 for i
in test_values
:
291 self
.do_test_stats(rd_size
= i
[0], rd_ops
= i
[1])
293 def test_write_only(self
):
294 test_values
= [[512, 1],
298 for i
in test_values
:
299 self
.do_test_stats(wr_size
= i
[0], wr_ops
= i
[1])
301 def test_invalid(self
):
302 self
.do_test_stats(invalid_rd_ops
= 7)
303 self
.do_test_stats(invalid_wr_ops
= 3)
304 self
.do_test_stats(invalid_rd_ops
= 4, invalid_wr_ops
= 5)
306 def test_failed(self
):
307 self
.do_test_stats(failed_rd_ops
= 8)
308 self
.do_test_stats(failed_wr_ops
= 6)
309 self
.do_test_stats(failed_rd_ops
= 5, failed_wr_ops
= 12)
311 def test_flush(self
):
312 self
.do_test_stats(flush_ops
= 8)
315 # rd_size, rd_ops, wr_size, wr_ops, flush_ops
316 # invalid_rd_ops, invalid_wr_ops,
317 # failed_rd_ops, failed_wr_ops
319 test_values
= [[512, 1, 512, 1, 1, 4, 7, 5, 2, 0],
320 [65536, 1, 2048, 12, 7, 7, 5, 2, 5, 0],
321 [32768, 9, 8192, 1, 4, 3, 2, 4, 6, 0],
322 [16384, 11, 3584, 16, 9, 8, 6, 7, 3, 0]]
323 for i
in test_values
:
324 self
.do_test_stats(*i
)
326 def test_no_op(self
):
327 # All values must be sane before doing any I/O
331 class BlockDeviceStatsTestAccountInvalid(BlockDeviceStatsTestCase
):
332 account_invalid
= True
333 account_failed
= False
335 class BlockDeviceStatsTestAccountFailed(BlockDeviceStatsTestCase
):
336 account_invalid
= False
337 account_failed
= True
339 class BlockDeviceStatsTestAccountBoth(BlockDeviceStatsTestCase
):
340 account_invalid
= True
341 account_failed
= True
343 class BlockDeviceStatsTestCoroutine(BlockDeviceStatsTestCase
):
344 test_driver
= "null-co"
346 if __name__
== '__main__':
347 if 'null-co' not in iotests
.supported_formats():
348 iotests
.notrun('null-co driver support missing')
349 iotests
.main(supported_fmts
=["raw"])