Skip to content

Added(server-info): collection of max/cur queue count info #199

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

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
96d7754
Added(server-info): collection of max/cur queue count info
strizhechenko Jan 24, 2018
e42da12
Fixed(server-info): --collect didn't collect queue data
strizhechenko Jan 26, 2018
d8589fe
Added(server-info): analyzing `ethtool -l` output to know about queues
strizhechenko Jan 27, 2018
d1ed0e9
Added(test-data): server info for Intel Core i5 + Intel I350 in mirror.
strizhechenko Jan 27, 2018
66782af
Refactoring(server-info): actual expected output and renamed it to .y…
strizhechenko Jan 27, 2018
ceeb82e
Refactoring(server-info): removed duplicating code for parsing buffer…
strizhechenko Jan 27, 2018
5f31377
Refactoring(server-info): simplified queue data processing
strizhechenko Jan 27, 2018
46d043e
Addded(server-info): CPU data is collecting if --net / --system flags…
strizhechenko Jan 27, 2018
28a3536
Fixed(server-info): don't show cpu if only --system / --net args given
strizhechenko Jan 27, 2018
5bf6eb0
Added(server-info): collection of device binding to NUMA node
strizhechenko Jan 27, 2018
a74eea6
Added(test-data): samples of NUMA-node data for network device.
strizhechenko Jan 27, 2018
a6c20d4
Refactoring(server-info): network routine moved from net.py to net_re…
strizhechenko Jan 28, 2018
02752e6
Refactoring(server-info): server main read simplified
strizhechenko Jan 28, 2018
6f0e093
Added(server-info): parsing numa-node file
strizhechenko Jan 28, 2018
79b4660
Fixed(server-info): added numa-node info to expected output of tests.
strizhechenko Jan 29, 2018
50c90e0
Fixed(server-info): print_ from six was missed
strizhechenko Jan 29, 2018
abb5486
Fixed(server-info): return 1 if no data about queues.
strizhechenko Feb 12, 2018
efd2d29
Merge branch 'master' into ethtool-queue-info
strizhechenko Feb 12, 2018
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
152 changes: 24 additions & 128 deletions netutils_linux_hardware/net.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
# coding=utf-8
import os

from six import iteritems, print_

from netutils_linux_hardware.grade import Grade
from netutils_linux_hardware.interrupts import IRQQueueCounter
from netutils_linux_hardware.parser import Parser, YAMLLike
from netutils_linux_hardware.net_read import ReaderNet
from netutils_linux_hardware.rate_math import extract
from netutils_linux_hardware.subsystem import Subsystem

Expand All @@ -17,15 +14,16 @@ def parse(self):
return ReaderNet(self.datadir, self.path).netdevs

def rate(self):
return self.map(self.__netdev, 'net')
return self.map(self.rate_device, 'net')

def __netdev(self, netdev):
def rate_device(self, netdev):
netdevinfo = extract(self.data, ['net', netdev])
queues = sum(
len(extract(netdevinfo, ['queues', x])) for x in ('rx', 'rxtx'))
queues = sum(len(extract(netdevinfo, ['queues', x])) for x in ('rx', 'rxtx'))
queues_ethtool = self.rate_queue_ethtool(netdevinfo)
buffers = netdevinfo.get('buffers') or {}
return self.folding.fold({
'queues': Grade.int(queues, 2, 8),
'queues_ethtool': Grade.int(queues_ethtool, 2, 8),
'driver': {
'mlx5_core': 10, # 7500 mbit/s
'mlx4_en': 9, # 6500 mbit/s
Expand All @@ -43,123 +41,21 @@ def __netdev(self, netdev):
}, self.folding.DEVICE)
}, self.folding.DEVICE)


class ReductorMirror(Parser):
@staticmethod
def parse(text):
lines = dict((line.split(' ', 1)) for line in text.strip().split('\n'))
for netdev, conf in iteritems(lines):
output = dict()
output['conf'] = dict()
output['conf']['vlan'], output['conf']['ip'] = conf.split()
output['conf']['vlan'] = output['conf']['vlan'] != '-'
if output['conf']['ip'] == '-':
output['conf']['ip'] = ''
lines[netdev] = output
return lines


class NetdevParser(Parser):
@staticmethod
def parse(netdev_keys):
"""
:param netdev_keys: list of device names
:return: dict[device_name] = {'vlan': bool; 'ip': ''}
"""
netdevs = dict()
for key in netdev_keys:
if key.count('.') == 1:
dev, _ = key.split('.')
elif key.count('.') == 0:
dev = key
else:
print_('QinQ not supported yet. Device: {0}'.format(key))
raise NotImplementedError
netdevs[dev] = dict()
netdevs[dev]['conf'] = {
'vlan': key.count('.') == 1,
'ip': '',
}
return netdevs


class BridgeOutput(NetdevParser):
@staticmethod
def parse(text):
""" # 0 - id, 1 - dev, 2 - _, 3 - state, 4 - _, 5 - details, 6 - _, 7 - mtu, 8 - _, 9 - master
:param text: # 3: eth1 state DOWN : <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 master br1 state disabled
:return: analyzed netdevs
"""
__dev__ = 1
netdevs_keys = [line.split()[__dev__] for line in text.strip().split('\n')]
return NetdevParser.parse(netdevs_keys)


class EthtoolFiles(NetdevParser):
def parse_file(self, filepath, **kwargs):
return self.parse(os.listdir(filepath))


class EthtoolBuffers(Parser):
@staticmethod
def parse(text):
buffers = [int(line.split()[1])
for line in text.strip().split('\n')
if line.startswith('RX:')]
if buffers and len(buffers) == 2:
return {
'max': buffers[0],
'cur': buffers[1],
}


class ReaderNet(object):
netdevs = None

def __init__(self, datadir, path):
self.datadir = datadir
self.path = path
self.net_dev_list()

def net_dev_mirror_info(self):
return ReductorMirror().parse_file_safe(self.path('mirror_info.conf'))

def net_dev_list_bridge(self):
return BridgeOutput().parse_file_safe(self.path('bridge_link'))

def net_dev_list_ethtool(self):
return EthtoolFiles().parse_file(self.path('ethtool/i'))

def net_dev_list_buffers(self):
for netdev in self.netdevs:
buffers_path = os.path.join(self.datadir, 'ethtool/g', netdev)
self.netdevs[netdev]['buffers'] = EthtoolBuffers().parse_file_safe(buffers_path)

def net_dev_list_drivers(self):
keys_required = (
'driver',
'version',
)
for netdev in self.netdevs:
driverfile = os.path.join(self.datadir, 'ethtool/i', netdev)
driverdata = YAMLLike().parse_file_safe(driverfile)
if driverdata:
driverdata = dict((key, v) for key, v in iteritems(driverdata) if key in keys_required)
else:
driverdata = dict()
self.netdevs[netdev]['driver'] = driverdata

def net_dev_list(self):
"""
Priority:
1. mirror_info.conf (collected only in case of reductor (master conf))
2. bridges output (collected only in case of reductor (manual conf))
3. ethtool information (non reductor case)
"""
self.netdevs = self.net_dev_mirror_info() or self.net_dev_list_bridge() or self.net_dev_list_ethtool()
if not self.netdevs:
return
self.net_dev_list_buffers()
self.net_dev_list_drivers()
interrupts = self.path('interrupts')
IRQQueueCounter().parse_file_safe(interrupts, netdevs=self.netdevs)
def rate_queue_ethtool(self, netdevinfo):
# TODO: numa belonging
# import json
# print(json.dumps(netdevinfo, indent=2))
# exit(0)
queues_ethtool = netdevinfo['queues_ethtool']
queues_type = 'rx'
if not queues_ethtool:
return 1
if len(queues_ethtool.keys()) == 1:
queues_type = list(queues_ethtool.keys())[0]
cpu_count = len(self.data.get('cpu').get('layout'))
# print(json.dumps(queues_ethtool, indent=2))
# print(json.dumps(self.data.get('cpu').get('layout'), indent=4))
queues_count = 0
if queues_type == 'combined' or queues_type == 'rx':
queues_count = queues_ethtool.get(queues_type).get('cur')
return int(queues_count / cpu_count * 10)
154 changes: 154 additions & 0 deletions netutils_linux_hardware/net_read.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# coding=utf-8
import os

from six import iteritems, print_

from netutils_linux_hardware.interrupts import IRQQueueCounter
from netutils_linux_hardware.parser import Parser, YAMLLike


class ReaderNet(object):
""" Reading data about network from multiple sources and merging them for every network device """
netdevs = None

def __init__(self, datadir, path):
self.datadir = datadir
self.path = path
self.net_dev_list()

def max_cur_parse(self, ethtool_key, parser, output_key):
for netdev in self.netdevs:
file_path = os.path.join(self.datadir, 'ethtool/{0}'.format(ethtool_key), netdev)
self.netdevs[netdev][output_key] = parser().parse_file_safe(file_path)

def net_dev_list_queues(self):
self.max_cur_parse('l', EthtoolQueues, 'queues_ethtool')
IRQQueueCounter().parse_file_safe(self.path('interrupts'), netdevs=self.netdevs)

def net_dev_list_buffers(self):
self.max_cur_parse('g', EthtoolBuffers, 'buffers')

def net_dev_list_drivers(self):
keys_required = (
'driver',
'version',
)
for netdev in self.netdevs:
driver_file = os.path.join(self.datadir, 'ethtool/i', netdev)
driver_data = YAMLLike().parse_file_safe(driver_file) or dict()
if driver_data:
driver_data = dict((key, v) for key, v in iteritems(driver_data) if key in keys_required)
self.netdevs[netdev]['driver'] = driver_data

def net_dev_list_numa(self):
for netdev in self.netdevs:
numa_file = os.path.join(self.datadir, 'sys/class/net/', netdev, 'device/numa_node')
self.netdevs[netdev]['numa_node'] = int(open(numa_file).read()) if os.path.exists(numa_file) else -1

def net_dev_list(self):
"""
Priority:
1. mirror_info.conf (collected only in case of reductor (master conf))
2. bridges output (collected only in case of reductor (manual conf))
3. ethtool information (non reductor case)
"""
self.netdevs = ReductorMirror().parse_file_safe(self.path('mirror_info.conf'))
if not self.netdevs:
self.netdevs = BridgeOutput().parse_file_safe(self.path('bridge_link'))
if not self.netdevs:
self.netdevs = EthtoolFiles().parse_file(self.path('ethtool/i'))
if self.netdevs:
self.net_dev_list_buffers()
self.net_dev_list_drivers()
self.net_dev_list_queues()
self.net_dev_list_numa()


class NetdevClassificator(Parser):
@staticmethod
def parse(netdev_keys):
"""
:param netdev_keys: list of device names
:return: dict[device_name] = {'vlan': bool; 'ip': ''}
"""
netdevs = dict()
for key in netdev_keys:
if key.count('.') == 1:
dev, _ = key.split('.')
elif key.count('.') == 0:
dev = key
else:
print_('QinQ not supported yet. Device: {0}'.format(key))
raise NotImplementedError
netdevs[dev] = dict()
netdevs[dev]['conf'] = {
'vlan': key.count('.') == 1,
'ip': '',
}
return netdevs


class ReductorMirror(Parser):
@staticmethod
def parse(text):
lines = dict((line.split(' ', 1)) for line in text.strip().split('\n'))
for netdev, conf in iteritems(lines):
output = dict()
output['conf'] = dict()
output['conf']['vlan'], output['conf']['ip'] = conf.split()
output['conf']['vlan'] = output['conf']['vlan'] != '-'
if output['conf']['ip'] == '-':
output['conf']['ip'] = ''
lines[netdev] = output
return lines


class BridgeOutput(NetdevClassificator):
@staticmethod
def parse(text):
""" # 0 - id, 1 - dev, 2 - _, 3 - state, 4 - _, 5 - details, 6 - _, 7 - mtu, 8 - _, 9 - master
:param text: # 3: eth1 state DOWN : <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 master br1 state disabled
:return: analyzed netdevs
"""
__dev__ = 1
netdevs_keys = [line.split()[__dev__] for line in text.strip().split('\n')]
return NetdevClassificator.parse(netdevs_keys)


class EthtoolFiles(NetdevClassificator):
def parse_file(self, filepath, **kwargs):
return self.parse(os.listdir(filepath))


class EthtoolBuffers(Parser):
@staticmethod
def parse(text):
buffers = [int(line.split()[1])
for line in text.strip().split('\n')
if line.startswith('RX:')]
if buffers and len(buffers) == 2:
return {
'max': buffers[0],
'cur': buffers[1],
}


class EthtoolQueues(Parser):
@staticmethod
def parse(text):
__queues = [int(line.split()[1]) for line in text.split('\n') if line.split()[1].isdigit()]
if len(__queues) != 8:
return
queues = {
'rx': {
'max': __queues[0],
'cur': __queues[4],
},
'combined': {
'max': __queues[3],
'cur': __queues[7],
},
}
if queues['rx']['max'] == 0 and queues['combined']['max'] != 0:
del queues['rx']
return queues
16 changes: 11 additions & 5 deletions netutils_linux_hardware/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,10 @@ def collect(self):

def read(self):
""" Parser of raw saved data info dictionary """
info = dict()
for key, subsystem in self.subsystems.items():
if key != 'system' and getattr(self.args, key):
info[key] = subsystem(datadir=self.directory).parse()
if not self.args.cpu and self.args.system:
info = self.__read()
# system requires cpu because virtualization data is in lscpu output
# net requires cpu because queue count rate depends on NUMA's core count
if self.args.rate and not self.args.cpu and (self.args.system or self.args.net):
info['cpu'] = CPU(datadir=self.directory).parse()
return info

Expand All @@ -79,6 +78,13 @@ def archive(self):
os.chdir(os.path.join(self.directory, '..'))
os.system('tar cfz {0} {1} 2>/dev/null'.format(self.tarball, self.directory))

def __read(self):
return dict(
(key, subsystem(datadir=self.directory).parse())
for key, subsystem in self.subsystems.items()
if key != 'system' and getattr(self.args, key)
)

def __parse_args(self):
default_directory = '/tmp/netutils_server_info/'
self.parser.add_argument('--directory', type=str, help="Specify a data directory or a tarball",
Expand Down
17 changes: 8 additions & 9 deletions tests/server-info-show
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ run_test() {
echo -n "# $name "
server-info --show --directory="$1" > $TMPDIR/output
if [ "${REWRITE:-0}" = '1' ]; then
server-info --show --directory="$1" > "$testdata/$name/expected_output"
server-info --show --directory="$1" > "$testdata/$name/expected_output.yml"
fi
cmp -s $1/expected_output $TMPDIR/output || rc=$?
cmp -s $1/expected_output.yml $TMPDIR/output || rc=$?
if [ "$rc" = '0' ]; then
echo OK
else
diff -U 5 "$testdata/$name/expected_output" "$TMPDIR/output"
diff -U 5 "$testdata/$name/expected_output.yml" "$TMPDIR/output"
echo FAIL
fi
return "$rc"
Expand All @@ -28,12 +28,11 @@ run_test() {
retval=0
# shellcheck disable=SC2044
for test in $(find "$testdata" -mindepth 1 -maxdepth 1 -type d); do
if [ -f "$test/expected_output" ]; then
if ! run_test "$test"; then
retval=1
break
fi
fi
[ -f "$test/expected_output.yml" ] || continue
run_test "$test" && continue
[ "${REWRITE:-0}" = '1' ] && continue
retval=1
break
done
rm -rf $TMPDIR
exit "$retval"
Loading