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