-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpin-irq.sh
More file actions
executable file
·191 lines (170 loc) · 6.44 KB
/
pin-irq.sh
File metadata and controls
executable file
·191 lines (170 loc) · 6.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#!/usr/bin/env bash
# pin-irq.sh — Pin NIC TX/RX IRQs and the scancraft process to the same CPU core.
#
# Usage:
# sudo ./pin-irq.sh [OPTIONS] -- [SCANCRAFT ARGS]
#
# Options:
# -i IFACE Network interface to optimise (default: auto-detect default route iface)
# -c CPU CPU core number to pin to (default: 2)
# -n Dry run — print commands without executing them
# -h Show this help
#
# Examples:
# sudo ./pin-irq.sh -i eth0 -c 3 -- --rate 10000000
# sudo ./pin-irq.sh -n -i eth0 -c 2 -- --rate 5000000
set -euo pipefail
# ---- defaults ----------------------------------------------------------------
IFACE=""
CPU=2
DRY_RUN=0
BINARY="./target/release/scancraft"
# ---- argument parsing --------------------------------------------------------
while getopts ":i:c:nh" opt; do
case $opt in
i) IFACE="$OPTARG" ;;
c) CPU="$OPTARG" ;;
n) DRY_RUN=1 ;;
h)
sed -n '2,/^$/p' "$0"
exit 0
;;
:) echo "Option -$OPTARG requires an argument." >&2; exit 1 ;;
\?) echo "Unknown option: -$OPTARG" >&2; exit 1 ;;
esac
done
shift $((OPTIND - 1))
# Remaining args after -- are passed to scancraft
SCANCRAFT_ARGS=("$@")
# ---- helpers -----------------------------------------------------------------
run() {
if [[ $DRY_RUN -eq 1 ]]; then
echo "[dry-run] $*"
else
echo "+ $*"
eval "$*"
fi
}
require_root() {
if [[ $EUID -ne 0 && $DRY_RUN -eq 0 ]]; then
echo "Error: must be run as root (use sudo, or -n for a dry run)." >&2
exit 1
fi
}
# ---- resolve interface -------------------------------------------------------
if [[ -z "$IFACE" ]]; then
IFACE=$(ip route show default | awk '/default/ { print $5; exit }')
if [[ -z "$IFACE" ]]; then
echo "Error: could not auto-detect default interface; use -i IFACE." >&2
exit 1
fi
echo "Auto-detected interface: $IFACE"
fi
# ---- validate CPU ------------------------------------------------------------
NUM_CPUS=$(nproc)
if (( CPU < 0 || CPU >= NUM_CPUS )); then
echo "Error: CPU $CPU is out of range (0–$((NUM_CPUS - 1)))." >&2
exit 1
fi
# IRQ affinity bitmask: bit N set = CPU N. CPU 2 → 0x4, CPU 3 → 0x8, etc.
IRQ_MASK=$(printf '%x' $((1 << CPU)))
require_root
# ---- show NUMA topology (informational) -------------------------------------
echo ""
echo "=== NUMA / PCI topology ==="
if command -v lscpu &>/dev/null; then
lscpu | grep -E "NUMA|Socket|Core" || true
fi
if [[ -f /sys/class/net/"$IFACE"/device/numa_node ]]; then
NUMA_NODE=$(cat /sys/class/net/"$IFACE"/device/numa_node)
if [[ "$NUMA_NODE" == "-1" || "$NUMA_NODE" == "" ]]; then
echo "NIC $IFACE: single-socket / non-NUMA system — any CPU is equidistant."
else
echo "NIC $IFACE is on NUMA node: $NUMA_NODE"
if command -v numactl &>/dev/null; then
echo "CPUs on that node: $(numactl --hardware | grep "node $NUMA_NODE cpus" | cut -d: -f2)"
fi
fi
fi
echo ""
# ---- warn if this looks like a WiFi interface --------------------------------
if [[ -d /sys/class/net/"$IFACE"/wireless ]]; then
echo "WARNING: $IFACE appears to be a WiFi interface."
echo " WiFi cannot sustain high pps due to CSMA/CA, ACK overhead, and"
echo " half-duplex behaviour. Use a wired (Ethernet) interface instead."
echo ""
fi
# ---- find IRQs for the interface --------------------------------------------
echo "=== IRQs for $IFACE ==="
# /proc/interrupts truncates driver names, so try progressively shorter prefixes.
# E.g. "enp6s0" may appear as "enp6s0", "enp6s", or just "enp6".
mapfile -t IRQ_LINES < <(grep -E "\b${IFACE}\b" /proc/interrupts || true)
if [[ ${#IRQ_LINES[@]} -eq 0 ]]; then
echo "Note: exact match not found; trying truncated name prefixes..."
for prefix_len in ${#IFACE} $((${#IFACE}-1)) $((${#IFACE}-2)) 5 4; do
[[ $prefix_len -lt 3 ]] && break
prefix="${IFACE:0:$prefix_len}"
mapfile -t IRQ_LINES < <(grep -E "${prefix}" /proc/interrupts || true)
if [[ ${#IRQ_LINES[@]} -gt 0 ]]; then
echo " Matched using prefix '${prefix}'."
break
fi
done
fi
if [[ ${#IRQ_LINES[@]} -eq 0 ]]; then
echo "Error: no IRQ lines matched for $IFACE." >&2
echo " Check /proc/interrupts manually — relevant lines:" >&2
grep -E "eth|eno|enp|ens|wlp|wlan" /proc/interrupts | head -20 || true
exit 1
fi
declare -a IRQS=()
for line in "${IRQ_LINES[@]}"; do
irq_num=$(echo "$line" | awk -F: '{ gsub(/ /, "", $1); print $1 }')
echo " IRQ $irq_num: $(echo "$line" | awk '{ $1=$2=$3=$4=$5=$6=$7=$8=$9=$10=$11=""; print }' | xargs)"
IRQS+=("$irq_num")
done
echo ""
# ---- pin each IRQ to the chosen CPU -----------------------------------------
echo "=== Pinning ${#IRQS[@]} IRQ(s) to CPU $CPU (affinity mask 0x$IRQ_MASK) ==="
for irq in "${IRQS[@]}"; do
affinity_file="/proc/irq/$irq/smp_affinity"
if [[ ! -f "$affinity_file" ]]; then
echo " Skipping IRQ $irq — $affinity_file not found."
continue
fi
run "echo $IRQ_MASK > $affinity_file"
if [[ $DRY_RUN -eq 0 ]]; then
actual=$(cat "$affinity_file")
echo " IRQ $irq affinity is now: $actual"
fi
done
echo ""
# ---- disable IRQ coalescing --------------------------------------------------
echo "=== Disabling IRQ coalescing on $IFACE ==="
if command -v ethtool &>/dev/null; then
run "ethtool -C $IFACE tx-usecs 0 tx-frames 1 rx-usecs 0 rx-frames 1"
else
echo " Warning: ethtool not found; skipping coalescing configuration."
fi
echo ""
# ---- optionally set RPS/RFS --------------------------------------------------
# Receive Packet Steering — steer all RX softirqs to our chosen CPU.
RPS_FILE="/sys/class/net/$IFACE/queues/rx-0/rps_cpus"
if [[ -f "$RPS_FILE" ]]; then
echo "=== Setting RPS on $IFACE rx-0 to CPU $CPU ==="
run "echo $IRQ_MASK > $RPS_FILE"
echo ""
fi
# ---- run scancraft pinned to the chosen CPU ----------------------------------
if [[ ${#SCANCRAFT_ARGS[@]} -gt 0 ]]; then
if [[ ! -x "$BINARY" ]]; then
echo "Warning: $BINARY not found or not executable; skipping launch."
echo " Build with: cargo build --release"
else
echo "=== Launching scancraft on CPU $CPU ==="
run "taskset -c $CPU $BINARY ${SCANCRAFT_ARGS[*]}"
fi
else
echo "No scancraft arguments given — skipping launch."
echo "To run: sudo ./pin-irq.sh -i $IFACE -c $CPU -- --rate 10000000"
fi