Skip to content

Commit 9a5f091

Browse files
committed
Add 'vardir' disk utilization collecting
Added for 'vardir' path disk disk utilization collecting routine which parses file: /proc/diskstats for the value milliseconds spent doing I/Os from the given device and counts 'busy' value in percents, like iostat tool does for its '%util' field. Check for more information linux kernel documentation [1]. We use from the 'diskstats' file Field 10 which is in real at the 12th position: Field 9 -- # of I/Os currently in progress The only field that should go to zero. Incremented as requests are given to appropriate struct request_queue and decremented as they finish. Field 10 -- # of milliseconds spent doing I/Os This field increases so long as field 9 is nonzero. Decided to collect 'vardir' disk utilization per each run test in standalone file and print to stdout after testing completed, up to 10 most biggest of it. To count 'vardir' disk utilization for each test we need to know what was the value of the disk bound on the test start and time when test started to count later the utilization. On test finish we check the test finish time and current disk bound and count disk utilization. All of these checks and counts implemented as the new routine 'get_disk_bound_stat_busy()' in 'lib/utils.py' file. To collect disk bounds and durations on test start/finish events used 'StatisticsWatcher' listener which has the following usable routines for it: process_result() Using 'WorkerCurrentTask' queue message apperance to save the disk bounds values at the start of the tests. Using 'WorkerTaskResult' queue message appearance to save final disk bounds values for finished tests. print_statistics() - statistics printing to stdout after testing. Prints disk bounds for failed tests and up to 10 most used it tests. We use standalone 'statistics' directory in 'vardir' path to save 'durations.log' file in it with disk bounds for each tested task in format: <test task name> <disk bound> Part of tarantool/tarantool-qa#97 Closes #308 [1]: https://www.kernel.org/doc/Documentation/iostats.txt
1 parent 7ddaf6a commit 9a5f091

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed

lib/utils.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,60 @@ def get_proc_stat_rss(pid):
256256
return rss
257257

258258

259+
def proc_diskstats_supported():
260+
return os.path.isfile('/proc/mounts') and os.path.isfile('/proc/diskstats')
261+
262+
263+
def get_vardir_device(vardir):
264+
dev = 'unknown'
265+
path = os.path.abspath(vardir)
266+
while not os.path.ismount(path):
267+
path = os.path.dirname(path)
268+
try:
269+
with open('/proc/mounts', 'r') as f:
270+
for line in f:
271+
if line.split()[1] == path:
272+
mount = line.split()[0]
273+
dev = mount.split('/')[2]
274+
except (OSError, IOError):
275+
pass
276+
return dev
277+
278+
279+
# This routine gets the value milliseconds spent doing I/Os
280+
# from the given 'vardir' device found at '/proc/diskstats'
281+
# and counts 'busy' value in percents like iostat tool does
282+
# for its '%util' field.
283+
#
284+
# Check for more information linux kernel documentation:
285+
# https://www.kernel.org/doc/Documentation/iostats.txt
286+
#
287+
# We use Field 10 which has the 12th position in the file:
288+
#
289+
# Field 9 -- # of I/Os currently in progress
290+
# The only field that should go to zero. Incremented as requests are
291+
# given to appropriate struct request_queue and decremented as they finish.
292+
#
293+
# Field 10 -- # of milliseconds spent doing I/Os
294+
# This field increases so long as field 9 is nonzero.
295+
def get_disk_bound_stat_busy(devname, previous):
296+
busy = 'unknown'
297+
times = time.time()
298+
value = 0
299+
try:
300+
with open('/proc/diskstats', 'r') as f:
301+
for line in f:
302+
if line.split()[2] == devname:
303+
value = line.split()[12]
304+
if previous[1] == 'unknown':
305+
previous[1] = value
306+
busy = 100 * (int(value) - int(previous[1])) / \
307+
(1024 * (times - previous[0]))
308+
except (OSError, IOError):
309+
pass
310+
return busy, [times, value]
311+
312+
259313
def set_fd_cloexec(socket):
260314
flags = fcntl.fcntl(socket, fcntl.F_GETFD)
261315
fcntl.fcntl(socket, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)

listeners.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import sys
33
import yaml
44
import shutil
5+
import time
56

67
from lib import Options
78
from lib.colorer import color_stdout
@@ -16,6 +17,9 @@
1617
from lib.utils import safe_makedirs
1718
from lib.utils import print_tail_n
1819
from lib.utils import print_unidiff
20+
from lib.utils import proc_diskstats_supported
21+
from lib.utils import get_vardir_device
22+
from lib.utils import get_disk_bound_stat_busy
1923

2024

2125
class BaseWatcher(object):
@@ -37,11 +41,22 @@ def __init__(self, get_logfile):
3741
self.field_size = 60
3842
self._sampler = sampler
3943
self.duration_stats = dict()
44+
self.proc_diskstats_supported = proc_diskstats_supported()
45+
if self.proc_diskstats_supported:
46+
self.vardir_devname = get_vardir_device(Options().args.vardir)
47+
self.vardir_usage = dict()
48+
self.vardir_data = dict()
4049
self.failed_tasks = []
4150
self.get_logfile = get_logfile
4251
self.long_tasks = set()
4352

4453
def process_result(self, obj):
54+
# Called only once on task run initiated.
55+
if self.proc_diskstats_supported and isinstance(obj, WorkerCurrentTask):
56+
task_id = (obj.task_name, obj.task_param)
57+
self.vardir_usage[task_id], self.vardir_data[task_id] = get_disk_bound_stat_busy(
58+
self.vardir_devname, [time.time(), 'unknown'])
59+
4560
if not isinstance(obj, WorkerTaskResult):
4661
return
4762

@@ -59,6 +74,10 @@ def process_result(self, obj):
5974
obj.show_reproduce_content))
6075

6176
self.duration_stats[obj.task_id] = obj.duration
77+
if self.proc_diskstats_supported:
78+
self.vardir_usage[obj.task_id], self.vardir_data[obj.task_id] = \
79+
get_disk_bound_stat_busy(self.vardir_devname,
80+
self.vardir_data[obj.task_id])
6281

6382
def get_long_mark(self, task):
6483
return '(long)' if task in self.long_tasks else ''
@@ -145,6 +164,42 @@ def print_duration(self, stats_dir):
145164
self.duration_stats[task_id]))
146165
fd.close()
147166

167+
# Disk usage.
168+
def print_disk_usage_summary(self, stats_dir):
169+
if not self.proc_diskstats_supported:
170+
return
171+
172+
top_usage = 10
173+
174+
# Print to stdout disk usage statistics for all failed tasks.
175+
if self.failed_tasks:
176+
color_stdout('Disk usage of failed tests:\n', schema='info')
177+
for task in self.failed_tasks:
178+
task_id = task[0]
179+
if task_id in self.vardir_usage:
180+
color_stdout('* %3d %s %s\n' % (self.vardir_usage[task_id],
181+
self.prettify_task_name(task_id).ljust(self.field_size),
182+
self.get_long_mark(task_id)),
183+
schema='info')
184+
185+
# Print to stdout disk usage statistics for some number of most it used tasks.
186+
color_stdout('Top {} tests by disk usage:\n'.format(top_usage), schema='info')
187+
results_sorted = sorted(self.vardir_usage.items(), key=lambda x: x[1], reverse=True)
188+
for task_id, usage in results_sorted[:top_usage]:
189+
color_stdout('* %3d %s %s\n' % (usage,
190+
self.prettify_task_name(task_id).ljust(self.field_size),
191+
self.get_long_mark(task_id)), schema='info')
192+
193+
color_stdout('-' * 81, "\n", schema='separator')
194+
195+
# Print disk usage statistics to '<vardir>/statistics/diskusage.log' file.
196+
filepath = os.path.join(stats_dir, 'diskusage.log')
197+
fd = open(filepath, 'w')
198+
for task_id in self.vardir_usage:
199+
fd.write("{} {}\n".format(self.prettify_task_name(task_id),
200+
self.vardir_usage[task_id]))
201+
fd.close()
202+
148203
def print_statistics(self):
149204
"""Print statistics and results of testing."""
150205
# Prepare standalone subpath '<vardir>/statistics' for statistics files.
@@ -153,6 +208,7 @@ def print_statistics(self):
153208

154209
self.print_rss_summary(stats_dir)
155210
self.print_duration(stats_dir)
211+
self.print_disk_usage_summary(stats_dir)
156212

157213
if self.stats:
158214
color_stdout('Statistics:\n', schema='test_var')

0 commit comments

Comments
 (0)