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
32 #include "path-util.h"
33 #include "terminal-util.h"
36 #include "cgroup-util.h"
40 typedef struct Group
{
50 unsigned cpu_iteration
;
52 struct timespec cpu_timestamp
;
57 unsigned io_iteration
;
58 uint64_t io_input
, io_output
;
59 struct timespec io_timestamp
;
60 uint64_t io_input_bps
, io_output_bps
;
63 static unsigned arg_depth
= 3;
64 static unsigned arg_iterations
= (unsigned)-1;
65 static bool arg_batch
= false;
66 static bool arg_raw
= false;
67 static usec_t arg_delay
= 1*USEC_PER_SEC
;
75 } arg_order
= ORDER_CPU
;
80 } arg_cpu_type
= CPU_PERCENT
;
82 static void group_free(Group
*g
) {
89 static void group_hashmap_clear(Hashmap
*h
) {
92 while ((g
= hashmap_steal_first(h
)))
96 static void group_hashmap_free(Hashmap
*h
) {
97 group_hashmap_clear(h
);
101 static const char *maybe_format_bytes(char *buf
, size_t l
, bool is_valid
, off_t t
) {
105 snprintf(buf
, l
, "%jd", t
);
108 return format_bytes(buf
, l
, t
);
111 static int process(const char *controller
, const char *path
, Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
122 g
= hashmap_get(a
, path
);
124 g
= hashmap_get(b
, path
);
130 g
->path
= strdup(path
);
136 r
= hashmap_put(a
, g
->path
, g
);
142 r
= hashmap_move_one(a
, b
, path
);
145 g
->cpu_valid
= g
->memory_valid
= g
->io_valid
= g
->n_tasks_valid
= false;
149 /* Regardless which controller, let's find the maximum number
150 * of processes in any of it */
152 r
= cg_enumerate_processes(controller
, path
, &f
);
157 while (cg_read_pid(f
, &pid
) > 0)
162 if (g
->n_tasks_valid
)
163 g
->n_tasks
= MAX(g
->n_tasks
, n
);
167 g
->n_tasks_valid
= true;
170 if (streq(controller
, "cpuacct")) {
175 r
= cg_get_path(controller
, path
, "cpuacct.usage", &p
);
179 r
= read_one_line_file(p
, &v
);
184 r
= safe_atou64(v
, &new_usage
);
189 assert_se(clock_gettime(CLOCK_MONOTONIC
, &ts
) == 0);
191 if (g
->cpu_iteration
== iteration
- 1) {
194 x
= ((uint64_t) ts
.tv_sec
* 1000000000ULL + (uint64_t) ts
.tv_nsec
) -
195 ((uint64_t) g
->cpu_timestamp
.tv_sec
* 1000000000ULL + (uint64_t) g
->cpu_timestamp
.tv_nsec
);
197 y
= new_usage
- g
->cpu_usage
;
200 g
->cpu_fraction
= (double) y
/ (double) x
;
205 g
->cpu_usage
= new_usage
;
206 g
->cpu_timestamp
= ts
;
207 g
->cpu_iteration
= iteration
;
209 } else if (streq(controller
, "memory")) {
212 r
= cg_get_path(controller
, path
, "memory.usage_in_bytes", &p
);
216 r
= read_one_line_file(p
, &v
);
221 r
= safe_atou64(v
, &g
->memory
);
227 g
->memory_valid
= true;
229 } else if (streq(controller
, "blkio")) {
231 uint64_t wr
= 0, rd
= 0;
234 r
= cg_get_path(controller
, path
, "blkio.io_service_bytes", &p
);
245 char line
[LINE_MAX
], *l
;
248 if (!fgets(line
, sizeof(line
), f
))
252 l
+= strcspn(l
, WHITESPACE
);
253 l
+= strspn(l
, WHITESPACE
);
255 if (first_word(l
, "Read")) {
258 } else if (first_word(l
, "Write")) {
264 l
+= strspn(l
, WHITESPACE
);
265 r
= safe_atou64(l
, &k
);
274 assert_se(clock_gettime(CLOCK_MONOTONIC
, &ts
) == 0);
276 if (g
->io_iteration
== iteration
- 1) {
279 x
= ((uint64_t) ts
.tv_sec
* 1000000000ULL + (uint64_t) ts
.tv_nsec
) -
280 ((uint64_t) g
->io_timestamp
.tv_sec
* 1000000000ULL + (uint64_t) g
->io_timestamp
.tv_nsec
);
282 yr
= rd
- g
->io_input
;
283 yw
= wr
- g
->io_output
;
285 if (g
->io_input
> 0 || g
->io_output
> 0) {
286 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
287 g
->io_output_bps
= (yw
* 1000000000ULL) / x
;
294 g
->io_timestamp
= ts
;
295 g
->io_iteration
= iteration
;
301 static int refresh_one(
302 const char *controller
,
316 if (depth
> arg_depth
)
319 r
= process(controller
, path
, a
, b
, iteration
);
323 r
= cg_enumerate_subgroups(controller
, path
, &d
);
334 r
= cg_read_subgroup(d
, &fn
);
338 p
= strjoin(path
, "/", fn
, NULL
);
346 path_kill_slashes(p
);
348 r
= refresh_one(controller
, p
, a
, b
, iteration
, depth
+ 1);
362 static int refresh(Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
367 r
= refresh_one("name=systemd", "/", a
, b
, iteration
, 0);
371 r
= refresh_one("cpuacct", "/", a
, b
, iteration
, 0);
375 r
= refresh_one("memory", "/", a
, b
, iteration
, 0);
380 r
= refresh_one("blkio", "/", a
, b
, iteration
, 0);
387 static int group_compare(const void*a
, const void *b
) {
388 const Group
*x
= *(Group
**)a
, *y
= *(Group
**)b
;
390 if (path_startswith(y
->path
, x
->path
))
392 if (path_startswith(x
->path
, y
->path
))
395 if (arg_order
== ORDER_CPU
) {
396 if (arg_cpu_type
== CPU_PERCENT
) {
397 if (x
->cpu_valid
&& y
->cpu_valid
) {
398 if (x
->cpu_fraction
> y
->cpu_fraction
)
400 else if (x
->cpu_fraction
< y
->cpu_fraction
)
402 } else if (x
->cpu_valid
)
404 else if (y
->cpu_valid
)
407 if (x
->cpu_usage
> y
->cpu_usage
)
409 else if (x
->cpu_usage
< y
->cpu_usage
)
414 if (arg_order
== ORDER_TASKS
) {
416 if (x
->n_tasks_valid
&& y
->n_tasks_valid
) {
417 if (x
->n_tasks
> y
->n_tasks
)
419 else if (x
->n_tasks
< y
->n_tasks
)
421 } else if (x
->n_tasks_valid
)
423 else if (y
->n_tasks_valid
)
427 if (arg_order
== ORDER_MEMORY
) {
428 if (x
->memory_valid
&& y
->memory_valid
) {
429 if (x
->memory
> y
->memory
)
431 else if (x
->memory
< y
->memory
)
433 } else if (x
->memory_valid
)
435 else if (y
->memory_valid
)
439 if (arg_order
== ORDER_IO
) {
440 if (x
->io_valid
&& y
->io_valid
) {
441 if (x
->io_input_bps
+ x
->io_output_bps
> y
->io_input_bps
+ y
->io_output_bps
)
443 else if (x
->io_input_bps
+ x
->io_output_bps
< y
->io_input_bps
+ y
->io_output_bps
)
445 } else if (x
->io_valid
)
447 else if (y
->io_valid
)
451 return strcmp(x
->path
, y
->path
);
454 #define ON ANSI_HIGHLIGHT_ON
455 #define OFF ANSI_HIGHLIGHT_OFF
457 static int display(Hashmap
*a
) {
462 unsigned rows
, n
= 0, j
, maxtcpu
= 0, maxtpath
= 3; /* 3 for ellipsize() to work properly */
463 char buffer
[MAX3(21, FORMAT_BYTES_MAX
, FORMAT_TIMESPAN_MAX
)];
467 /* Set cursor to top left corner and clear screen */
472 array
= alloca(sizeof(Group
*) * hashmap_size(a
));
474 HASHMAP_FOREACH(g
, a
, i
)
475 if (g
->n_tasks_valid
|| g
->cpu_valid
|| g
->memory_valid
|| g
->io_valid
)
478 qsort_safe(array
, n
, sizeof(Group
*), group_compare
);
480 /* Find the longest names in one run */
481 for (j
= 0; j
< n
; j
++) {
482 unsigned cputlen
, pathtlen
;
484 format_timespan(buffer
, sizeof(buffer
), (nsec_t
) (array
[j
]->cpu_usage
/ NSEC_PER_USEC
), 0);
485 cputlen
= strlen(buffer
);
486 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
, "Path",
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
++) {
524 if (on_tty() && j
+ 5 > rows
)
529 p
= ellipsize(g
->path
, path_columns
, 33);
530 printf("%-*s", path_columns
, p
? p
: g
->path
);
533 if (g
->n_tasks_valid
)
534 printf(" %7u", g
->n_tasks
);
538 if (arg_cpu_type
== CPU_PERCENT
) {
540 printf(" %6.1f", g
->cpu_fraction
*100);
544 printf(" %*s", maxtcpu
, format_timespan(buffer
, sizeof(buffer
), (nsec_t
) (g
->cpu_usage
/ NSEC_PER_USEC
), 0));
546 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->memory_valid
, g
->memory
));
547 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_input_bps
));
548 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_output_bps
));
556 static void help(void) {
557 printf("%s [OPTIONS...]\n\n"
558 "Show top control groups by their resource usage.\n\n"
559 " -h --help Show this help\n"
560 " --version Print version and exit\n"
561 " -p Order by path\n"
562 " -t Order by number of tasks\n"
563 " -c Order by CPU load\n"
564 " -m Order by memory load\n"
565 " -i Order by IO load\n"
566 " -r --raw Provide raw (not human-readable) numbers\n"
567 " --cpu[=TYPE] Show CPU usage as time or percentage (default)\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
[]) {
583 static const struct option options
[] = {
584 { "help", no_argument
, NULL
, 'h' },
585 { "version", no_argument
, NULL
, ARG_VERSION
},
586 { "delay", required_argument
, NULL
, 'd' },
587 { "iterations", required_argument
, NULL
, 'n' },
588 { "batch", no_argument
, NULL
, 'b' },
589 { "raw", no_argument
, NULL
, 'r' },
590 { "depth", required_argument
, NULL
, ARG_DEPTH
},
591 { "cpu", optional_argument
, NULL
, ARG_CPU_TYPE
},
601 while ((c
= getopt_long(argc
, argv
, "hptcmin:brd:", options
, NULL
)) >= 0)
610 puts(PACKAGE_STRING
);
611 puts(SYSTEMD_FEATURES
);
616 if (strcmp(optarg
, "time") == 0)
617 arg_cpu_type
= CPU_TIME
;
618 else if (strcmp(optarg
, "percentage") == 0)
619 arg_cpu_type
= CPU_PERCENT
;
626 r
= safe_atou(optarg
, &arg_depth
);
628 log_error("Failed to parse depth parameter.");
635 r
= parse_sec(optarg
, &arg_delay
);
636 if (r
< 0 || arg_delay
<= 0) {
637 log_error("Failed to parse delay parameter.");
644 r
= safe_atou(optarg
, &arg_iterations
);
646 log_error("Failed to parse iterations parameter.");
661 arg_order
= ORDER_PATH
;
665 arg_order
= ORDER_TASKS
;
669 arg_order
= ORDER_CPU
;
673 arg_order
= ORDER_MEMORY
;
677 arg_order
= ORDER_IO
;
684 assert_not_reached("Unhandled option");
688 log_error("Too many arguments.");
695 int main(int argc
, char *argv
[]) {
697 Hashmap
*a
= NULL
, *b
= NULL
;
698 unsigned iteration
= 0;
699 usec_t last_refresh
= 0;
700 bool quit
= false, immediate_refresh
= false;
702 log_parse_environment();
705 r
= parse_argv(argc
, argv
);
709 a
= hashmap_new(&string_hash_ops
);
710 b
= hashmap_new(&string_hash_ops
);
716 signal(SIGWINCH
, columns_lines_cache_reset
);
718 if (arg_iterations
== (unsigned)-1)
719 arg_iterations
= on_tty() ? 0 : 1;
725 char h
[FORMAT_TIMESPAN_MAX
];
727 t
= now(CLOCK_MONOTONIC
);
729 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
731 r
= refresh(a
, b
, iteration
++);
735 group_hashmap_clear(b
);
742 immediate_refresh
= false;
749 if (arg_iterations
&& iteration
>= arg_iterations
)
752 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
757 usleep(last_refresh
+ arg_delay
- t
);
759 r
= read_one_char(stdin
, &key
,
760 last_refresh
+ arg_delay
- t
, NULL
);
764 log_error_errno(r
, "Couldn't read key: %m");
769 if (on_tty()) { /* TTY: Clear any user keystroke */
770 fputs("\r \r", stdout
);
780 immediate_refresh
= true;
788 arg_order
= ORDER_PATH
;
792 arg_order
= ORDER_TASKS
;
796 arg_order
= ORDER_CPU
;
800 arg_order
= ORDER_MEMORY
;
804 arg_order
= ORDER_IO
;
808 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
812 if (arg_delay
< USEC_PER_SEC
)
813 arg_delay
+= USEC_PER_MSEC
*250;
815 arg_delay
+= USEC_PER_SEC
;
817 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
823 if (arg_delay
<= USEC_PER_MSEC
*500)
824 arg_delay
= USEC_PER_MSEC
*250;
825 else if (arg_delay
< USEC_PER_MSEC
*1250)
826 arg_delay
-= USEC_PER_MSEC
*250;
828 arg_delay
-= USEC_PER_SEC
;
830 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
838 "\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"
839 "\t<" ON
"+" OFF
"> Increase delay; <" ON
"-" OFF
"> Decrease delay; <" ON
"%%" OFF
"> Toggle time\n"
840 "\t<" ON
"q" OFF
"> Quit; <" ON
"SPACE" OFF
"> Refresh");
846 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
856 group_hashmap_free(a
);
857 group_hashmap_free(b
);
860 log_error_errno(r
, "Exiting with failure: %m");