1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
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
11 (at your option) any later version.
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
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
31 #include "path-util.h"
32 #include "terminal-util.h"
35 #include "cgroup-util.h"
39 typedef struct Group
{
49 unsigned cpu_iteration
;
56 unsigned io_iteration
;
57 uint64_t io_input
, io_output
;
59 uint64_t io_input_bps
, io_output_bps
;
62 static unsigned arg_depth
= 3;
63 static unsigned arg_iterations
= (unsigned) -1;
64 static bool arg_batch
= false;
65 static bool arg_raw
= false;
66 static usec_t arg_delay
= 1*USEC_PER_SEC
;
74 } arg_order
= ORDER_CPU
;
79 } arg_cpu_type
= CPU_PERCENT
;
81 static void group_free(Group
*g
) {
88 static void group_hashmap_clear(Hashmap
*h
) {
91 while ((g
= hashmap_steal_first(h
)))
95 static void group_hashmap_free(Hashmap
*h
) {
96 group_hashmap_clear(h
);
100 static const char *maybe_format_bytes(char *buf
, size_t l
, bool is_valid
, off_t t
) {
104 snprintf(buf
, l
, "%jd", t
);
107 return format_bytes(buf
, l
, t
);
110 static int process(const char *controller
, const char *path
, Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
118 g
= hashmap_get(a
, path
);
120 g
= hashmap_get(b
, path
);
126 g
->path
= strdup(path
);
132 r
= hashmap_put(a
, g
->path
, g
);
138 r
= hashmap_move_one(a
, b
, path
);
142 g
->cpu_valid
= g
->memory_valid
= g
->io_valid
= g
->n_tasks_valid
= false;
146 if (streq(controller
, SYSTEMD_CGROUP_CONTROLLER
)) {
147 _cleanup_fclose_
FILE *f
= NULL
;
150 r
= cg_enumerate_processes(controller
, path
, &f
);
157 while (cg_read_pid(f
, &pid
) > 0)
161 g
->n_tasks_valid
= true;
163 } else if (streq(controller
, "cpuacct")) {
164 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
168 r
= cg_get_path(controller
, path
, "cpuacct.usage", &p
);
172 r
= read_one_line_file(p
, &v
);
178 r
= safe_atou64(v
, &new_usage
);
182 timestamp
= now_nsec(CLOCK_MONOTONIC
);
184 if (g
->cpu_iteration
== iteration
- 1 &&
185 (nsec_t
) new_usage
> g
->cpu_usage
) {
189 x
= timestamp
- g
->cpu_timestamp
;
193 y
= (nsec_t
) new_usage
- g
->cpu_usage
;
194 g
->cpu_fraction
= (double) y
/ (double) x
;
198 g
->cpu_usage
= (nsec_t
) new_usage
;
199 g
->cpu_timestamp
= timestamp
;
200 g
->cpu_iteration
= iteration
;
202 } else if (streq(controller
, "memory")) {
203 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
205 r
= cg_get_path(controller
, path
, "memory.usage_in_bytes", &p
);
209 r
= read_one_line_file(p
, &v
);
215 r
= safe_atou64(v
, &g
->memory
);
220 g
->memory_valid
= true;
222 } else if (streq(controller
, "blkio")) {
223 _cleanup_fclose_
FILE *f
= NULL
;
224 _cleanup_free_
char *p
= NULL
;
225 uint64_t wr
= 0, rd
= 0;
228 r
= cg_get_path(controller
, path
, "blkio.io_service_bytes", &p
);
240 char line
[LINE_MAX
], *l
;
243 if (!fgets(line
, sizeof(line
), f
))
247 l
+= strcspn(l
, WHITESPACE
);
248 l
+= strspn(l
, WHITESPACE
);
250 if (first_word(l
, "Read")) {
253 } else if (first_word(l
, "Write")) {
259 l
+= strspn(l
, WHITESPACE
);
260 r
= safe_atou64(l
, &k
);
267 timestamp
= now_nsec(CLOCK_MONOTONIC
);
269 if (g
->io_iteration
== iteration
- 1) {
272 x
= (uint64_t) (timestamp
- g
->io_timestamp
);
276 if (rd
> g
->io_input
)
277 yr
= rd
- g
->io_input
;
281 if (wr
> g
->io_output
)
282 yw
= wr
- g
->io_output
;
286 if (yr
> 0 || yw
> 0) {
287 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
288 g
->io_output_bps
= (yw
* 1000000000ULL) / x
;
295 g
->io_timestamp
= timestamp
;
296 g
->io_iteration
= iteration
;
302 static int refresh_one(
303 const char *controller
,
310 _cleanup_closedir_
DIR *d
= NULL
;
317 if (depth
> arg_depth
)
320 r
= process(controller
, path
, a
, b
, iteration
);
324 r
= cg_enumerate_subgroups(controller
, path
, &d
);
331 _cleanup_free_
char *fn
= NULL
, *p
= NULL
;
333 r
= cg_read_subgroup(d
, &fn
);
337 p
= strjoin(path
, "/", fn
, NULL
);
341 path_kill_slashes(p
);
343 r
= refresh_one(controller
, p
, a
, b
, iteration
, depth
+ 1);
351 static int refresh(Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
356 r
= refresh_one(SYSTEMD_CGROUP_CONTROLLER
, "/", a
, b
, iteration
, 0);
359 r
= refresh_one("cpuacct", "/", a
, b
, iteration
, 0);
362 r
= refresh_one("memory", "/", a
, b
, iteration
, 0);
365 r
= refresh_one("blkio", "/", a
, b
, iteration
, 0);
372 static int group_compare(const void*a
, const void *b
) {
373 const Group
*x
= *(Group
**)a
, *y
= *(Group
**)b
;
375 if (arg_order
!= ORDER_TASKS
) {
376 /* Let's make sure that the parent is always before
377 * the child. Except when ordering by tasks, since
378 * that is actually not accumulative for all
381 if (path_startswith(y
->path
, x
->path
))
383 if (path_startswith(x
->path
, y
->path
))
393 if (arg_cpu_type
== CPU_PERCENT
) {
394 if (x
->cpu_valid
&& y
->cpu_valid
) {
395 if (x
->cpu_fraction
> y
->cpu_fraction
)
397 else if (x
->cpu_fraction
< y
->cpu_fraction
)
399 } else if (x
->cpu_valid
)
401 else if (y
->cpu_valid
)
404 if (x
->cpu_usage
> y
->cpu_usage
)
406 else if (x
->cpu_usage
< y
->cpu_usage
)
413 if (x
->n_tasks_valid
&& y
->n_tasks_valid
) {
414 if (x
->n_tasks
> y
->n_tasks
)
416 else if (x
->n_tasks
< y
->n_tasks
)
418 } else if (x
->n_tasks_valid
)
420 else if (y
->n_tasks_valid
)
426 if (x
->memory_valid
&& y
->memory_valid
) {
427 if (x
->memory
> y
->memory
)
429 else if (x
->memory
< y
->memory
)
431 } else if (x
->memory_valid
)
433 else if (y
->memory_valid
)
439 if (x
->io_valid
&& y
->io_valid
) {
440 if (x
->io_input_bps
+ x
->io_output_bps
> y
->io_input_bps
+ y
->io_output_bps
)
442 else if (x
->io_input_bps
+ x
->io_output_bps
< y
->io_input_bps
+ y
->io_output_bps
)
444 } else if (x
->io_valid
)
446 else if (y
->io_valid
)
450 return path_compare(x
->path
, y
->path
);
453 #define ON ANSI_HIGHLIGHT_ON
454 #define OFF ANSI_HIGHLIGHT_OFF
456 static int display(Hashmap
*a
) {
461 unsigned rows
, n
= 0, j
, maxtcpu
= 0, maxtpath
= 3; /* 3 for ellipsize() to work properly */
462 char buffer
[MAX3(21, FORMAT_BYTES_MAX
, FORMAT_TIMESPAN_MAX
)];
466 /* Set cursor to top left corner and clear screen */
471 array
= alloca(sizeof(Group
*) * hashmap_size(a
));
473 HASHMAP_FOREACH(g
, a
, i
)
474 if (g
->n_tasks_valid
|| g
->cpu_valid
|| g
->memory_valid
|| g
->io_valid
)
477 qsort_safe(array
, n
, sizeof(Group
*), group_compare
);
479 /* Find the longest names in one run */
480 for (j
= 0; j
< n
; j
++) {
481 unsigned cputlen
, pathtlen
;
483 format_timespan(buffer
, sizeof(buffer
), (usec_t
) (array
[j
]->cpu_usage
/ NSEC_PER_USEC
), 0);
484 cputlen
= strlen(buffer
);
485 maxtcpu
= MAX(maxtcpu
, cputlen
);
487 pathtlen
= strlen(array
[j
]->path
);
488 maxtpath
= MAX(maxtpath
, pathtlen
);
491 if (arg_cpu_type
== CPU_PERCENT
)
492 snprintf(buffer
, sizeof(buffer
), "%6s", "%CPU");
494 snprintf(buffer
, sizeof(buffer
), "%*s", maxtcpu
, "CPU Time");
501 path_columns
= columns() - 36 - strlen(buffer
);
502 if (path_columns
< 10)
505 printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
506 arg_order
== ORDER_PATH
? ON
: "", path_columns
, "Control Group",
507 arg_order
== ORDER_PATH
? OFF
: "",
508 arg_order
== ORDER_TASKS
? ON
: "", "Tasks",
509 arg_order
== ORDER_TASKS
? OFF
: "",
510 arg_order
== ORDER_CPU
? ON
: "", buffer
,
511 arg_order
== ORDER_CPU
? OFF
: "",
512 arg_order
== ORDER_MEMORY
? ON
: "", "Memory",
513 arg_order
== ORDER_MEMORY
? OFF
: "",
514 arg_order
== ORDER_IO
? ON
: "", "Input/s",
515 arg_order
== ORDER_IO
? OFF
: "",
516 arg_order
== ORDER_IO
? ON
: "", "Output/s",
517 arg_order
== ORDER_IO
? OFF
: "");
519 path_columns
= maxtpath
;
521 for (j
= 0; j
< n
; j
++) {
522 _cleanup_free_
char *p
= NULL
;
524 if (on_tty() && j
+ 5 > rows
)
529 p
= ellipsize(g
->path
, path_columns
, 33);
530 printf("%-*s", path_columns
, p
?: g
->path
);
532 if (g
->n_tasks_valid
)
533 printf(" %7u", g
->n_tasks
);
537 if (arg_cpu_type
== CPU_PERCENT
) {
539 printf(" %6.1f", g
->cpu_fraction
*100);
543 printf(" %*s", maxtcpu
, format_timespan(buffer
, sizeof(buffer
), (usec_t
) (g
->cpu_usage
/ NSEC_PER_USEC
), 0));
545 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->memory_valid
, g
->memory
));
546 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_input_bps
));
547 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_output_bps
));
555 static void help(void) {
556 printf("%s [OPTIONS...]\n\n"
557 "Show top control groups by their resource usage.\n\n"
558 " -h --help Show this help\n"
559 " --version Show package version\n"
560 " -p --order=path Order by path\n"
561 " -t --order=tasks Order by number of tasks\n"
562 " -c --order=cpu Order by CPU load (default)\n"
563 " -m --order=memory Order by memory load\n"
564 " -i --order=io Order by IO load\n"
565 " -r --raw Provide raw (not human-readable) numbers\n"
566 " --cpu=percentage Show CPU usage as percentage (default)\n"
567 " --cpu=time Show CPU usage as time\n"
568 " -d --delay=DELAY Delay between updates\n"
569 " -n --iterations=N Run for N iterations before exiting\n"
570 " -b --batch Run in batch mode, accepting no input\n"
571 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
572 , program_invocation_short_name
, arg_depth
);
575 static int parse_argv(int argc
, char *argv
[]) {
584 static const struct option options
[] = {
585 { "help", no_argument
, NULL
, 'h' },
586 { "version", no_argument
, NULL
, ARG_VERSION
},
587 { "delay", required_argument
, NULL
, 'd' },
588 { "iterations", required_argument
, NULL
, 'n' },
589 { "batch", no_argument
, NULL
, 'b' },
590 { "raw", no_argument
, NULL
, 'r' },
591 { "depth", required_argument
, NULL
, ARG_DEPTH
},
592 { "cpu", optional_argument
, NULL
, ARG_CPU_TYPE
},
593 { "order", required_argument
, NULL
, ARG_ORDER
},
603 while ((c
= getopt_long(argc
, argv
, "hptcmin:brd:", options
, NULL
)) >= 0)
612 puts(PACKAGE_STRING
);
613 puts(SYSTEMD_FEATURES
);
618 if (streq(optarg
, "time"))
619 arg_cpu_type
= CPU_TIME
;
620 else if (streq(optarg
, "percentage"))
621 arg_cpu_type
= CPU_PERCENT
;
623 log_error("Unknown argument to --cpu=: %s", optarg
);
627 arg_cpu_type
= CPU_TIME
;
632 r
= safe_atou(optarg
, &arg_depth
);
634 log_error("Failed to parse depth parameter.");
641 r
= parse_sec(optarg
, &arg_delay
);
642 if (r
< 0 || arg_delay
<= 0) {
643 log_error("Failed to parse delay parameter.");
650 r
= safe_atou(optarg
, &arg_iterations
);
652 log_error("Failed to parse iterations parameter.");
667 arg_order
= ORDER_PATH
;
671 arg_order
= ORDER_TASKS
;
675 arg_order
= ORDER_CPU
;
679 arg_order
= ORDER_MEMORY
;
683 arg_order
= ORDER_IO
;
687 if (streq(optarg
, "path"))
688 arg_order
= ORDER_PATH
;
689 else if (streq(optarg
, "tasks"))
690 arg_order
= ORDER_TASKS
;
691 else if (streq(optarg
, "cpu"))
692 arg_order
= ORDER_CPU
;
693 else if (streq(optarg
, "memory"))
694 arg_order
= ORDER_MEMORY
;
695 else if (streq(optarg
, "io"))
696 arg_order
= ORDER_IO
;
698 log_error("Invalid argument to --order=: %s", optarg
);
707 assert_not_reached("Unhandled option");
711 log_error("Too many arguments.");
718 int main(int argc
, char *argv
[]) {
720 Hashmap
*a
= NULL
, *b
= NULL
;
721 unsigned iteration
= 0;
722 usec_t last_refresh
= 0;
723 bool quit
= false, immediate_refresh
= false;
725 log_parse_environment();
728 r
= parse_argv(argc
, argv
);
732 a
= hashmap_new(&string_hash_ops
);
733 b
= hashmap_new(&string_hash_ops
);
739 signal(SIGWINCH
, columns_lines_cache_reset
);
741 if (arg_iterations
== (unsigned) -1)
742 arg_iterations
= on_tty() ? 0 : 1;
748 char h
[FORMAT_TIMESPAN_MAX
];
750 t
= now(CLOCK_MONOTONIC
);
752 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
754 r
= refresh(a
, b
, iteration
++);
758 group_hashmap_clear(b
);
765 immediate_refresh
= false;
772 if (arg_iterations
&& iteration
>= arg_iterations
)
775 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
780 usleep(last_refresh
+ arg_delay
- t
);
782 r
= read_one_char(stdin
, &key
, last_refresh
+ arg_delay
- t
, NULL
);
786 log_error_errno(r
, "Couldn't read key: %m");
791 if (on_tty()) { /* TTY: Clear any user keystroke */
792 fputs("\r \r", stdout
);
802 immediate_refresh
= true;
810 arg_order
= ORDER_PATH
;
814 arg_order
= ORDER_TASKS
;
818 arg_order
= ORDER_CPU
;
822 arg_order
= ORDER_MEMORY
;
826 arg_order
= ORDER_IO
;
830 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
834 if (arg_delay
< USEC_PER_SEC
)
835 arg_delay
+= USEC_PER_MSEC
*250;
837 arg_delay
+= USEC_PER_SEC
;
839 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
845 if (arg_delay
<= USEC_PER_MSEC
*500)
846 arg_delay
= USEC_PER_MSEC
*250;
847 else if (arg_delay
< USEC_PER_MSEC
*1250)
848 arg_delay
-= USEC_PER_MSEC
*250;
850 arg_delay
-= USEC_PER_SEC
;
852 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
860 "\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"
861 "\t<" ON
"+" OFF
"> Increase delay; <" ON
"-" OFF
"> Decrease delay; <" ON
"%%" OFF
"> Toggle time\n"
862 "\t<" ON
"q" OFF
"> Quit; <" ON
"SPACE" OFF
"> Refresh");
869 fprintf(stdout
, "\nUnknown key '\\x%x'. Ignoring.", key
);
871 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
881 group_hashmap_free(a
);
882 group_hashmap_free(b
);
885 log_error_errno(r
, "Exiting with failure: %m");