3
3
""" Receive Side Scaling tuning utility """
4
4
5
5
import re
6
+ import sys
6
7
from argparse import ArgumentParser
7
8
from os .path import join , exists
8
9
11
12
12
13
from netutils_linux_hardware .assessor_math import any2int
13
14
from netutils_linux_monitoring .numa import Numa
15
+ from netutils_linux_monitoring .colors import wrap , YELLOW , cpu_color , COLORS_NODE
14
16
15
17
MAX_QUEUE_PER_DEVICE = 16
16
18
17
19
18
20
class RSSLadder (object ):
19
21
""" Distributor of queues' interrupts by multiple CPUs """
20
22
21
- def __init__ (self ):
23
+ def __init__ (self , argv = None ):
22
24
interrupts_file = '/proc/interrupts'
25
+ if argv :
26
+ sys .argv = [sys .argv [0 ]] + argv
23
27
self .options = self .parse_options ()
24
28
lscpu_output = None
25
29
if self .options .test_dir :
26
30
interrupts_file = join (self .options .test_dir , "interrupts" )
27
31
lscpu_output_filename = join (self .options .test_dir , "lscpu_output" )
28
32
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>
31
34
# read() in both cases return <str>
32
35
if isinstance (lscpu_output , bytes ):
33
36
lscpu_output = str (lscpu_output )
@@ -36,24 +39,37 @@ def __init__(self):
36
39
self .interrupts = open (interrupts_file ).readlines ()
37
40
if not self .options .cpus : # no need to detect topology if user gave us cpu list
38
41
self .numa = Numa (lscpu_output = lscpu_output )
42
+ self .socket_detect ()
43
+ self .numa .devices = self .numa .node_dev_dict ([self .options .dev ], False )
39
44
for postfix in sorted (self .queue_postfixes_detect ()):
40
45
self .smp_affinity_list_apply (self .smp_affinity_list_make (postfix ))
41
46
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
+
42
53
@staticmethod
43
54
def parse_options ():
55
+ """
56
+ :return: parsed arguments
57
+ """
44
58
parser = ArgumentParser ()
45
59
parser .add_argument ('-t' , '--test-dir' , type = str ,
46
60
help = "Use prepared test dataset in TEST_DIR directory instead of /proc/interrupts." )
47
61
parser .add_argument ('-d' , '--dry-run' , help = "Don't write anything to smp_affinity_list." ,
48
62
action = 'store_true' , default = False )
63
+ parser .add_argument ('--no-color' , help = 'Disable all highlights' , dest = 'color' , action = 'store_false' ,
64
+ default = True )
49
65
parser .add_argument ('-o' , '--offset' , type = int , default = 0 ,
50
66
help = "If you have 2 NICs with 4 queues and 1 socket with 8 cpus, you may be want "
51
67
"distribution like this: eth0: [0, 1, 2, 3]; eth1: [4, 5, 6, 7]; "
52
68
"so run: rss-ladder-test eth0; rss-ladder-test --offset=4 eth1" )
53
69
parser .add_argument ('-c' , '--cpus' , help = 'Explicitly define list of CPUs for binding NICs queues' , type = int ,
54
70
nargs = '+' )
55
71
parser .add_argument ('dev' , type = str )
56
- parser .add_argument ('socket' , nargs = '?' , type = int , default = 0 )
72
+ parser .add_argument ('socket' , nargs = '?' , type = int )
57
73
return parser .parse_args ()
58
74
59
75
def queue_postfix_extract (self , line ):
@@ -96,13 +112,40 @@ def smp_affinity_list_make(self, postfix):
96
112
if queue_name :
97
113
yield any2int (line .split ()[0 ]), queue_name [0 ], rss_cpus .pop ()
98
114
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
+
99
133
def smp_affinity_list_apply (self , smp_affinity ):
100
134
"""
101
135
'* 4' is in case of NIC has more queues than socket has CPUs
102
136
:param smp_affinity: list of tuples(irq, queue_name, socket)
103
137
"""
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 )))
106
149
if self .options .dry_run :
107
150
continue
108
151
filename = "/proc/irq/{0}/smp_affinity_list" .format (irq )
0 commit comments