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/>.
22 #define __STDC_FORMAT_MACROS
31 #include "path-util.h"
32 #include "terminal-util.h"
35 #include "cgroup-util.h"
39 typedef struct Group
{
49 unsigned cpu_iteration
;
51 struct timespec cpu_timestamp
;
56 unsigned io_iteration
;
57 uint64_t io_input
, io_output
;
58 struct timespec io_timestamp
;
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
) {
121 g
= hashmap_get(a
, path
);
123 g
= hashmap_get(b
, path
);
129 g
->path
= strdup(path
);
135 r
= hashmap_put(a
, g
->path
, g
);
141 r
= hashmap_move_one(a
, b
, path
);
144 g
->cpu_valid
= g
->memory_valid
= g
->io_valid
= g
->n_tasks_valid
= false;
148 /* Regardless which controller, let's find the maximum number
149 * of processes in any of it */
151 r
= cg_enumerate_processes(controller
, path
, &f
);
156 while (cg_read_pid(f
, &pid
) > 0)
161 if (g
->n_tasks_valid
)
162 g
->n_tasks
= MAX(g
->n_tasks
, n
);
166 g
->n_tasks_valid
= true;
169 if (streq(controller
, "cpuacct")) {
174 r
= cg_get_path(controller
, path
, "cpuacct.usage", &p
);
178 r
= read_one_line_file(p
, &v
);
183 r
= safe_atou64(v
, &new_usage
);
188 assert_se(clock_gettime(CLOCK_MONOTONIC
, &ts
) == 0);
190 if (g
->cpu_iteration
== iteration
- 1) {
193 x
= ((uint64_t) ts
.tv_sec
* 1000000000ULL + (uint64_t) ts
.tv_nsec
) -
194 ((uint64_t) g
->cpu_timestamp
.tv_sec
* 1000000000ULL + (uint64_t) g
->cpu_timestamp
.tv_nsec
);
196 y
= new_usage
- g
->cpu_usage
;
199 g
->cpu_fraction
= (double) y
/ (double) x
;
204 g
->cpu_usage
= new_usage
;
205 g
->cpu_timestamp
= ts
;
206 g
->cpu_iteration
= iteration
;
208 } else if (streq(controller
, "memory")) {
211 r
= cg_get_path(controller
, path
, "memory.usage_in_bytes", &p
);
215 r
= read_one_line_file(p
, &v
);
220 r
= safe_atou64(v
, &g
->memory
);
226 g
->memory_valid
= true;
228 } else if (streq(controller
, "blkio")) {
230 uint64_t wr
= 0, rd
= 0;
233 r
= cg_get_path(controller
, path
, "blkio.io_service_bytes", &p
);
244 char line
[LINE_MAX
], *l
;
247 if (!fgets(line
, sizeof(line
), f
))
251 l
+= strcspn(l
, WHITESPACE
);
252 l
+= strspn(l
, WHITESPACE
);
254 if (first_word(l
, "Read")) {
257 } else if (first_word(l
, "Write")) {
263 l
+= strspn(l
, WHITESPACE
);
264 r
= safe_atou64(l
, &k
);
273 assert_se(clock_gettime(CLOCK_MONOTONIC
, &ts
) == 0);
275 if (g
->io_iteration
== iteration
- 1) {
278 x
= ((uint64_t) ts
.tv_sec
* 1000000000ULL + (uint64_t) ts
.tv_nsec
) -
279 ((uint64_t) g
->io_timestamp
.tv_sec
* 1000000000ULL + (uint64_t) g
->io_timestamp
.tv_nsec
);
281 yr
= rd
- g
->io_input
;
282 yw
= wr
- g
->io_output
;
284 if (g
->io_input
> 0 || g
->io_output
> 0) {
285 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
286 g
->io_output_bps
= (yw
* 1000000000ULL) / x
;
293 g
->io_timestamp
= ts
;
294 g
->io_iteration
= iteration
;
300 static int refresh_one(
301 const char *controller
,
315 if (depth
> arg_depth
)
318 r
= process(controller
, path
, a
, b
, iteration
);
322 r
= cg_enumerate_subgroups(controller
, path
, &d
);
333 r
= cg_read_subgroup(d
, &fn
);
337 p
= strjoin(path
, "/", fn
, NULL
);
345 path_kill_slashes(p
);
347 r
= refresh_one(controller
, p
, a
, b
, iteration
, depth
+ 1);
361 static int refresh(Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
366 r
= refresh_one("name=systemd", "/", a
, b
, iteration
, 0);
370 r
= refresh_one("cpuacct", "/", a
, b
, iteration
, 0);
374 r
= refresh_one("memory", "/", a
, b
, iteration
, 0);
379 r
= refresh_one("blkio", "/", a
, b
, iteration
, 0);
386 static int group_compare(const void*a
, const void *b
) {
387 const Group
*x
= *(Group
**)a
, *y
= *(Group
**)b
;
389 if (path_startswith(y
->path
, x
->path
))
391 if (path_startswith(x
->path
, y
->path
))
394 if (arg_order
== ORDER_CPU
) {
395 if (arg_cpu_type
== CPU_PERCENT
) {
396 if (x
->cpu_valid
&& y
->cpu_valid
) {
397 if (x
->cpu_fraction
> y
->cpu_fraction
)
399 else if (x
->cpu_fraction
< y
->cpu_fraction
)
401 } else if (x
->cpu_valid
)
403 else if (y
->cpu_valid
)
406 if (x
->cpu_usage
> y
->cpu_usage
)
408 else if (x
->cpu_usage
< y
->cpu_usage
)
413 if (arg_order
== ORDER_TASKS
) {
415 if (x
->n_tasks_valid
&& y
->n_tasks_valid
) {
416 if (x
->n_tasks
> y
->n_tasks
)
418 else if (x
->n_tasks
< y
->n_tasks
)
420 } else if (x
->n_tasks_valid
)
422 else if (y
->n_tasks_valid
)
426 if (arg_order
== ORDER_MEMORY
) {
427 if (x
->memory_valid
&& y
->memory_valid
) {
428 if (x
->memory
> y
->memory
)
430 else if (x
->memory
< y
->memory
)
432 } else if (x
->memory_valid
)
434 else if (y
->memory_valid
)
438 if (arg_order
== ORDER_IO
) {
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 strcmp(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
), (nsec_t
) (array
[j
]->cpu_usage
/ NSEC_PER_USEC
), 0);
484 cputlen
= strlen(buffer
);
485 maxtcpu
= MAX(maxtcpu
, cputlen
);
486 pathtlen
= strlen(array
[j
]->path
);
487 maxtpath
= MAX(maxtpath
, pathtlen
);
490 if (arg_cpu_type
== CPU_PERCENT
)
491 snprintf(buffer
, sizeof(buffer
), "%6s", "%CPU");
493 snprintf(buffer
, sizeof(buffer
), "%*s", maxtcpu
, "CPU Time");
500 path_columns
= columns() - 36 - strlen(buffer
);
501 if (path_columns
< 10)
504 printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
505 arg_order
== ORDER_PATH
? ON
: "", path_columns
, "Path",
506 arg_order
== ORDER_PATH
? OFF
: "",
507 arg_order
== ORDER_TASKS
? ON
: "", "Tasks",
508 arg_order
== ORDER_TASKS
? OFF
: "",
509 arg_order
== ORDER_CPU
? ON
: "", buffer
,
510 arg_order
== ORDER_CPU
? OFF
: "",
511 arg_order
== ORDER_MEMORY
? ON
: "", "Memory",
512 arg_order
== ORDER_MEMORY
? OFF
: "",
513 arg_order
== ORDER_IO
? ON
: "", "Input/s",
514 arg_order
== ORDER_IO
? OFF
: "",
515 arg_order
== ORDER_IO
? ON
: "", "Output/s",
516 arg_order
== ORDER_IO
? OFF
: "");
518 path_columns
= maxtpath
;
520 for (j
= 0; j
< n
; j
++) {
523 if (on_tty() && j
+ 5 > rows
)
528 p
= ellipsize(g
->path
, path_columns
, 33);
529 printf("%-*s", path_columns
, p
? 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
), (nsec_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 Print version and exit\n"
560 " -p Order by path\n"
561 " -t Order by number of tasks\n"
562 " -c Order by CPU load\n"
563 " -m Order by memory load\n"
564 " -i Order by IO load\n"
565 " -r --raw Provide raw (not human-readable) numbers\n"
566 " --cpu[=TYPE] Show CPU usage as time or percentage (default)\n"
567 " -d --delay=DELAY Delay between updates\n"
568 " -n --iterations=N Run for N iterations before exiting\n"
569 " -b --batch Run in batch mode, accepting no input\n"
570 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
571 , program_invocation_short_name
, arg_depth
);
574 static int parse_argv(int argc
, char *argv
[]) {
582 static const struct option options
[] = {
583 { "help", no_argument
, NULL
, 'h' },
584 { "version", no_argument
, NULL
, ARG_VERSION
},
585 { "delay", required_argument
, NULL
, 'd' },
586 { "iterations", required_argument
, NULL
, 'n' },
587 { "batch", no_argument
, NULL
, 'b' },
588 { "raw", no_argument
, NULL
, 'r' },
589 { "depth", required_argument
, NULL
, ARG_DEPTH
},
590 { "cpu", optional_argument
, NULL
, ARG_CPU_TYPE
},
600 while ((c
= getopt_long(argc
, argv
, "hptcmin:brd:", options
, NULL
)) >= 0)
609 puts(PACKAGE_STRING
);
610 puts(SYSTEMD_FEATURES
);
615 if (strcmp(optarg
, "time") == 0)
616 arg_cpu_type
= CPU_TIME
;
617 else if (strcmp(optarg
, "percentage") == 0)
618 arg_cpu_type
= CPU_PERCENT
;
625 r
= safe_atou(optarg
, &arg_depth
);
627 log_error("Failed to parse depth parameter.");
634 r
= parse_sec(optarg
, &arg_delay
);
635 if (r
< 0 || arg_delay
<= 0) {
636 log_error("Failed to parse delay parameter.");
643 r
= safe_atou(optarg
, &arg_iterations
);
645 log_error("Failed to parse iterations parameter.");
660 arg_order
= ORDER_PATH
;
664 arg_order
= ORDER_TASKS
;
668 arg_order
= ORDER_CPU
;
672 arg_order
= ORDER_MEMORY
;
676 arg_order
= ORDER_IO
;
683 assert_not_reached("Unhandled option");
687 log_error("Too many arguments.");
694 int main(int argc
, char *argv
[]) {
696 Hashmap
*a
= NULL
, *b
= NULL
;
697 unsigned iteration
= 0;
698 usec_t last_refresh
= 0;
699 bool quit
= false, immediate_refresh
= false;
701 log_parse_environment();
704 r
= parse_argv(argc
, argv
);
708 a
= hashmap_new(&string_hash_ops
);
709 b
= hashmap_new(&string_hash_ops
);
715 signal(SIGWINCH
, columns_lines_cache_reset
);
717 if (arg_iterations
== (unsigned)-1)
718 arg_iterations
= on_tty() ? 0 : 1;
724 char h
[FORMAT_TIMESPAN_MAX
];
726 t
= now(CLOCK_MONOTONIC
);
728 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
730 r
= refresh(a
, b
, iteration
++);
734 group_hashmap_clear(b
);
741 immediate_refresh
= false;
748 if (arg_iterations
&& iteration
>= arg_iterations
)
751 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
756 usleep(last_refresh
+ arg_delay
- t
);
758 r
= read_one_char(stdin
, &key
,
759 last_refresh
+ arg_delay
- t
, NULL
);
763 log_error_errno(r
, "Couldn't read key: %m");
768 if (on_tty()) { /* TTY: Clear any user keystroke */
769 fputs("\r \r", stdout
);
779 immediate_refresh
= true;
787 arg_order
= ORDER_PATH
;
791 arg_order
= ORDER_TASKS
;
795 arg_order
= ORDER_CPU
;
799 arg_order
= ORDER_MEMORY
;
803 arg_order
= ORDER_IO
;
807 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
811 if (arg_delay
< USEC_PER_SEC
)
812 arg_delay
+= USEC_PER_MSEC
*250;
814 arg_delay
+= USEC_PER_SEC
;
816 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
822 if (arg_delay
<= USEC_PER_MSEC
*500)
823 arg_delay
= USEC_PER_MSEC
*250;
824 else if (arg_delay
< USEC_PER_MSEC
*1250)
825 arg_delay
-= USEC_PER_MSEC
*250;
827 arg_delay
-= USEC_PER_SEC
;
829 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
837 "\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"
838 "\t<" ON
"+" OFF
"> Increase delay; <" ON
"-" OFF
"> Decrease delay; <" ON
"%%" OFF
"> Toggle time\n"
839 "\t<" ON
"q" OFF
"> Quit; <" ON
"SPACE" OFF
"> Refresh");
845 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
855 group_hashmap_free(a
);
856 group_hashmap_free(b
);
859 log_error_errno(r
, "Exiting with failure: %m");