]>
Commit | Line | Data |
---|---|---|
5407f897 QY |
1 | /* |
2 | * uclampset.c - change utilization clamping attributes of a task or the system | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License, version 2, as | |
6 | * published by the Free Software Foundation | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, | |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | * GNU General Public License for more details. | |
12 | * | |
13 | * You should have received a copy of the GNU General Public License along | |
14 | * with this program; if not, write to the Free Software Foundation, Inc., | |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
16 | * | |
17 | * Copyright (C) 2020-2021 Qais Yousef | |
18 | * Copyright (C) 2020-2021 Arm Ltd | |
19 | */ | |
20 | ||
21 | #include <errno.h> | |
22 | #include <getopt.h> | |
23 | #include <sched.h> | |
24 | #include <stdio.h> | |
25 | #include <stdlib.h> | |
26 | ||
27 | #include "closestream.h" | |
28 | #include "path.h" | |
29 | #include "pathnames.h" | |
602f734a | 30 | #include "procfs.h" |
5407f897 QY |
31 | #include "sched_attr.h" |
32 | #include "strutils.h" | |
33 | ||
34 | #define NOT_SET -2U | |
35 | ||
36 | struct uclampset { | |
37 | unsigned int util_min; | |
38 | unsigned int util_max; | |
39 | ||
40 | pid_t pid; | |
41 | unsigned int all_tasks:1, /* all threads of the PID */ | |
42 | system:1, | |
43 | util_min_set:1, /* indicates -m option was passed */ | |
44 | util_max_set:1, /* indicates -M option was passed */ | |
45 | reset_on_fork:1, | |
46 | verbose:1; | |
47 | char *cmd; | |
48 | }; | |
49 | ||
50 | static void __attribute__((__noreturn__)) usage(void) | |
51 | { | |
52 | FILE *out = stdout; | |
53 | ||
f4b07719 | 54 | fputs(USAGE_HEADER, out); |
5407f897 QY |
55 | fprintf(out, |
56 | _(" %1$s [options]\n" | |
57 | " %1$s [options] --pid <pid> | --system | <command> <arg>...\n"), | |
58 | program_invocation_short_name); | |
59 | ||
60 | fputs(USAGE_SEPARATOR, out); | |
f4b07719 | 61 | fputs(_("Show or change the utilization clamping attributes.\n"), out); |
5407f897 QY |
62 | |
63 | fputs(USAGE_OPTIONS, out); | |
f4b07719 KZ |
64 | fputs(_(" -m <value> util_min value to set\n"), out); |
65 | fputs(_(" -M <value> util_max value to set\n"), out); | |
5407f897 | 66 | fputs(_(" -a, --all-tasks operate on all the tasks (threads) for a given pid\n"), out); |
f4b07719 | 67 | fputs(_(" -p, --pid <pid> operate on existing given pid\n"), out); |
5407f897 QY |
68 | fputs(_(" -s, --system operate on system\n"), out); |
69 | fputs(_(" -R, --reset-on-fork set reset-on-fork flag\n"), out); | |
70 | fputs(_(" -v, --verbose display status information\n"), out); | |
71 | ||
5407f897 QY |
72 | printf(USAGE_HELP_OPTIONS(22)); |
73 | ||
f4b07719 KZ |
74 | fputs(USAGE_SEPARATOR, out); |
75 | fputs(_("Utilization value range is [0:1024]. Use special -1 value to " | |
76 | "reset to system's default.\n"), out); | |
77 | ||
5407f897 QY |
78 | printf(USAGE_MAN_TAIL("uclampset(1)")); |
79 | exit(EXIT_SUCCESS); | |
80 | } | |
81 | ||
82 | static void show_uclamp_pid_info(pid_t pid, char *cmd) | |
83 | { | |
84 | struct sched_attr sa; | |
85 | char *comm; | |
86 | ||
87 | /* don't display "pid 0" as that is confusing */ | |
88 | if (!pid) | |
89 | pid = getpid(); | |
90 | ||
91 | if (sched_getattr(pid, &sa, sizeof(sa), 0) != 0) | |
92 | err(EXIT_FAILURE, _("failed to get pid %d's uclamp values"), pid); | |
93 | ||
94 | if (cmd) | |
95 | comm = cmd; | |
96 | else | |
602f734a | 97 | comm = pid_get_cmdname(pid); |
5407f897 QY |
98 | |
99 | printf(_("%s (%d) util_clamp: min: %d max: %d\n"), | |
993556aa | 100 | comm ? : "unknown", pid, sa.sched_util_min, sa.sched_util_max); |
5407f897 QY |
101 | |
102 | if (!cmd) | |
103 | free(comm); | |
104 | } | |
105 | ||
106 | static unsigned int read_uclamp_sysfs(char *filename) | |
107 | { | |
108 | unsigned int val; | |
109 | ||
110 | if (ul_path_read_u32(NULL, &val, filename) != 0) | |
111 | err(EXIT_FAILURE, _("cannot read %s"), filename); | |
112 | ||
113 | return val; | |
114 | } | |
115 | ||
116 | static void write_uclamp_sysfs(char *filename, unsigned int val) | |
117 | { | |
118 | if (ul_path_write_u64(NULL, val, filename) != 0) | |
119 | err(EXIT_FAILURE, _("cannot write %s"), filename); | |
120 | } | |
121 | ||
122 | static void show_uclamp_system_info(void) | |
123 | { | |
124 | unsigned int min, max; | |
125 | ||
126 | min = read_uclamp_sysfs(_PATH_PROC_UCLAMP_MIN); | |
127 | max = read_uclamp_sysfs(_PATH_PROC_UCLAMP_MAX); | |
128 | ||
129 | printf(_("System util_clamp: min: %u max: %u\n"), min, max); | |
130 | } | |
131 | ||
132 | static void show_uclamp_info(struct uclampset *ctl) | |
133 | { | |
134 | if (ctl->system) { | |
135 | show_uclamp_system_info(); | |
136 | } else if (ctl->all_tasks) { | |
602f734a | 137 | DIR *sub = NULL; |
5407f897 | 138 | pid_t tid; |
602f734a | 139 | struct path_cxt *pc = ul_new_procfs_path(ctl->pid, NULL); |
5407f897 | 140 | |
602f734a | 141 | if (!pc) |
5407f897 QY |
142 | err(EXIT_FAILURE, _("cannot obtain the list of tasks")); |
143 | ||
602f734a | 144 | while (procfs_process_next_tid(pc, &sub, &tid) == 0) |
5407f897 QY |
145 | show_uclamp_pid_info(tid, NULL); |
146 | ||
602f734a | 147 | ul_unref_path(pc); |
5407f897 QY |
148 | } else { |
149 | show_uclamp_pid_info(ctl->pid, ctl->cmd); | |
150 | } | |
151 | } | |
152 | ||
153 | static int set_uclamp_one(struct uclampset *ctl, pid_t pid) | |
154 | { | |
155 | struct sched_attr sa; | |
156 | ||
157 | if (sched_getattr(pid, &sa, sizeof(sa), 0) != 0) | |
158 | err(EXIT_FAILURE, _("failed to get pid %d's uclamp values"), pid); | |
159 | ||
160 | if (ctl->util_min_set) | |
161 | sa.sched_util_min = ctl->util_min; | |
162 | if (ctl->util_max_set) | |
163 | sa.sched_util_max = ctl->util_max; | |
164 | ||
165 | sa.sched_flags = SCHED_FLAG_KEEP_POLICY | | |
166 | SCHED_FLAG_KEEP_PARAMS | | |
167 | SCHED_FLAG_UTIL_CLAMP_MIN | | |
168 | SCHED_FLAG_UTIL_CLAMP_MAX; | |
169 | ||
170 | if (ctl->reset_on_fork) | |
171 | sa.sched_flags |= SCHED_FLAG_RESET_ON_FORK; | |
172 | ||
173 | return sched_setattr(pid, &sa, 0); | |
174 | } | |
175 | ||
176 | static void set_uclamp_pid(struct uclampset *ctl) | |
177 | { | |
178 | if (ctl->all_tasks) { | |
602f734a | 179 | DIR *sub = NULL; |
5407f897 | 180 | pid_t tid; |
602f734a | 181 | struct path_cxt *pc = ul_new_procfs_path(ctl->pid, NULL); |
5407f897 | 182 | |
602f734a | 183 | if (!pc) |
5407f897 QY |
184 | err(EXIT_FAILURE, _("cannot obtain the list of tasks")); |
185 | ||
602f734a | 186 | while (procfs_process_next_tid(pc, &sub, &tid) == 0) { |
5407f897 QY |
187 | if (set_uclamp_one(ctl, tid) == -1) |
188 | err(EXIT_FAILURE, _("failed to set tid %d's uclamp values"), tid); | |
602f734a KZ |
189 | } |
190 | ul_unref_path(pc); | |
5407f897 QY |
191 | |
192 | } else if (set_uclamp_one(ctl, ctl->pid) == -1) { | |
193 | err(EXIT_FAILURE, _("failed to set pid %d's uclamp values"), ctl->pid); | |
194 | } | |
195 | } | |
196 | ||
197 | static void set_uclamp_system(struct uclampset *ctl) | |
198 | { | |
199 | if (!ctl->util_min_set) | |
200 | ctl->util_min = read_uclamp_sysfs(_PATH_PROC_UCLAMP_MIN); | |
201 | ||
202 | if (!ctl->util_max_set) | |
203 | ctl->util_max = read_uclamp_sysfs(_PATH_PROC_UCLAMP_MAX); | |
204 | ||
205 | if (ctl->util_min > ctl->util_max) { | |
206 | errno = EINVAL; | |
207 | err(EXIT_FAILURE, _("util_min must be <= util_max")); | |
208 | } | |
209 | ||
210 | write_uclamp_sysfs(_PATH_PROC_UCLAMP_MIN, ctl->util_min); | |
211 | write_uclamp_sysfs(_PATH_PROC_UCLAMP_MAX, ctl->util_max); | |
212 | } | |
213 | ||
214 | static void validate_util(int val) | |
215 | { | |
216 | if (val > 1024 || val < -1) { | |
217 | errno = EINVAL; | |
218 | err(EXIT_FAILURE, _("%d out of range"), val); | |
219 | } | |
220 | } | |
221 | ||
222 | int main(int argc, char **argv) | |
223 | { | |
224 | struct uclampset _ctl = { | |
225 | .pid = -1, | |
226 | .util_min = NOT_SET, | |
227 | .util_max = NOT_SET, | |
228 | .cmd = NULL | |
229 | }; | |
230 | struct uclampset *ctl = &_ctl; | |
231 | int c; | |
232 | ||
233 | static const struct option longopts[] = { | |
234 | { "all-tasks", no_argument, NULL, 'a' }, | |
235 | { "pid", required_argument, NULL, 'p' }, | |
236 | { "system", no_argument, NULL, 's' }, | |
237 | { "reset-on-fork", no_argument, NULL, 'R' }, | |
238 | { "help", no_argument, NULL, 'h' }, | |
239 | { "verbose", no_argument, NULL, 'v' }, | |
240 | { "version", no_argument, NULL, 'V' }, | |
241 | { NULL, no_argument, NULL, 0 } | |
242 | }; | |
243 | ||
244 | setlocale(LC_ALL, ""); | |
245 | bindtextdomain(PACKAGE, LOCALEDIR); | |
246 | textdomain(PACKAGE); | |
247 | close_stdout_atexit(); | |
248 | ||
249 | while((c = getopt_long(argc, argv, "+asRp:hm:M:vV", longopts, NULL)) != -1) | |
250 | { | |
251 | switch (c) { | |
252 | case 'a': | |
253 | ctl->all_tasks = 1; | |
254 | break; | |
255 | case 'p': | |
256 | errno = 0; | |
257 | ctl->pid = strtos32_or_err(optarg, _("invalid PID argument")); | |
5407f897 QY |
258 | break; |
259 | case 's': | |
260 | ctl->system = 1; | |
261 | break; | |
262 | case 'R': | |
263 | ctl->reset_on_fork = 1; | |
264 | break; | |
265 | case 'v': | |
266 | ctl->verbose = 1; | |
267 | break; | |
268 | case 'm': | |
269 | ctl->util_min = strtos32_or_err(optarg, _("invalid util_min argument")); | |
270 | ctl->util_min_set = 1; | |
271 | validate_util(ctl->util_min); | |
5407f897 QY |
272 | break; |
273 | case 'M': | |
274 | ctl->util_max = strtos32_or_err(optarg, _("invalid util_max argument")); | |
275 | ctl->util_max_set = 1; | |
276 | validate_util(ctl->util_max); | |
5407f897 QY |
277 | break; |
278 | case 'V': | |
279 | print_version(EXIT_SUCCESS); | |
280 | /* fallthrough */ | |
281 | case 'h': | |
282 | usage(); | |
283 | default: | |
284 | errtryhelp(EXIT_FAILURE); | |
285 | } | |
286 | } | |
287 | ||
288 | if (argc == 1) { | |
289 | usage(); | |
290 | exit(EXIT_FAILURE); | |
291 | } | |
292 | ||
293 | /* all_tasks implies --pid */ | |
294 | if (ctl->all_tasks && ctl->pid == -1) { | |
295 | errno = EINVAL; | |
296 | err(EXIT_FAILURE, _("missing -p option")); | |
297 | } | |
298 | ||
299 | if (!ctl->util_min_set && !ctl->util_max_set) { | |
300 | /* -p or -s must be passed */ | |
301 | if (!ctl->system && ctl->pid == -1) { | |
302 | usage(); | |
303 | exit(EXIT_FAILURE); | |
304 | } | |
305 | ||
306 | show_uclamp_info(ctl); | |
307 | return EXIT_SUCCESS; | |
308 | } | |
309 | ||
310 | /* ensure there's a command to execute if no -s or -p */ | |
311 | if (!ctl->system && ctl->pid == -1) { | |
312 | if (argc <= optind) { | |
313 | errno = EINVAL; | |
314 | err(EXIT_FAILURE, _("no cmd to execute")); | |
315 | } | |
316 | ||
317 | argv += optind; | |
318 | ctl->cmd = argv[0]; | |
319 | } | |
320 | ||
321 | if (ctl->pid == -1) | |
322 | ctl->pid = 0; | |
323 | ||
324 | if (ctl->system) | |
325 | set_uclamp_system(ctl); | |
326 | else | |
327 | set_uclamp_pid(ctl); | |
328 | ||
329 | if (ctl->verbose) | |
330 | show_uclamp_info(ctl); | |
331 | ||
332 | if (ctl->cmd) { | |
333 | execvp(ctl->cmd, argv); | |
334 | errexec(ctl->cmd); | |
335 | } | |
336 | ||
337 | return EXIT_SUCCESS; | |
338 | } |