netifd: add a packet steering mode matching the old script
[openwrt/staging/nbd.git] / package / network / config / netifd / files / usr / libexec / network / packet-steering.uc
1 #!/usr/bin/env ucode
2 'use strict';
3 import { glob, basename, dirname, readlink, readfile, realpath, writefile, error, open } from "fs";
4
5 let napi_weight = 1.0;
6 let cpu_thread_weight = 0.75;
7 let rx_weight = 0.75;
8 let eth_bias = 2.0;
9 let debug = 0, do_nothing = 0;
10 let disable;
11 let cpus;
12 let all_cpus;
13
14 for (let arg in ARGV) {
15 switch (arg) {
16 case "-d":
17 debug++;
18 break;
19 case "-n":
20 do_nothing++;
21 break;
22 case '0':
23 disable = true;
24 break;
25 case '2':
26 all_cpus = true;
27 break;
28 }
29 }
30
31 function task_name(pid)
32 {
33 let stat = open(`/proc/${pid}/status`, "r");
34 if (!stat)
35 return;
36 let line = stat.read("line");
37 stat.close();
38 return trim(split(line, "\t", 2)[1]);
39 }
40
41 function set_task_cpu(pid, cpu) {
42 if (disable)
43 cpu = join(",", map(cpus, (cpu) => cpu.id));
44 let name = task_name(pid);
45 if (!name)
46 return;
47 if (debug || do_nothing)
48 warn(`taskset -p -c ${cpu} ${name}\n`);
49 if (!do_nothing)
50 system(`taskset -p -c ${cpu} ${pid}`);
51 }
52
53 function cpu_mask(cpu)
54 {
55 let mask;
56 if (cpu < 0)
57 mask = (1 << length(cpus)) - 1;
58 else
59 mask = (1 << int(cpu));
60 return sprintf("%x", mask);
61 }
62
63 function set_netdev_cpu(dev, cpu) {
64 let queues = glob(`/sys/class/net/${dev}/queues/rx-*/rps_cpus`);
65 let val = cpu_mask(cpu);
66 if (disable)
67 val = 0;
68 for (let queue in queues) {
69 if (debug || do_nothing)
70 warn(`echo ${val} > ${queue}\n`);
71 if (!do_nothing)
72 writefile(queue, `${val}`);
73 }
74 }
75
76 function task_device_match(name, device)
77 {
78 let napi_match = match(name, /napi\/([^-+])-\d+/);
79 if (!napi_match)
80 napi_match = match(name, /mt76-tx (phy\d+)/);
81 if (napi_match &&
82 (index(device.phy, napi_match[1]) >= 0 ||
83 index(device.netdev, napi_match[1]) >= 0))
84 return true;
85
86 if (device.driver == "mtk_soc_eth" && match(name, /napi\/mtk_eth-/))
87 return true;
88
89 return false;
90 }
91
92 cpus = map(glob("/sys/bus/cpu/devices/*"), (path) => {
93 return {
94 id: int(match(path, /.*cpu(\d+)/)[1]),
95 core: int(trim(readfile(`${path}/topology/core_id`))),
96 load: 0.0,
97 };
98 });
99
100 cpus = slice(cpus, 0, 64);
101 if (length(cpus) < 2)
102 exit(0);
103
104 function cpu_add_weight(cpu_id, weight)
105 {
106 let cpu = cpus[cpu_id];
107 cpu.load += weight;
108 for (let sibling in cpus) {
109 if (sibling == cpu || sibling.core != cpu.core)
110 continue;
111 sibling.load += weight * cpu_thread_weight;
112 }
113 }
114
115 function get_next_cpu(weight, prev_cpu)
116 {
117 if (disable)
118 return 0;
119
120 let sort_cpus = sort(slice(cpus), (a, b) => a.load - b.load);
121 let idx = 0;
122
123 if (prev_cpu != null && sort_cpus[idx].id == prev_cpu)
124 idx++;
125
126 let cpu = sort_cpus[idx].id;
127 cpu_add_weight(cpu, weight);
128 return cpu;
129 }
130
131 let phys_devs = {};
132 let netdev_phys = {};
133 let netdevs = map(glob("/sys/class/net/*"), (dev) => basename(dev));
134
135 for (let dev in netdevs) {
136 let pdev_path = realpath(`/sys/class/net/${dev}/device`);
137 if (!pdev_path)
138 continue;
139
140 if (length(glob(`/sys/class/net/${dev}/lower_*`)) > 0)
141 continue;
142
143 let pdev = phys_devs[pdev_path];
144 if (!pdev) {
145 pdev = phys_devs[pdev_path] = {
146 path: pdev_path,
147 driver: basename(readlink(`${pdev_path}/driver`)),
148 netdev: [],
149 phy: [],
150 tasks: [],
151 };
152 }
153
154 let phyidx = trim(readfile(`/sys/class/net/${dev}/phy80211/index`));
155 if (phyidx != null) {
156 let phy = `phy${phyidx}`;
157 if (index(pdev.phy, phy) < 0)
158 push(pdev.phy, phy);
159 }
160
161 push(pdev.netdev, dev);
162 netdev_phys[dev] = pdev;
163 }
164
165 for (let path in glob("/proc/*/exe")) {
166 readlink(path);
167 if (error() != "No such file or directory")
168 continue;
169
170 let pid = basename(dirname(path));
171 let name = task_name(pid);
172 for (let devname in phys_devs) {
173 let dev = phys_devs[devname];
174 if (!task_device_match(name, dev))
175 continue;
176
177 push(dev.tasks, pid);
178 break;
179 }
180 }
181
182 function assign_dev_cpu(dev) {
183 if (length(dev.tasks) > 0) {
184 let cpu = dev.napi_cpu = get_next_cpu(napi_weight);
185 for (let task in dev.tasks)
186 set_task_cpu(task, cpu);
187 }
188
189 if (length(dev.netdev) > 0) {
190 let cpu;
191 if (all_cpus)
192 cpu = -1;
193 else
194 cpu = get_next_cpu(rx_weight, dev.napi_cpu);
195 dev.rx_cpu = cpu;
196 for (let netdev in dev.netdev)
197 set_netdev_cpu(netdev, cpu);
198 }
199 }
200
201 // Assign ethernet devices first
202 for (let devname in phys_devs) {
203 let dev = phys_devs[devname];
204 if (!length(dev.phy))
205 assign_dev_cpu(dev);
206 }
207
208 // Add bias to avoid assigning other tasks to CPUs with ethernet NAPI
209 for (let devname in phys_devs) {
210 let dev = phys_devs[devname];
211 if (!length(dev.tasks) || dev.napi_cpu == null)
212 continue;
213 cpu_add_weight(dev.napi_cpu, eth_bias);
214 }
215
216 // Assign WLAN devices
217 for (let devname in phys_devs) {
218 let dev = phys_devs[devname];
219 if (length(dev.phy) > 0)
220 assign_dev_cpu(dev);
221 }
222
223 if (debug > 1)
224 warn(sprintf("devices: %.J\ncpus: %.J\n", phys_devs, cpus));