Skip to content

Commit 052f919

Browse files
Rss ladder warn (#127)
* Added(rss-ladder): warning about using few queues on same cpu. #113 * Added(rss-ladder): colorized output #117 * Added(rss-ladder): automatic socket detection #123 * Fixed(autorps): explicit passing --socket 0 didn't work and called socket_detection logic. * Fixed: reorder tests by time they take because they should fail as soon as possible. * Added(rss-ladder): docstrings everywhere * Changes(pylint): disabled C0111. * Fixed: pylint violations.
1 parent 545eb57 commit 052f919

File tree

17 files changed

+115
-59
lines changed

17 files changed

+115
-59
lines changed

.pylintrc

+5
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
[FORMAT]
2+
# why: because checks should be consistent with default pycharm settings
23
max-line-length=120
4+
5+
[MESSAGES CONTROL]
6+
# why: because I think module/class docstrings are generally useless, naming should be the documentation
7+
disable=C0111

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
test:
2+
pytest netutils_linux_*/
3+
./tests/utils_runnable
24
./tests/rss-ladder-test
35
./tests/server-info-show
46
./tests/link_rate_units.sh
5-
./tests/utils_runnable
6-
pytest netutils_linux_*/
77

88
env:
99
rm -rf env

netutils_linux_hardware/assessor_math.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
import math
66

77

8-
def round_(x, d=0):
9-
p = 10 ** d
10-
return float(math.floor((x * p) + math.copysign(0.5, x))) / p
8+
def round_(value, precision=0):
9+
"""
10+
:param value: float value
11+
:param precision: how much digits after ',' we need
12+
:return: rounded float value
13+
"""
14+
precision = 10 ** precision
15+
return float(math.floor((value * precision) + math.copysign(0.5, value))) / precision
1116

1217

1318
def extract(dictionary, key_sequence):
@@ -25,9 +30,9 @@ def any2int(value):
2530
elif value is None:
2631
return 0
2732
elif isinstance(value, str):
28-
v = re.sub(r'[^0-9]', '', value)
29-
if v.isdigit():
30-
return int(v)
33+
value = re.sub(r'[^0-9]', '', value)
34+
if value.isdigit():
35+
return int(value)
3136
elif isinstance(value, float):
3237
return int(value)
3338
return 0

netutils_linux_hardware/grade_test.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,16 @@ def test_grade_str(self):
3333
"Dlink": 1,
3434
"Realtek": 1,
3535
}
36-
for k, v in iteritems(expected):
37-
self.assertEqual(Grade.str(k, good, bad), v)
36+
for k, value in iteritems(expected):
37+
self.assertEqual(Grade.str(k, good, bad), value)
3838

3939
def test_grade_fact(self):
4040
self.assertEqual(Grade.fact(None, True), 1)
41-
self.assertEqual(Grade.fact(None, False), 10)
41+
self.assertEqual(Grade.fact(None), 10)
4242
self.assertEqual(Grade.fact("Anything", True), 10)
43-
self.assertEqual(Grade.fact("Anything", False), 1)
43+
self.assertEqual(Grade.fact("Anything"), 1)
4444
self.assertEqual(Grade.fact(15, True), 10)
45-
self.assertEqual(Grade.fact(15, False), 1)
45+
self.assertEqual(Grade.fact(15), 1)
4646
self.assertEqual(Grade.fact({'x': 'y'}, True), 10)
4747
self.assertEqual(Grade.fact({}, True), 10)
4848

netutils_linux_monitoring/layout.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@
88

99
def make_table(header, align_map=None, rows=None):
1010
""" Wrapper for pretty table """
11-
t = PrettyTable()
12-
t.horizontal_char = t.vertical_char = t.junction_char = ' '
13-
t.field_names = header
11+
table = PrettyTable()
12+
table.horizontal_char = table.vertical_char = table.junction_char = ' '
13+
table.field_names = header
1414
if align_map:
1515
for field, align in zip(header, align_map):
16-
t.align[field] = align
16+
table.align[field] = align
1717
if rows:
1818
for row in rows:
19-
if len(row) < len(t.field_names):
19+
if len(row) < len(table.field_names):
2020
continue
2121
try:
22-
t.add_row(row)
22+
table.add_row(row)
2323
except Exception as err:
24-
print_('fields:', t.field_names)
24+
print_('fields:', table.field_names)
2525
print_('row:', row)
2626
print_('rows:', rows)
2727
raise err
28-
return t
28+
return table

netutils_linux_monitoring/link_rate.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def devices_list(self):
135135
devices = self.options.devices.split(',')
136136
else:
137137
devices = self.devices_list_regex()
138-
return list(filter(self.devices_list_validate, devices))
138+
return [dev for dev in devices if self.devices_list_validate(dev)]
139139

140140
def post_optparse(self):
141141
""" Asserting and applying parsing options """

netutils_linux_monitoring/numa.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ def dev_node(self, dev, fake=False):
5858
filename = '/sys/class/net/{0}/device/numa_node'.format(dev)
5959
if not os.path.isfile(filename):
6060
return -1
61-
with open(filename) as fd:
62-
return int(fd.read().strip())
61+
with open(filename) as dev_file:
62+
return int(dev_file.read().strip())
6363

6464
def detect_layouts_fallback(self):
6565
"""
@@ -96,7 +96,7 @@ def detect_layouts(self, lscpu_output=None):
9696
""" Determine NUMA and sockets layout """
9797
stdout = self.detect_layout_lscpu(lscpu_output)
9898
rows = [row for row in stdout.strip().split('\n') if not row.startswith('#')]
99-
layouts = [list(map(any2int, row.split(',')[2:4])) for row in rows]
99+
layouts = [[any2int(value) for value in row.split(',')][2:4] for row in rows]
100100
numa_layout, socket_layout = zip(*layouts)
101101
self.numa_layout = dict(enumerate(numa_layout))
102102
self.socket_layout = dict(enumerate(socket_layout))

netutils_linux_monitoring/softirqs.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,9 @@ def post_optparse(self):
3030
self.numa = Numa(fake=self.options.random)
3131

3232
def parse(self):
33-
with open(self.options.softirqs_file) as fd:
34-
metrics = [line.strip().split(':')
35-
for line in fd.readlines() if ':' in line]
36-
return dict((k, list(map(int, v.strip().split()))) for k, v in metrics)
33+
with open(self.options.softirqs_file) as softirq_file:
34+
metrics = [line.strip().split(':') for line in softirq_file.readlines() if ':' in line]
35+
return dict((k, [int(d) for d in v.strip().split()]) for k, v in metrics)
3736

3837
@staticmethod
3938
def __active_cpu_count__(data):

netutils_linux_monitoring/softnet_stat.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,7 @@ def make_rows(self):
9393
colorize(stat.time_squeeze, self.time_squeeze_warning, self.time_squeeze_error),
9494
colorize(stat.cpu_collision, self.cpu_collision_warning, self.cpu_collision_error),
9595
stat.received_rps
96-
]
97-
for stat in self.repr_source()
98-
]
96+
] for stat in self.repr_source()]
9997

10098
def __repr__(self):
10199
table = make_table(self.make_header(), self.align, list(self.make_rows()))

netutils_linux_tuning/__init__.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
from netutils_linux_tuning.rx_buffers import RxBuffersIncreaser
2+
from netutils_linux_tuning.rss_ladder import RSSLadder
3+
from netutils_linux_tuning.autorps import AutoRPS
24

3-
__all__ = ['RxBuffersIncreaser']
5+
__all__ = ['RxBuffersIncreaser', 'AutoRPS', 'RSSLadder']

netutils_linux_tuning/autorps.py

+12-8
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ def __init__(self):
1414
self.options = self.parse_options()
1515
self.numa = self.make_numa()
1616
self.process_options()
17-
self.mask_apply()
17+
queues = self.detect_queues()
18+
self.mask_apply(queues)
1819

1920
def socket_detect(self):
20-
if any([self.options.socket, self.options.cpus, self.options.cpu_mask]):
21+
if any([self.options.socket is not None, self.options.cpus, self.options.cpu_mask]):
2122
return
2223
socket = self.numa.node_dev_dict([self.options.dev], True).get(self.options.dev)
2324
self.options.socket = 0 if socket == -1 else socket
@@ -42,7 +43,7 @@ def cpus2mask(cpus, cpus_count):
4243
bitmap = [0] * cpus_count
4344
for cpu in cpus:
4445
bitmap[cpu] = 1
45-
return hex(int("".join(map(str, bitmap)), 2))[2:]
46+
return hex(int("".join([str(cpu) for cpu in bitmap]), 2))[2:] # no need to write 0x
4647

4748
def mask_detect(self):
4849
if self.options.cpu_mask:
@@ -55,16 +56,19 @@ def process_options(self):
5556
self.socket_detect()
5657
self.mask_detect()
5758

58-
def detect_queues(self, queue_dir):
59+
def detect_queues_real(self):
60+
queue_dir = "/sys/class/net/{0}/queues/".format(self.options.dev)
5961
return [queue for queue in os.listdir(queue_dir) if queue.startswith('rx')]
6062

61-
def mask_apply(self):
63+
def detect_queues(self):
6264
if self.options.test_dir:
63-
self.mask_apply_test()
64-
queue_dir = "/sys/class/net/{0}/queues/".format(self.options.dev)
65-
queues = self.detect_queues(queue_dir)
65+
return ['rx-0']
66+
return self.detect_queues_real()
67+
68+
def mask_apply(self, queues):
6669
if len(queues) > 1 and not self.options.force:
6770
raise OSError("Refuse to use RPS on multiqueue NIC. You may use --force flag to apply RPS for all queues")
71+
queue_dir = "/sys/class/net/{0}/queues/".format(self.options.dev)
6872
for queue in queues:
6973
print_("Using mask '{0}' for {1}-{2}".format(self.options.cpu_mask, self.options.dev, queue))
7074
if self.options.dry_run:

netutils_linux_tuning/rss_ladder.py

+49-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
""" Receive Side Scaling tuning utility """
44

55
import re
6+
import sys
67
from argparse import ArgumentParser
78
from os.path import join, exists
89

@@ -11,23 +12,25 @@
1112

1213
from netutils_linux_hardware.assessor_math import any2int
1314
from netutils_linux_monitoring.numa import Numa
15+
from netutils_linux_monitoring.colors import wrap, YELLOW, cpu_color, COLORS_NODE
1416

1517
MAX_QUEUE_PER_DEVICE = 16
1618

1719

1820
class RSSLadder(object):
1921
""" Distributor of queues' interrupts by multiple CPUs """
2022

21-
def __init__(self):
23+
def __init__(self, argv=None):
2224
interrupts_file = '/proc/interrupts'
25+
if argv:
26+
sys.argv = [sys.argv[0]] + argv
2327
self.options = self.parse_options()
2428
lscpu_output = None
2529
if self.options.test_dir:
2630
interrupts_file = join(self.options.test_dir, "interrupts")
2731
lscpu_output_filename = join(self.options.test_dir, "lscpu_output")
2832
lscpu_output = open(lscpu_output_filename).read()
29-
# Popen.stdout in python 2.7 returns <str>
30-
# Popen.stdout in python 3.6 returns <bytes>
33+
# Popen.stdout: python 2.7 returns <str>, 3.6 returns <bytes>
3134
# read() in both cases return <str>
3235
if isinstance(lscpu_output, bytes):
3336
lscpu_output = str(lscpu_output)
@@ -36,24 +39,37 @@ def __init__(self):
3639
self.interrupts = open(interrupts_file).readlines()
3740
if not self.options.cpus: # no need to detect topology if user gave us cpu list
3841
self.numa = Numa(lscpu_output=lscpu_output)
42+
self.socket_detect()
43+
self.numa.devices = self.numa.node_dev_dict([self.options.dev], False)
3944
for postfix in sorted(self.queue_postfixes_detect()):
4045
self.smp_affinity_list_apply(self.smp_affinity_list_make(postfix))
4146

47+
def socket_detect(self):
48+
if any([self.options.socket is not None, self.options.cpus]):
49+
return
50+
socket = self.numa.node_dev_dict([self.options.dev], True).get(self.options.dev)
51+
self.options.socket = 0 if socket == -1 else socket
52+
4253
@staticmethod
4354
def parse_options():
55+
"""
56+
:return: parsed arguments
57+
"""
4458
parser = ArgumentParser()
4559
parser.add_argument('-t', '--test-dir', type=str,
4660
help="Use prepared test dataset in TEST_DIR directory instead of /proc/interrupts.")
4761
parser.add_argument('-d', '--dry-run', help="Don't write anything to smp_affinity_list.",
4862
action='store_true', default=False)
63+
parser.add_argument('--no-color', help='Disable all highlights', dest='color', action='store_false',
64+
default=True)
4965
parser.add_argument('-o', '--offset', type=int, default=0,
5066
help="If you have 2 NICs with 4 queues and 1 socket with 8 cpus, you may be want "
5167
"distribution like this: eth0: [0, 1, 2, 3]; eth1: [4, 5, 6, 7]; "
5268
"so run: rss-ladder-test eth0; rss-ladder-test --offset=4 eth1")
5369
parser.add_argument('-c', '--cpus', help='Explicitly define list of CPUs for binding NICs queues', type=int,
5470
nargs='+')
5571
parser.add_argument('dev', type=str)
56-
parser.add_argument('socket', nargs='?', type=int, default=0)
72+
parser.add_argument('socket', nargs='?', type=int)
5773
return parser.parse_args()
5874

5975
def queue_postfix_extract(self, line):
@@ -96,13 +112,40 @@ def smp_affinity_list_make(self, postfix):
96112
if queue_name:
97113
yield any2int(line.split()[0]), queue_name[0], rss_cpus.pop()
98114

115+
def dev_colorize(self):
116+
"""
117+
:return: highlighted by NUMA-node name of the device
118+
"""
119+
if not self.numa or not self.options.color:
120+
return self.options.dev
121+
color = COLORS_NODE.get(self.numa.devices.get(self.options.dev))
122+
return wrap(self.options.dev, color)
123+
124+
def cpu_colorize(self, cpu):
125+
"""
126+
:param cpu: cpu number (0)
127+
:return: highlighted by NUMA-node cpu number.
128+
"""
129+
if not self.numa or not self.options.color:
130+
return cpu
131+
return wrap(cpu, cpu_color(cpu, numa=self.numa))
132+
99133
def smp_affinity_list_apply(self, smp_affinity):
100134
"""
101135
'* 4' is in case of NIC has more queues than socket has CPUs
102136
:param smp_affinity: list of tuples(irq, queue_name, socket)
103137
"""
104-
for irq, queue_name, socket_cpu in smp_affinity:
105-
print_(" - {0}: irq {1} {2} -> {3}".format(self.options.dev, irq, queue_name, socket_cpu))
138+
affinity = list(smp_affinity)
139+
cpus = [socket_cpu for irq, queue, socket_cpu in affinity]
140+
if len(set(cpus)) != len(cpus):
141+
warning = "WARNING: some CPUs process multiple queues, consider reduce queue count for this network device"
142+
if self.options.color:
143+
print_(wrap(warning, YELLOW))
144+
else:
145+
print_(warning)
146+
for irq, queue_name, socket_cpu in affinity:
147+
print_(" - {0}: irq {1} {2} -> {3}".format(
148+
self.dev_colorize(), irq, queue_name, self.cpu_colorize(socket_cpu)))
106149
if self.options.dry_run:
107150
continue
108151
filename = "/proc/irq/{0}/smp_affinity_list".format(irq)

netutils_linux_tuning/rss_ladder_test.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
class RSSLadderTests(TestCase):
88

99
def setUp(self):
10-
self.rss = RSSLadder()
11-
self.rss.options.dev = 'eth0'
10+
self.rss = RSSLadder(argv=['--dry-run', 'eth0'])
1211

1312
def test_queue_postfix_extract(self):
1413
line = "31312 0 0 0 blabla eth0-TxRx-0"

netutils_linux_tuning/rx_buffers.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ def __str__(self):
2727

2828
def investigate(self):
2929
""" get maximum and current rx ring buffers values via ethtool """
30-
def extract_value(s):
31-
return int(s.strip('RX:\t\n'))
30+
def extract_value(line):
31+
return int(line.strip('RX:\t\n'))
3232

3333
# We need to be sure that on RHEL system we don't automatically tune buffers
3434
# that have been already manually tuned. In non RHEL system we skip this checks.
35-
ns = '/etc/sysconfig/network-scripts/'
36-
if path.exists(ns): # don't check ifcfg on non RHEL-based systems.
37-
config_file = path.join(ns, 'ifcfg-' + self.dev)
35+
network_scripts = '/etc/sysconfig/network-scripts/'
36+
if path.exists(network_scripts): # don't check ifcfg on non RHEL-based systems.
37+
config_file = path.join(network_scripts, 'ifcfg-' + self.dev)
3838
if path.exists(config_file):
3939
with open(config_file) as config:
4040
if any(line for line in config.readlines() if 'ETHTOOL_OPTS' in line):

tests/rss-ladder-test

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ run_test() {
1212
cp $testdata/$name/interrupts ./interrupts || return 1
1313
cp $testdata/$name/lscpu_output ./lscpu_output || return 2
1414
read -a params < $testdata/$name/params
15-
rss-ladder --dry-run --test-dir $testdata/$name "${params[@]}" > ./output || return 3
15+
rss-ladder --no-color --dry-run --test-dir $testdata/$name "${params[@]}" > ./output || return 3
1616
cmp -s $testdata/$name/expected output || rc=$?
1717
if [ "$rc" = '0' ]; then
1818
echo OK

tests/rss-ladder.tests/ixgbe.E5645/expected

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
- distribute interrupts of eth1 (-TxRx-) on socket 1
2+
WARNING: some CPUs process multiple queues, consider reduce queue count for this network device
23
- eth1: irq 75 eth1-TxRx-0 -> 6
34
- eth1: irq 76 eth1-TxRx-1 -> 7
45
- eth1: irq 77 eth1-TxRx-2 -> 8

0 commit comments

Comments
 (0)