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