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 (yr
> 0 || yw
> 0) {
285 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
286 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
)
753 usleep(last_refresh
+ arg_delay
- t
);
755 r
= read_one_char(stdin
, &key
,
756 last_refresh
+ arg_delay
- t
, NULL
);
760 log_error_errno(r
, "Couldn't read key: %m");
765 fputs("\r \r", stdout
);
774 immediate_refresh
= true;
782 arg_order
= ORDER_PATH
;
786 arg_order
= ORDER_TASKS
;
790 arg_order
= ORDER_CPU
;
794 arg_order
= ORDER_MEMORY
;
798 arg_order
= ORDER_IO
;
802 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
806 if (arg_delay
< USEC_PER_SEC
)
807 arg_delay
+= USEC_PER_MSEC
*250;
809 arg_delay
+= USEC_PER_SEC
;
811 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
817 if (arg_delay
<= USEC_PER_MSEC
*500)
818 arg_delay
= USEC_PER_MSEC
*250;
819 else if (arg_delay
< USEC_PER_MSEC
*1250)
820 arg_delay
-= USEC_PER_MSEC
*250;
822 arg_delay
-= USEC_PER_SEC
;
824 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
832 "\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"
833 "\t<" ON
"+" OFF
"> Increase delay; <" ON
"-" OFF
"> Decrease delay; <" ON
"%%" OFF
"> Toggle time\n"
834 "\t<" ON
"q" OFF
"> Quit; <" ON
"SPACE" OFF
"> Refresh");
840 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
850 group_hashmap_free(a
);
851 group_hashmap_free(b
);
854 log_error_errno(r
, "Exiting with failure: %m");