Skip to content

Commit 97e2a2d

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 97e2a2d

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

lib/utils.py

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

258258

259+
def proc_diskstats_supported(vardir):
260+
return get_vardir_device(vardir) != 'unknown' 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, IndexError):
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+
"""Function options are:
297+
devname - initialy got by get_vardir_device()
298+
previous - array of 2 fields:
299+
[0] - previous time value
300+
[1] - previous milliseconds spent doing I/Os value
301+
"""
302+
busy = 0
303+
times = time.time()
304+
value = 0
305+
try:
306+
with open('/proc/diskstats', 'r') as f:
307+
for line in f:
308+
if line.split()[2] == devname:
309+
value = line.split()[12]
310+
busy = 100 * (int(value) - int(previous[1])) / \
311+
(1024 * (times - previous[0]))
312+
except (OSError, IOError):
313+
pass
314+
return busy, [times, value]
315+
316+
259317
def set_fd_cloexec(socket):
260318
flags = fcntl.fcntl(socket, fcntl.F_GETFD)
261319
fcntl.fcntl(socket, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)

listeners.py

Lines changed: 57 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,23 @@ def __init__(self, get_logfile):
3741
self.field_size = 60
3842
self._sampler = sampler
3943
self.duration_stats = dict()
44+
self.vardir = Options().args.vardir
45+
self.proc_diskstats_supported = proc_diskstats_supported(self.vardir)
46+
if self.proc_diskstats_supported:
47+
self.vardir_devname = get_vardir_device(self.vardir)
48+
self.vardir_usage = dict()
49+
self.vardir_data = dict()
4050
self.failed_tasks = []
4151
self.get_logfile = get_logfile
4252
self.long_tasks = set()
4353

4454
def process_result(self, obj):
55+
# Called only once on task run initiated.
56+
if self.proc_diskstats_supported and isinstance(obj, WorkerCurrentTask):
57+
task_id = (obj.task_name, obj.task_param)
58+
self.vardir_usage[task_id], self.vardir_data[task_id] = get_disk_bound_stat_busy(
59+
self.vardir_devname, [time.time(), 0])
60+
4561
if not isinstance(obj, WorkerTaskResult):
4662
return
4763

@@ -59,6 +75,10 @@ def process_result(self, obj):
5975
obj.show_reproduce_content))
6076

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

6383
def get_long_mark(self, task):
6484
return '(long)' if task in self.long_tasks else ''
@@ -145,6 +165,42 @@ def print_duration(self, stats_dir):
145165
self.duration_stats[task_id]))
146166
fd.close()
147167

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

154210
self.print_rss_summary(stats_dir)
155211
self.print_duration(stats_dir)
212+
self.print_disk_usage_summary(stats_dir)
156213

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

0 commit comments

Comments
 (0)