]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/cgtop/cgtop.c
sd-event: improve debug message when we fail to remove and fd from an epoll
[thirdparty/systemd.git] / src / cgtop / cgtop.c
CommitLineData
8f2d43a0
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2012 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
8f2d43a0
LP
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 16 Lesser General Public License for more details.
8f2d43a0 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
8f2d43a0
LP
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <errno.h>
23#include <string.h>
24#include <stdlib.h>
1e913bcb 25#include <stdint.h>
8f2d43a0
LP
26#include <unistd.h>
27#include <alloca.h>
28#include <getopt.h>
97b845b0 29#include <signal.h>
8f2d43a0 30
9eb977db 31#include "path-util.h"
288a74cc 32#include "terminal-util.h"
41ba8b6e 33#include "process-util.h"
8f2d43a0
LP
34#include "util.h"
35#include "hashmap.h"
36#include "cgroup-util.h"
0d7e32fa 37#include "build.h"
a5c32cff 38#include "fileio.h"
8f2d43a0
LP
39
40typedef struct Group {
41 char *path;
42
43 bool n_tasks_valid:1;
44 bool cpu_valid:1;
45 bool memory_valid:1;
46 bool io_valid:1;
47
48 unsigned n_tasks;
49
50 unsigned cpu_iteration;
45d7a8bb
LP
51 nsec_t cpu_usage;
52 nsec_t cpu_timestamp;
8f2d43a0
LP
53 double cpu_fraction;
54
55 uint64_t memory;
56
57 unsigned io_iteration;
58 uint64_t io_input, io_output;
45d7a8bb 59 nsec_t io_timestamp;
8f2d43a0
LP
60 uint64_t io_input_bps, io_output_bps;
61} Group;
62
30edf116 63static unsigned arg_depth = 3;
45d7a8bb 64static unsigned arg_iterations = (unsigned) -1;
e66bb58b 65static bool arg_batch = false;
a2c9f631 66static bool arg_raw = false;
8f2d43a0 67static usec_t arg_delay = 1*USEC_PER_SEC;
41ba8b6e 68static bool arg_kernel_threads = false;
3cb5beea 69static bool arg_recursive = true;
8f2d43a0
LP
70
71static enum {
72 ORDER_PATH,
73 ORDER_TASKS,
74 ORDER_CPU,
75 ORDER_MEMORY,
76 ORDER_IO
77} arg_order = ORDER_CPU;
78
1e913bcb
UTL
79static enum {
80 CPU_PERCENT,
81 CPU_TIME,
82} arg_cpu_type = CPU_PERCENT;
83
8f2d43a0
LP
84static void group_free(Group *g) {
85 assert(g);
86
87 free(g->path);
88 free(g);
89}
90
91static void group_hashmap_clear(Hashmap *h) {
92 Group *g;
93
94 while ((g = hashmap_steal_first(h)))
95 group_free(g);
96}
97
98static void group_hashmap_free(Hashmap *h) {
99 group_hashmap_clear(h);
100 hashmap_free(h);
101}
102
a2c9f631
CD
103static const char *maybe_format_bytes(char *buf, size_t l, bool is_valid, off_t t) {
104 if (!is_valid)
105 return "-";
106 if (arg_raw) {
107 snprintf(buf, l, "%jd", t);
108 return buf;
109 }
110 return format_bytes(buf, l, t);
111}
112
3cb5beea
LP
113static int process(
114 const char *controller,
115 const char *path,
116 Hashmap *a,
117 Hashmap *b,
118 unsigned iteration,
119 Group **ret) {
120
8f2d43a0
LP
121 Group *g;
122 int r;
8f2d43a0
LP
123
124 assert(controller);
125 assert(path);
126 assert(a);
127
128 g = hashmap_get(a, path);
129 if (!g) {
130 g = hashmap_get(b, path);
131 if (!g) {
132 g = new0(Group, 1);
133 if (!g)
134 return -ENOMEM;
135
136 g->path = strdup(path);
137 if (!g->path) {
138 group_free(g);
139 return -ENOMEM;
140 }
141
142 r = hashmap_put(a, g->path, g);
143 if (r < 0) {
144 group_free(g);
145 return r;
146 }
147 } else {
2d5c93c7
MS
148 r = hashmap_move_one(a, b, path);
149 if (r < 0)
150 return r;
45d7a8bb 151
8f2d43a0
LP
152 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
153 }
154 }
155
45d7a8bb
LP
156 if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) {
157 _cleanup_fclose_ FILE *f = NULL;
158 pid_t pid;
8f2d43a0 159
45d7a8bb
LP
160 r = cg_enumerate_processes(controller, path, &f);
161 if (r == -ENOENT)
162 return 0;
163 if (r < 0)
164 return r;
8f2d43a0 165
45d7a8bb 166 g->n_tasks = 0;
41ba8b6e
LP
167 while (cg_read_pid(f, &pid) > 0) {
168
169 if (!arg_kernel_threads && is_kernel_thread(pid) > 0)
170 continue;
171
45d7a8bb 172 g->n_tasks++;
41ba8b6e 173 }
8f2d43a0 174
45d7a8bb
LP
175 if (g->n_tasks > 0)
176 g->n_tasks_valid = true;
8f2d43a0 177
45d7a8bb
LP
178 } else if (streq(controller, "cpuacct")) {
179 _cleanup_free_ char *p = NULL, *v = NULL;
8f2d43a0 180 uint64_t new_usage;
45d7a8bb 181 nsec_t timestamp;
8f2d43a0
LP
182
183 r = cg_get_path(controller, path, "cpuacct.usage", &p);
184 if (r < 0)
185 return r;
186
187 r = read_one_line_file(p, &v);
45d7a8bb
LP
188 if (r == -ENOENT)
189 return 0;
8f2d43a0
LP
190 if (r < 0)
191 return r;
192
193 r = safe_atou64(v, &new_usage);
8f2d43a0
LP
194 if (r < 0)
195 return r;
196
45d7a8bb 197 timestamp = now_nsec(CLOCK_MONOTONIC);
8f2d43a0 198
45d7a8bb
LP
199 if (g->cpu_iteration == iteration - 1 &&
200 (nsec_t) new_usage > g->cpu_usage) {
8f2d43a0 201
45d7a8bb 202 nsec_t x, y;
8f2d43a0 203
45d7a8bb
LP
204 x = timestamp - g->cpu_timestamp;
205 if (x < 1)
206 x = 1;
8f2d43a0 207
45d7a8bb
LP
208 y = (nsec_t) new_usage - g->cpu_usage;
209 g->cpu_fraction = (double) y / (double) x;
210 g->cpu_valid = true;
8f2d43a0
LP
211 }
212
45d7a8bb
LP
213 g->cpu_usage = (nsec_t) new_usage;
214 g->cpu_timestamp = timestamp;
8f2d43a0
LP
215 g->cpu_iteration = iteration;
216
217 } else if (streq(controller, "memory")) {
45d7a8bb 218 _cleanup_free_ char *p = NULL, *v = NULL;
8f2d43a0
LP
219
220 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
221 if (r < 0)
222 return r;
223
224 r = read_one_line_file(p, &v);
45d7a8bb
LP
225 if (r == -ENOENT)
226 return 0;
8f2d43a0
LP
227 if (r < 0)
228 return r;
229
230 r = safe_atou64(v, &g->memory);
8f2d43a0
LP
231 if (r < 0)
232 return r;
233
234 if (g->memory > 0)
235 g->memory_valid = true;
236
237 } else if (streq(controller, "blkio")) {
45d7a8bb
LP
238 _cleanup_fclose_ FILE *f = NULL;
239 _cleanup_free_ char *p = NULL;
8f2d43a0 240 uint64_t wr = 0, rd = 0;
45d7a8bb 241 nsec_t timestamp;
8f2d43a0
LP
242
243 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
244 if (r < 0)
245 return r;
246
247 f = fopen(p, "re");
45d7a8bb
LP
248 if (!f) {
249 if (errno == ENOENT)
250 return 0;
8f2d43a0 251 return -errno;
45d7a8bb 252 }
8f2d43a0
LP
253
254 for (;;) {
255 char line[LINE_MAX], *l;
256 uint64_t k, *q;
257
258 if (!fgets(line, sizeof(line), f))
259 break;
260
261 l = strstrip(line);
262 l += strcspn(l, WHITESPACE);
263 l += strspn(l, WHITESPACE);
264
265 if (first_word(l, "Read")) {
266 l += 4;
267 q = &rd;
268 } else if (first_word(l, "Write")) {
269 l += 5;
270 q = &wr;
271 } else
272 continue;
273
274 l += strspn(l, WHITESPACE);
275 r = safe_atou64(l, &k);
276 if (r < 0)
277 continue;
278
279 *q += k;
280 }
281
45d7a8bb 282 timestamp = now_nsec(CLOCK_MONOTONIC);
8f2d43a0
LP
283
284 if (g->io_iteration == iteration - 1) {
285 uint64_t x, yr, yw;
286
45d7a8bb
LP
287 x = (uint64_t) (timestamp - g->io_timestamp);
288 if (x < 1)
289 x = 1;
8f2d43a0 290
45d7a8bb
LP
291 if (rd > g->io_input)
292 yr = rd - g->io_input;
293 else
294 yr = 0;
295
296 if (wr > g->io_output)
297 yw = wr - g->io_output;
298 else
299 yw = 0;
8f2d43a0 300
45d7a8bb 301 if (yr > 0 || yw > 0) {
8f2d43a0
LP
302 g->io_input_bps = (yr * 1000000000ULL) / x;
303 g->io_output_bps = (yw * 1000000000ULL) / x;
304 g->io_valid = true;
8f2d43a0
LP
305 }
306 }
307
308 g->io_input = rd;
309 g->io_output = wr;
45d7a8bb 310 g->io_timestamp = timestamp;
8f2d43a0
LP
311 g->io_iteration = iteration;
312 }
313
3cb5beea
LP
314 if (ret)
315 *ret = g;
316
8f2d43a0
LP
317 return 0;
318}
319
320static int refresh_one(
321 const char *controller,
322 const char *path,
323 Hashmap *a,
324 Hashmap *b,
325 unsigned iteration,
3cb5beea
LP
326 unsigned depth,
327 Group **ret) {
8f2d43a0 328
45d7a8bb 329 _cleanup_closedir_ DIR *d = NULL;
3cb5beea 330 Group *ours;
8f2d43a0
LP
331 int r;
332
333 assert(controller);
334 assert(path);
335 assert(a);
336
337 if (depth > arg_depth)
338 return 0;
339
3cb5beea 340 r = process(controller, path, a, b, iteration, &ours);
8f2d43a0
LP
341 if (r < 0)
342 return r;
343
344 r = cg_enumerate_subgroups(controller, path, &d);
45d7a8bb
LP
345 if (r == -ENOENT)
346 return 0;
347 if (r < 0)
8f2d43a0 348 return r;
8f2d43a0
LP
349
350 for (;;) {
45d7a8bb 351 _cleanup_free_ char *fn = NULL, *p = NULL;
3cb5beea 352 Group *child = NULL;
8f2d43a0
LP
353
354 r = cg_read_subgroup(d, &fn);
3cb5beea 355 if (r < 0)
45d7a8bb 356 return r;
3cb5beea
LP
357 if (r == 0)
358 break;
8f2d43a0 359
b7def684 360 p = strjoin(path, "/", fn, NULL);
45d7a8bb
LP
361 if (!p)
362 return -ENOMEM;
8f2d43a0
LP
363
364 path_kill_slashes(p);
365
3cb5beea 366 r = refresh_one(controller, p, a, b, iteration, depth + 1, &child);
8f2d43a0 367 if (r < 0)
45d7a8bb 368 return r;
3cb5beea
LP
369
370 if (arg_recursive &&
371 child &&
372 child->n_tasks_valid &&
373 streq(controller, SYSTEMD_CGROUP_CONTROLLER)) {
374
375 /* Recursively sum up processes */
376
377 if (ours->n_tasks_valid)
378 ours->n_tasks += child->n_tasks;
379 else {
380 ours->n_tasks = child->n_tasks;
381 ours->n_tasks_valid = true;
382 }
383 }
8f2d43a0
LP
384 }
385
3cb5beea
LP
386 if (ret)
387 *ret = ours;
388
389 return 1;
8f2d43a0
LP
390}
391
03af6492 392static int refresh(const char *root, Hashmap *a, Hashmap *b, unsigned iteration) {
8f2d43a0
LP
393 int r;
394
395 assert(a);
396
3cb5beea 397 r = refresh_one(SYSTEMD_CGROUP_CONTROLLER, root, a, b, iteration, 0, NULL);
8f2d43a0 398 if (r < 0)
45d7a8bb 399 return r;
3cb5beea 400 r = refresh_one("cpuacct", root, a, b, iteration, 0, NULL);
8f2d43a0 401 if (r < 0)
45d7a8bb 402 return r;
3cb5beea 403 r = refresh_one("memory", root, a, b, iteration, 0, NULL);
8f2d43a0 404 if (r < 0)
45d7a8bb 405 return r;
3cb5beea 406 r = refresh_one("blkio", root, a, b, iteration, 0, NULL);
63210a15 407 if (r < 0)
45d7a8bb
LP
408 return r;
409
63210a15 410 return 0;
8f2d43a0
LP
411}
412
413static int group_compare(const void*a, const void *b) {
414 const Group *x = *(Group**)a, *y = *(Group**)b;
415
3cb5beea 416 if (arg_order != ORDER_TASKS || arg_recursive) {
45d7a8bb 417 /* Let's make sure that the parent is always before
3cb5beea
LP
418 * the child. Except when ordering by tasks and
419 * recursive summing is off, since that is actually
420 * not accumulative for all children. */
45d7a8bb
LP
421
422 if (path_startswith(y->path, x->path))
423 return -1;
424 if (path_startswith(x->path, y->path))
425 return 1;
426 }
427
428 switch (arg_order) {
429
430 case ORDER_PATH:
431 break;
8f2d43a0 432
45d7a8bb 433 case ORDER_CPU:
1e913bcb
UTL
434 if (arg_cpu_type == CPU_PERCENT) {
435 if (x->cpu_valid && y->cpu_valid) {
436 if (x->cpu_fraction > y->cpu_fraction)
437 return -1;
438 else if (x->cpu_fraction < y->cpu_fraction)
439 return 1;
440 } else if (x->cpu_valid)
8f2d43a0 441 return -1;
1e913bcb 442 else if (y->cpu_valid)
8f2d43a0 443 return 1;
1e913bcb
UTL
444 } else {
445 if (x->cpu_usage > y->cpu_usage)
446 return -1;
447 else if (x->cpu_usage < y->cpu_usage)
448 return 1;
449 }
8f2d43a0 450
45d7a8bb 451 break;
8f2d43a0 452
45d7a8bb 453 case ORDER_TASKS:
8f2d43a0
LP
454 if (x->n_tasks_valid && y->n_tasks_valid) {
455 if (x->n_tasks > y->n_tasks)
456 return -1;
457 else if (x->n_tasks < y->n_tasks)
458 return 1;
459 } else if (x->n_tasks_valid)
460 return -1;
461 else if (y->n_tasks_valid)
462 return 1;
8f2d43a0 463
45d7a8bb
LP
464 break;
465
466 case ORDER_MEMORY:
8f2d43a0
LP
467 if (x->memory_valid && y->memory_valid) {
468 if (x->memory > y->memory)
469 return -1;
470 else if (x->memory < y->memory)
471 return 1;
472 } else if (x->memory_valid)
473 return -1;
474 else if (y->memory_valid)
475 return 1;
8f2d43a0 476
45d7a8bb
LP
477 break;
478
479 case ORDER_IO:
8f2d43a0
LP
480 if (x->io_valid && y->io_valid) {
481 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
482 return -1;
483 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
484 return 1;
485 } else if (x->io_valid)
486 return -1;
487 else if (y->io_valid)
488 return 1;
489 }
490
45d7a8bb 491 return path_compare(x->path, y->path);
8f2d43a0
LP
492}
493
1e913bcb
UTL
494#define ON ANSI_HIGHLIGHT_ON
495#define OFF ANSI_HIGHLIGHT_OFF
496
8f2d43a0
LP
497static int display(Hashmap *a) {
498 Iterator i;
499 Group *g;
500 Group **array;
1e913bcb 501 signed path_columns;
510c4a0f 502 unsigned rows, n = 0, j, maxtcpu = 0, maxtpath = 3; /* 3 for ellipsize() to work properly */
62b95b8b 503 char buffer[MAX3(21, FORMAT_BYTES_MAX, FORMAT_TIMESPAN_MAX)];
8f2d43a0
LP
504
505 assert(a);
506
507 /* Set cursor to top left corner and clear screen */
1e913bcb
UTL
508 if (on_tty())
509 fputs("\033[H"
510 "\033[2J", stdout);
8f2d43a0
LP
511
512 array = alloca(sizeof(Group*) * hashmap_size(a));
513
514 HASHMAP_FOREACH(g, a, i)
515 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
516 array[n++] = g;
517
7ff7394d 518 qsort_safe(array, n, sizeof(Group*), group_compare);
8f2d43a0 519
1e913bcb
UTL
520 /* Find the longest names in one run */
521 for (j = 0; j < n; j++) {
522 unsigned cputlen, pathtlen;
62b95b8b 523
45d7a8bb 524 format_timespan(buffer, sizeof(buffer), (usec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0);
62b95b8b 525 cputlen = strlen(buffer);
1e913bcb 526 maxtcpu = MAX(maxtcpu, cputlen);
45d7a8bb 527
1e913bcb
UTL
528 pathtlen = strlen(array[j]->path);
529 maxtpath = MAX(maxtpath, pathtlen);
530 }
531
532 if (arg_cpu_type == CPU_PERCENT)
62b95b8b 533 snprintf(buffer, sizeof(buffer), "%6s", "%CPU");
1e913bcb 534 else
62b95b8b 535 snprintf(buffer, sizeof(buffer), "%*s", maxtcpu, "CPU Time");
1e913bcb 536
ed757c0c
LP
537 rows = lines();
538 if (rows <= 10)
539 rows = 10;
8f2d43a0 540
1e913bcb 541 if (on_tty()) {
62b95b8b 542 path_columns = columns() - 36 - strlen(buffer);
1e913bcb
UTL
543 if (path_columns < 10)
544 path_columns = 10;
545
546 printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
45d7a8bb 547 arg_order == ORDER_PATH ? ON : "", path_columns, "Control Group",
1e913bcb
UTL
548 arg_order == ORDER_PATH ? OFF : "",
549 arg_order == ORDER_TASKS ? ON : "", "Tasks",
550 arg_order == ORDER_TASKS ? OFF : "",
62b95b8b 551 arg_order == ORDER_CPU ? ON : "", buffer,
1e913bcb
UTL
552 arg_order == ORDER_CPU ? OFF : "",
553 arg_order == ORDER_MEMORY ? ON : "", "Memory",
554 arg_order == ORDER_MEMORY ? OFF : "",
555 arg_order == ORDER_IO ? ON : "", "Input/s",
556 arg_order == ORDER_IO ? OFF : "",
557 arg_order == ORDER_IO ? ON : "", "Output/s",
558 arg_order == ORDER_IO ? OFF : "");
559 } else
560 path_columns = maxtpath;
8f2d43a0
LP
561
562 for (j = 0; j < n; j++) {
45d7a8bb 563 _cleanup_free_ char *p = NULL;
8f2d43a0 564
1e913bcb 565 if (on_tty() && j + 5 > rows)
8f2d43a0
LP
566 break;
567
568 g = array[j];
569
11f96fac 570 p = ellipsize(g->path, path_columns, 33);
45d7a8bb 571 printf("%-*s", path_columns, p ?: g->path);
8f2d43a0
LP
572
573 if (g->n_tasks_valid)
574 printf(" %7u", g->n_tasks);
575 else
576 fputs(" -", stdout);
577
62b95b8b 578 if (arg_cpu_type == CPU_PERCENT) {
1e913bcb
UTL
579 if (g->cpu_valid)
580 printf(" %6.1f", g->cpu_fraction*100);
581 else
582 fputs(" -", stdout);
62b95b8b 583 } else
45d7a8bb 584 printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (usec_t) (g->cpu_usage / NSEC_PER_USEC), 0));
8f2d43a0 585
a2c9f631
CD
586 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->memory_valid, g->memory));
587 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_input_bps));
588 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_output_bps));
8f2d43a0
LP
589
590 putchar('\n');
591 }
592
593 return 0;
594}
595
601185b4 596static void help(void) {
8f2d43a0
LP
597 printf("%s [OPTIONS...]\n\n"
598 "Show top control groups by their resource usage.\n\n"
599 " -h --help Show this help\n"
45d7a8bb
LP
600 " --version Show package version\n"
601 " -p --order=path Order by path\n"
602 " -t --order=tasks Order by number of tasks\n"
603 " -c --order=cpu Order by CPU load (default)\n"
604 " -m --order=memory Order by memory load\n"
605 " -i --order=io Order by IO load\n"
a2c9f631 606 " -r --raw Provide raw (not human-readable) numbers\n"
45d7a8bb
LP
607 " --cpu=percentage Show CPU usage as percentage (default)\n"
608 " --cpu=time Show CPU usage as time\n"
41ba8b6e 609 " -k Include kernel threads in task count\n"
3cb5beea 610 " --recursive=BOOL Sum up task count recursively\n"
1e913bcb 611 " -d --delay=DELAY Delay between updates\n"
a152771a 612 " -n --iterations=N Run for N iterations before exiting\n"
e66bb58b 613 " -b --batch Run in batch mode, accepting no input\n"
601185b4
ZJS
614 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
615 , program_invocation_short_name, arg_depth);
0d7e32fa
ZJS
616}
617
8f2d43a0
LP
618static int parse_argv(int argc, char *argv[]) {
619
620 enum {
0d7e32fa
ZJS
621 ARG_VERSION = 0x100,
622 ARG_DEPTH,
45d7a8bb
LP
623 ARG_CPU_TYPE,
624 ARG_ORDER,
3cb5beea 625 ARG_RECURSIVE,
8f2d43a0
LP
626 };
627
628 static const struct option options[] = {
3cb5beea
LP
629 { "help", no_argument, NULL, 'h' },
630 { "version", no_argument, NULL, ARG_VERSION },
631 { "delay", required_argument, NULL, 'd' },
632 { "iterations", required_argument, NULL, 'n' },
633 { "batch", no_argument, NULL, 'b' },
634 { "raw", no_argument, NULL, 'r' },
635 { "depth", required_argument, NULL, ARG_DEPTH },
636 { "cpu", optional_argument, NULL, ARG_CPU_TYPE },
637 { "order", required_argument, NULL, ARG_ORDER },
638 { "recursive", required_argument, NULL, ARG_RECURSIVE },
eb9da376 639 {}
8f2d43a0
LP
640 };
641
3cb5beea 642 int c, r;
8f2d43a0
LP
643
644 assert(argc >= 1);
645 assert(argv);
646
41ba8b6e 647 while ((c = getopt_long(argc, argv, "hptcmin:brd:k", options, NULL)) >= 0)
8f2d43a0
LP
648
649 switch (c) {
650
651 case 'h':
601185b4
ZJS
652 help();
653 return 0;
8f2d43a0 654
0d7e32fa 655 case ARG_VERSION:
eb9da376
LP
656 puts(PACKAGE_STRING);
657 puts(SYSTEMD_FEATURES);
0d7e32fa
ZJS
658 return 0;
659
1e913bcb
UTL
660 case ARG_CPU_TYPE:
661 if (optarg) {
45d7a8bb 662 if (streq(optarg, "time"))
1e913bcb 663 arg_cpu_type = CPU_TIME;
45d7a8bb 664 else if (streq(optarg, "percentage"))
1e913bcb 665 arg_cpu_type = CPU_PERCENT;
45d7a8bb
LP
666 else {
667 log_error("Unknown argument to --cpu=: %s", optarg);
1e913bcb 668 return -EINVAL;
45d7a8bb
LP
669 }
670 } else
671 arg_cpu_type = CPU_TIME;
672
1e913bcb
UTL
673 break;
674
8f2d43a0
LP
675 case ARG_DEPTH:
676 r = safe_atou(optarg, &arg_depth);
677 if (r < 0) {
678 log_error("Failed to parse depth parameter.");
679 return -EINVAL;
680 }
681
682 break;
683
684 case 'd':
7f602784 685 r = parse_sec(optarg, &arg_delay);
8f2d43a0
LP
686 if (r < 0 || arg_delay <= 0) {
687 log_error("Failed to parse delay parameter.");
688 return -EINVAL;
689 }
690
691 break;
692
a152771a
DS
693 case 'n':
694 r = safe_atou(optarg, &arg_iterations);
695 if (r < 0) {
696 log_error("Failed to parse iterations parameter.");
697 return -EINVAL;
698 }
699
700 break;
701
e66bb58b
DS
702 case 'b':
703 arg_batch = true;
704 break;
705
a2c9f631
CD
706 case 'r':
707 arg_raw = true;
708 break;
709
8f2d43a0
LP
710 case 'p':
711 arg_order = ORDER_PATH;
712 break;
713
714 case 't':
715 arg_order = ORDER_TASKS;
716 break;
717
718 case 'c':
719 arg_order = ORDER_CPU;
720 break;
721
722 case 'm':
723 arg_order = ORDER_MEMORY;
724 break;
725
726 case 'i':
727 arg_order = ORDER_IO;
728 break;
729
45d7a8bb
LP
730 case ARG_ORDER:
731 if (streq(optarg, "path"))
732 arg_order = ORDER_PATH;
733 else if (streq(optarg, "tasks"))
734 arg_order = ORDER_TASKS;
735 else if (streq(optarg, "cpu"))
736 arg_order = ORDER_CPU;
737 else if (streq(optarg, "memory"))
738 arg_order = ORDER_MEMORY;
739 else if (streq(optarg, "io"))
740 arg_order = ORDER_IO;
741 else {
742 log_error("Invalid argument to --order=: %s", optarg);
743 return -EINVAL;
744 }
745 break;
746
41ba8b6e
LP
747 case 'k':
748 arg_kernel_threads = true;
749 break;
750
3cb5beea
LP
751 case ARG_RECURSIVE:
752 r = parse_boolean(optarg);
753 if (r < 0) {
754 log_error("Failed to parse --recursive= argument: %s", optarg);
755 return r;
756 }
757
758 arg_recursive = r;
759 break;
760
8f2d43a0
LP
761 case '?':
762 return -EINVAL;
763
764 default:
eb9da376 765 assert_not_reached("Unhandled option");
8f2d43a0 766 }
8f2d43a0
LP
767
768 if (optind < argc) {
769 log_error("Too many arguments.");
770 return -EINVAL;
771 }
772
773 return 1;
774}
775
776int main(int argc, char *argv[]) {
777 int r;
778 Hashmap *a = NULL, *b = NULL;
779 unsigned iteration = 0;
780 usec_t last_refresh = 0;
781 bool quit = false, immediate_refresh = false;
03af6492 782 _cleanup_free_ char *root = NULL;
8f2d43a0
LP
783
784 log_parse_environment();
785 log_open();
786
787 r = parse_argv(argc, argv);
788 if (r <= 0)
789 goto finish;
790
03af6492
LP
791 r = cg_get_root_path(&root);
792 if (r < 0) {
793 log_error_errno(r, "Failed to get root control group path: %m");
794 goto finish;
795 }
796
d5099efc
MS
797 a = hashmap_new(&string_hash_ops);
798 b = hashmap_new(&string_hash_ops);
8f2d43a0 799 if (!a || !b) {
0d0f0c50 800 r = log_oom();
8f2d43a0
LP
801 goto finish;
802 }
803
ed757c0c 804 signal(SIGWINCH, columns_lines_cache_reset);
28917d7d 805
45d7a8bb 806 if (arg_iterations == (unsigned) -1)
780fe62e 807 arg_iterations = on_tty() ? 0 : 1;
1e913bcb 808
8f2d43a0
LP
809 while (!quit) {
810 Hashmap *c;
811 usec_t t;
812 char key;
813 char h[FORMAT_TIMESPAN_MAX];
814
815 t = now(CLOCK_MONOTONIC);
816
817 if (t >= last_refresh + arg_delay || immediate_refresh) {
818
03af6492 819 r = refresh(root, a, b, iteration++);
8f2d43a0
LP
820 if (r < 0)
821 goto finish;
822
823 group_hashmap_clear(b);
824
825 c = a;
826 a = b;
827 b = c;
828
829 last_refresh = t;
830 immediate_refresh = false;
831 }
832
833 r = display(b);
834 if (r < 0)
835 goto finish;
836
a152771a
DS
837 if (arg_iterations && iteration >= arg_iterations)
838 break;
839
dcc7aacd
CD
840 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
841 fputs("\n", stdout);
842 fflush(stdout);
843
45d7a8bb 844 if (arg_batch)
e66bb58b 845 usleep(last_refresh + arg_delay - t);
45d7a8bb
LP
846 else {
847 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
e66bb58b
DS
848 if (r == -ETIMEDOUT)
849 continue;
850 if (r < 0) {
da927ba9 851 log_error_errno(r, "Couldn't read key: %m");
e66bb58b
DS
852 goto finish;
853 }
8f2d43a0
LP
854 }
855
dcc7aacd
CD
856 if (on_tty()) { /* TTY: Clear any user keystroke */
857 fputs("\r \r", stdout);
858 fflush(stdout);
859 }
8f2d43a0 860
e66bb58b
DS
861 if (arg_batch)
862 continue;
863
8f2d43a0
LP
864 switch (key) {
865
866 case ' ':
867 immediate_refresh = true;
868 break;
869
870 case 'q':
871 quit = true;
872 break;
873
874 case 'p':
875 arg_order = ORDER_PATH;
876 break;
877
878 case 't':
879 arg_order = ORDER_TASKS;
880 break;
881
882 case 'c':
883 arg_order = ORDER_CPU;
884 break;
885
886 case 'm':
887 arg_order = ORDER_MEMORY;
888 break;
889
890 case 'i':
891 arg_order = ORDER_IO;
892 break;
893
2bfc1eda
ZJS
894 case '%':
895 arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME;
896 break;
897
7fcfb7ee
LP
898 case 'k':
899 arg_kernel_threads = !arg_kernel_threads;
900 fprintf(stdout, "\nCounting kernel threads: %s.", yes_no(arg_kernel_threads));
901 fflush(stdout);
902 sleep(1);
903 break;
904
905 case 'r':
906 arg_recursive = !arg_recursive;
907 fprintf(stdout, "\nRecursive task counting: %s", yes_no(arg_recursive));
908 fflush(stdout);
909 sleep(1);
910 break;
911
8f2d43a0
LP
912 case '+':
913 if (arg_delay < USEC_PER_SEC)
914 arg_delay += USEC_PER_MSEC*250;
915 else
916 arg_delay += USEC_PER_SEC;
917
2fa4092c 918 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
8f2d43a0
LP
919 fflush(stdout);
920 sleep(1);
921 break;
922
923 case '-':
924 if (arg_delay <= USEC_PER_MSEC*500)
925 arg_delay = USEC_PER_MSEC*250;
926 else if (arg_delay < USEC_PER_MSEC*1250)
927 arg_delay -= USEC_PER_MSEC*250;
928 else
929 arg_delay -= USEC_PER_SEC;
930
2fa4092c 931 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
8f2d43a0
LP
932 fflush(stdout);
933 sleep(1);
934 break;
935
936 case '?':
937 case 'h':
938 fprintf(stdout,
c851f34b 939 "\t<" ON "p" OFF "> By path; <" ON "t" OFF "> By tasks; <" ON "c" OFF "> By CPU; <" ON "m" OFF "> By memory; <" ON "i" OFF "> By I/O\n"
7fcfb7ee
LP
940 "\t<" ON "+" OFF "> Inc. delay; <" ON "-" OFF "> Dec. delay; <" ON "%%" OFF "> Toggle time; <" ON "SPACE" OFF "> Refresh\n"
941 "\t<" ON "k" OFF "> Count kernel threads; <" ON "r" OFF "> Count recursively; <" ON "q" OFF "> Quit");
8f2d43a0
LP
942 fflush(stdout);
943 sleep(3);
944 break;
945
946 default:
45d7a8bb
LP
947 if (key < ' ')
948 fprintf(stdout, "\nUnknown key '\\x%x'. Ignoring.", key);
949 else
950 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
8f2d43a0
LP
951 fflush(stdout);
952 sleep(1);
953 break;
954 }
955 }
956
8f2d43a0
LP
957 r = 0;
958
959finish:
960 group_hashmap_free(a);
961 group_hashmap_free(b);
962
14212119 963 if (r < 0) {
da927ba9 964 log_error_errno(r, "Exiting with failure: %m");
14212119
SL
965 return EXIT_FAILURE;
966 }
967
968 return EXIT_SUCCESS;
8f2d43a0 969}