Skip to content

Add 'vardir' disk utilization collecting #296

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions lib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,64 @@ def get_proc_stat_rss(pid):
return rss


def proc_diskstats_supported(vardir):
return get_vardir_device(vardir) != 'unknown' and os.path.isfile('/proc/diskstats')


def get_vardir_device(vardir):
dev = 'unknown'
path = os.path.abspath(vardir)
while not os.path.ismount(path):
path = os.path.dirname(path)
try:
with open('/proc/mounts', 'r') as f:
for line in f:
if line.split()[1] == path:
mount = line.split()[0]
dev = mount.split('/')[2]
except (OSError, IOError, IndexError):
pass
return dev
Comment on lines +263 to +276
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't check, but will it work as expected for me?

$ cat /proc/mounts
/dev/root / ext4 rw,noatime 0 0
devtmpfs /dev devtmpfs rw,nosuid,relatime,size=10240k,nr_inodes=2033827,mode=755 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /run tmpfs rw,nodev,relatime,size=1627436k,mode=755 0 0
sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
debugfs /sys/kernel/debug debugfs rw,nosuid,nodev,noexec,relatime 0 0
fusectl /sys/fs/fuse/connections fusectl rw,nosuid,nodev,noexec,relatime 0 0
selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0
efivarfs /sys/firmware/efi/efivars efivarfs rw,nosuid,nodev,noexec,relatime 0 0
cgroup_root /sys/fs/cgroup tmpfs rw,nosuid,nodev,noexec,relatime,size=10240k,mode=755 0 0
openrc /sys/fs/cgroup/openrc cgroup rw,nosuid,nodev,noexec,relatime,release_agent=/lib/rc/sh/cgroup-release-agent.sh,name=openrc 0 0
none /sys/fs/cgroup/unified cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate 0 0
cpuset /sys/fs/cgroup/cpuset cgroup rw,nosuid,nodev,noexec,relatime,cpuset 0 0
cpu /sys/fs/cgroup/cpu cgroup rw,nosuid,nodev,noexec,relatime,cpu 0 0
cpuacct /sys/fs/cgroup/cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpuacct 0 0
blkio /sys/fs/cgroup/blkio cgroup rw,nosuid,nodev,noexec,relatime,blkio 0 0
memory /sys/fs/cgroup/memory cgroup rw,nosuid,nodev,noexec,relatime,memory 0 0
devices /sys/fs/cgroup/devices cgroup rw,nosuid,nodev,noexec,relatime,devices 0 0
freezer /sys/fs/cgroup/freezer cgroup rw,nosuid,nodev,noexec,relatime,freezer 0 0
net_cls /sys/fs/cgroup/net_cls cgroup rw,nosuid,nodev,noexec,relatime,net_cls 0 0
perf_event /sys/fs/cgroup/perf_event cgroup rw,nosuid,nodev,noexec,relatime,perf_event 0 0
net_prio /sys/fs/cgroup/net_prio cgroup rw,nosuid,nodev,noexec,relatime,net_prio 0 0
hugetlb /sys/fs/cgroup/hugetlb cgroup rw,nosuid,nodev,noexec,relatime,hugetlb 0 0
pids /sys/fs/cgroup/pids cgroup rw,nosuid,nodev,noexec,relatime,pids 0 0
mqueue /dev/mqueue mqueue rw,nosuid,nodev,noexec,relatime 0 0
devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
tmpfs /dev/shm tmpfs rw,nosuid,nodev,noexec,noatime,size=1048576k 0 0
tmpfs /var/tmp/portage tmpfs rw,nodev,noatime,size=4194304k 0 0
binfmt_misc /proc/sys/fs/binfmt_misc binfmt_misc rw,nosuid,nodev,noexec,relatime 0 0
/dev/root /var/lib/docker ext4 rw,noatime 0 0
nsfs /run/docker/netns/default nsfs rw 0 0

$ cat /proc/diskstats 
   7       0 loop0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       1 loop1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       2 loop2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       3 loop3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       4 loop4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       5 loop5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       6 loop6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   7       7 loop7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
   8       0 sda 3914740 2091334 209636333 3300777 20400957 25673178 8530616480 245749826 0 12550525 241975264 1065702 0 590682616 2552745
   8       1 sda1 90 0 9728 53 0 0 0 0 0 44 25 0 0 0 0
   8       2 sda2 28 0 2080 22 0 0 0 0 0 15 11 0 0 0 0
   8       3 sda3 3914590 2091334 209623349 3300680 20339615 25673178 8530616480 245695226 0 12541502 241943738 1065702 0 590682616 2552745
   9       0 md0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0



# This routine gets the value milliseconds spent doing I/Os
# from the given 'vardir' device found at '/proc/diskstats'
# and counts 'busy' value in percents like iostat tool does
# for its '%util' field.
#
# Check for more information linux kernel documentation:
# https://www.kernel.org/doc/Documentation/iostats.txt
#
# We use Field 10 which has the 12th position in the file:
#
# 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.
def get_disk_bound_stat_busy(devname, previous):
"""Function options are:
devname - initialy got by get_vardir_device()
previous - array of 2 fields:
[0] - previous time value
[1] - previous milliseconds spent doing I/Os value
"""
busy = 0
times = time.time()
value = 0
try:
with open('/proc/diskstats', 'r') as f:
for line in f:
if line.split()[2] == devname:
value = line.split()[12]
busy = 100 * (int(value) - int(previous[1])) / \
(1024 * (times - previous[0]))
except (OSError, IOError):
pass
return busy, [times, value]


def set_fd_cloexec(socket):
flags = fcntl.fcntl(socket, fcntl.F_GETFD)
fcntl.fcntl(socket, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
Expand Down
57 changes: 57 additions & 0 deletions listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import sys
import yaml
import shutil
import time

from lib import Options
from lib.colorer import color_stdout
Expand All @@ -16,6 +17,9 @@
from lib.utils import safe_makedirs
from lib.utils import print_tail_n
from lib.utils import print_unidiff
from lib.utils import proc_diskstats_supported
from lib.utils import get_vardir_device
from lib.utils import get_disk_bound_stat_busy


class BaseWatcher(object):
Expand All @@ -37,11 +41,23 @@ def __init__(self, get_logfile):
self.field_size = 60
self._sampler = sampler
self.duration_stats = dict()
self.vardir = Options().args.vardir
self.proc_diskstats_supported = proc_diskstats_supported(self.vardir)
if self.proc_diskstats_supported:
self.vardir_devname = get_vardir_device(self.vardir)
self.vardir_usage = dict()
self.vardir_data = dict()
self.failed_tasks = []
self.get_logfile = get_logfile
self.long_tasks = set()

def process_result(self, obj):
# Called only once on task run initiated.
if self.proc_diskstats_supported and isinstance(obj, WorkerCurrentTask):
task_id = (obj.task_name, obj.task_param)
self.vardir_usage[task_id], self.vardir_data[task_id] = get_disk_bound_stat_busy(
self.vardir_devname, [time.time(), 0])

if not isinstance(obj, WorkerTaskResult):
return

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

self.duration_stats[obj.task_id] = obj.duration
if self.proc_diskstats_supported:
self.vardir_usage[obj.task_id], self.vardir_data[obj.task_id] = \
get_disk_bound_stat_busy(self.vardir_devname,
self.vardir_data[obj.task_id])

def get_long_mark(self, task):
return '(long)' if task in self.long_tasks else ''
Expand Down Expand Up @@ -145,6 +165,42 @@ def print_duration(self, stats_dir):
self.duration_stats[task_id]))
fd.close()

# Disk usage.
def print_disk_usage_summary(self, stats_dir):
if not self.proc_diskstats_supported:
return

top_usage = 10

# Print to stdout disk usage statistics for all failed tasks.
if self.failed_tasks:
color_stdout('Disk usage of failed tests:\n', schema='info')
for task in self.failed_tasks:
task_id = task[0]
if task_id in self.vardir_usage:
color_stdout('* %3d %s %s\n' % (self.vardir_usage[task_id],
self.prettify_task_name(task_id).ljust(self.field_size),
self.get_long_mark(task_id)),
schema='info')

# Print to stdout disk usage statistics for some number of most it used tasks.
color_stdout('Top {} tests by disk usage:\n'.format(top_usage), schema='info')
results_sorted = sorted(self.vardir_usage.items(), key=lambda x: x[1], reverse=True)
for task_id, usage in results_sorted[:top_usage]:
color_stdout('* %3d %s %s\n' % (usage,
self.prettify_task_name(task_id).ljust(self.field_size),
self.get_long_mark(task_id)), schema='info')

color_stdout('-' * 81, "\n", schema='separator')

# Print disk usage statistics to '<vardir>/statistics/diskusage.log' file.
filepath = os.path.join(stats_dir, 'diskusage.log')
fd = open(filepath, 'w')
for task_id in self.vardir_usage:
fd.write("{} {}\n".format(self.prettify_task_name(task_id),
self.vardir_usage[task_id]))
fd.close()

def print_statistics(self):
"""Print statistics and results of testing."""
# Prepare standalone subpath '<vardir>/statistics' for statistics files.
Expand All @@ -153,6 +209,7 @@ def print_statistics(self):

self.print_rss_summary(stats_dir)
self.print_duration(stats_dir)
self.print_disk_usage_summary(stats_dir)

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