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 General Public License as published by
10 the Free Software Foundation; either version 2 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 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
31 #include "cgroup-util.h"
33 typedef struct Group
{
43 unsigned cpu_iteration
;
45 struct timespec cpu_timestamp
;
50 unsigned io_iteration
;
51 uint64_t io_input
, io_output
;
52 struct timespec io_timestamp
;
53 uint64_t io_input_bps
, io_output_bps
;
56 static unsigned arg_depth
= 2;
57 static usec_t arg_delay
= 1*USEC_PER_SEC
;
65 } arg_order
= ORDER_CPU
;
67 static void group_free(Group
*g
) {
74 static void group_hashmap_clear(Hashmap
*h
) {
77 while ((g
= hashmap_steal_first(h
)))
81 static void group_hashmap_free(Hashmap
*h
) {
82 group_hashmap_clear(h
);
86 static int process(const char *controller
, const char *path
, Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
97 g
= hashmap_get(a
, path
);
99 g
= hashmap_get(b
, path
);
105 g
->path
= strdup(path
);
111 r
= hashmap_put(a
, g
->path
, g
);
117 assert_se(hashmap_move_one(a
, b
, path
) == 0);
118 g
->cpu_valid
= g
->memory_valid
= g
->io_valid
= g
->n_tasks_valid
= false;
122 /* Regardless which controller, let's find the maximum number
123 * of processes in any of it */
125 r
= cg_enumerate_tasks(controller
, path
, &f
);
130 while (cg_read_pid(f
, &pid
) > 0)
135 if (g
->n_tasks_valid
)
136 g
->n_tasks
= MAX(g
->n_tasks
, n
);
140 g
->n_tasks_valid
= true;
143 if (streq(controller
, "cpuacct")) {
148 r
= cg_get_path(controller
, path
, "cpuacct.usage", &p
);
152 r
= read_one_line_file(p
, &v
);
157 r
= safe_atou64(v
, &new_usage
);
162 assert_se(clock_gettime(CLOCK_MONOTONIC
, &ts
) == 0);
164 if (g
->cpu_iteration
== iteration
- 1) {
167 x
= ((uint64_t) ts
.tv_sec
* 1000000000ULL + (uint64_t) ts
.tv_nsec
) -
168 ((uint64_t) g
->cpu_timestamp
.tv_sec
* 1000000000ULL + (uint64_t) g
->cpu_timestamp
.tv_nsec
);
170 y
= new_usage
- g
->cpu_usage
;
173 g
->cpu_fraction
= (double) y
/ (double) x
;
178 g
->cpu_usage
= new_usage
;
179 g
->cpu_timestamp
= ts
;
180 g
->cpu_iteration
= iteration
;
182 } else if (streq(controller
, "memory")) {
185 r
= cg_get_path(controller
, path
, "memory.usage_in_bytes", &p
);
189 r
= read_one_line_file(p
, &v
);
194 r
= safe_atou64(v
, &g
->memory
);
200 g
->memory_valid
= true;
202 } else if (streq(controller
, "blkio")) {
204 uint64_t wr
= 0, rd
= 0;
207 r
= cg_get_path(controller
, path
, "blkio.io_service_bytes", &p
);
218 char line
[LINE_MAX
], *l
;
221 if (!fgets(line
, sizeof(line
), f
))
225 l
+= strcspn(l
, WHITESPACE
);
226 l
+= strspn(l
, WHITESPACE
);
228 if (first_word(l
, "Read")) {
231 } else if (first_word(l
, "Write")) {
237 l
+= strspn(l
, WHITESPACE
);
238 r
= safe_atou64(l
, &k
);
247 assert_se(clock_gettime(CLOCK_MONOTONIC
, &ts
) == 0);
249 if (g
->io_iteration
== iteration
- 1) {
252 x
= ((uint64_t) ts
.tv_sec
* 1000000000ULL + (uint64_t) ts
.tv_nsec
) -
253 ((uint64_t) g
->io_timestamp
.tv_sec
* 1000000000ULL + (uint64_t) g
->io_timestamp
.tv_nsec
);
255 yr
= rd
- g
->io_input
;
256 yw
= wr
- g
->io_output
;
258 if (yr
> 0 || yw
> 0) {
259 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
260 g
->io_output_bps
= (yw
* 1000000000ULL) / x
;
268 g
->io_timestamp
= ts
;
269 g
->io_iteration
= iteration
;
275 static int refresh_one(
276 const char *controller
,
290 if (depth
> arg_depth
)
293 r
= process(controller
, path
, a
, b
, iteration
);
297 r
= cg_enumerate_subgroups(controller
, path
, &d
);
308 r
= cg_read_subgroup(d
, &fn
);
312 p
= join(path
, "/", fn
, NULL
);
320 path_kill_slashes(p
);
322 r
= refresh_one(controller
, p
, a
, b
, iteration
, depth
+ 1);
336 static int refresh(Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
341 r
= refresh_one("name=systemd", "/", a
, b
, iteration
, 0);
345 r
= refresh_one("cpuacct", "/", a
, b
, iteration
, 0);
349 r
= refresh_one("memory", "/", a
, b
, iteration
, 0);
353 return refresh_one("blkio", "/", a
, b
, iteration
, 0);
356 static int group_compare(const void*a
, const void *b
) {
357 const Group
*x
= *(Group
**)a
, *y
= *(Group
**)b
;
359 if (path_startswith(y
->path
, x
->path
))
361 if (path_startswith(x
->path
, y
->path
))
364 if (arg_order
== ORDER_CPU
) {
365 if (x
->cpu_valid
&& y
->cpu_valid
) {
367 if (x
->cpu_fraction
> y
->cpu_fraction
)
369 else if (x
->cpu_fraction
< y
->cpu_fraction
)
371 } else if (x
->cpu_valid
)
373 else if (y
->cpu_valid
)
377 if (arg_order
== ORDER_TASKS
) {
379 if (x
->n_tasks_valid
&& y
->n_tasks_valid
) {
380 if (x
->n_tasks
> y
->n_tasks
)
382 else if (x
->n_tasks
< y
->n_tasks
)
384 } else if (x
->n_tasks_valid
)
386 else if (y
->n_tasks_valid
)
390 if (arg_order
== ORDER_MEMORY
) {
391 if (x
->memory_valid
&& y
->memory_valid
) {
392 if (x
->memory
> y
->memory
)
394 else if (x
->memory
< y
->memory
)
396 } else if (x
->memory_valid
)
398 else if (y
->memory_valid
)
402 if (arg_order
== ORDER_IO
) {
403 if (x
->io_valid
&& y
->io_valid
) {
404 if (x
->io_input_bps
+ x
->io_output_bps
> y
->io_input_bps
+ y
->io_output_bps
)
406 else if (x
->io_input_bps
+ x
->io_output_bps
< y
->io_input_bps
+ y
->io_output_bps
)
408 } else if (x
->io_valid
)
410 else if (y
->io_valid
)
414 return strcmp(x
->path
, y
->path
);
417 static int display(Hashmap
*a
) {
421 unsigned rows
, n
= 0, j
;
425 /* Set cursor to top left corner and clear screen */
429 array
= alloca(sizeof(Group
*) * hashmap_size(a
));
431 HASHMAP_FOREACH(g
, a
, i
)
432 if (g
->n_tasks_valid
|| g
->cpu_valid
|| g
->memory_valid
|| g
->io_valid
)
435 qsort(array
, n
, sizeof(Group
*), group_compare
);
437 rows
= fd_lines(STDOUT_FILENO
);
441 printf("%s%-37s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
442 arg_order
== ORDER_PATH
? ANSI_HIGHLIGHT_ON
: "", "Path", arg_order
== ORDER_PATH
? ANSI_HIGHLIGHT_OFF
: "",
443 arg_order
== ORDER_TASKS
? ANSI_HIGHLIGHT_ON
: "", "Tasks", arg_order
== ORDER_TASKS
? ANSI_HIGHLIGHT_OFF
: "",
444 arg_order
== ORDER_CPU
? ANSI_HIGHLIGHT_ON
: "", "%CPU", arg_order
== ORDER_CPU
? ANSI_HIGHLIGHT_OFF
: "",
445 arg_order
== ORDER_MEMORY
? ANSI_HIGHLIGHT_ON
: "", "Memory", arg_order
== ORDER_MEMORY
? ANSI_HIGHLIGHT_OFF
: "",
446 arg_order
== ORDER_IO
? ANSI_HIGHLIGHT_ON
: "", "Input/s", arg_order
== ORDER_IO
? ANSI_HIGHLIGHT_OFF
: "",
447 arg_order
== ORDER_IO
? ANSI_HIGHLIGHT_ON
: "", "Output/s", arg_order
== ORDER_IO
? ANSI_HIGHLIGHT_OFF
: "");
449 for (j
= 0; j
< n
; j
++) {
451 char m
[FORMAT_BYTES_MAX
];
458 p
= ellipsize(g
->path
, 37, 33);
459 printf("%-37s", p
? p
: g
->path
);
462 if (g
->n_tasks_valid
)
463 printf(" %7u", g
->n_tasks
);
468 printf(" %6.1f", g
->cpu_fraction
*100);
473 printf(" %8s", format_bytes(m
, sizeof(m
), g
->memory
));
479 format_bytes(m
, sizeof(m
), g
->io_input_bps
));
481 format_bytes(m
, sizeof(m
), g
->io_output_bps
));
483 fputs(" - -", stdout
);
491 static void help(void) {
493 printf("%s [OPTIONS...]\n\n"
494 "Show top control groups by their resource usage.\n\n"
495 " -h --help Show this help\n"
496 " -p Order by path\n"
497 " -t Order by number of tasks\n"
498 " -c Order by CPU load\n"
499 " -m Order by memory load\n"
500 " -i Order by IO load\n"
501 " -d --delay=DELAY Specify delay\n"
502 " --depth=DEPTH Maximum traversal depth\n",
503 program_invocation_short_name
);
506 static int parse_argv(int argc
, char *argv
[]) {
512 static const struct option options
[] = {
513 { "help", no_argument
, NULL
, 'h' },
514 { "delay", required_argument
, NULL
, 'd' },
515 { "depth", required_argument
, NULL
, ARG_DEPTH
},
525 while ((c
= getopt_long(argc
, argv
, "hptcmid:", options
, NULL
)) >= 0) {
534 r
= safe_atou(optarg
, &arg_depth
);
536 log_error("Failed to parse depth parameter.");
543 r
= parse_usec(optarg
, &arg_delay
);
544 if (r
< 0 || arg_delay
<= 0) {
545 log_error("Failed to parse delay parameter.");
552 arg_order
= ORDER_PATH
;
556 arg_order
= ORDER_TASKS
;
560 arg_order
= ORDER_CPU
;
564 arg_order
= ORDER_MEMORY
;
568 arg_order
= ORDER_IO
;
575 log_error("Unknown option code %c", c
);
581 log_error("Too many arguments.");
588 int main(int argc
, char *argv
[]) {
590 Hashmap
*a
= NULL
, *b
= NULL
;
591 unsigned iteration
= 0;
592 usec_t last_refresh
= 0;
593 bool quit
= false, immediate_refresh
= false;
595 log_parse_environment();
598 r
= parse_argv(argc
, argv
);
602 a
= hashmap_new(string_hash_func
, string_compare_func
);
603 b
= hashmap_new(string_hash_func
, string_compare_func
);
605 log_error("Out of memory");
614 char h
[FORMAT_TIMESPAN_MAX
];
616 t
= now(CLOCK_MONOTONIC
);
618 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
620 r
= refresh(a
, b
, iteration
++);
624 group_hashmap_clear(b
);
631 immediate_refresh
= false;
638 r
= read_one_char(stdin
, &key
, last_refresh
+ arg_delay
- t
, NULL
);
642 log_error("Couldn't read key: %s", strerror(-r
));
646 fputs("\r \r", stdout
);
652 immediate_refresh
= true;
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
;
680 if (arg_delay
< USEC_PER_SEC
)
681 arg_delay
+= USEC_PER_MSEC
*250;
683 arg_delay
+= USEC_PER_SEC
;
685 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
));
691 if (arg_delay
<= USEC_PER_MSEC
*500)
692 arg_delay
= USEC_PER_MSEC
*250;
693 else if (arg_delay
< USEC_PER_MSEC
*1250)
694 arg_delay
-= USEC_PER_MSEC
*250;
696 arg_delay
-= USEC_PER_SEC
;
698 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
));
706 "\t<" ANSI_HIGHLIGHT_ON
"P" ANSI_HIGHLIGHT_OFF
"> By path; <" ANSI_HIGHLIGHT_ON
"T" ANSI_HIGHLIGHT_OFF
"> By tasks; <" ANSI_HIGHLIGHT_ON
"C" ANSI_HIGHLIGHT_OFF
"> By CPU; <" ANSI_HIGHLIGHT_ON
"M" ANSI_HIGHLIGHT_OFF
"> By memory; <" ANSI_HIGHLIGHT_ON
"I" ANSI_HIGHLIGHT_OFF
"> By I/O\n"
707 "\t<" ANSI_HIGHLIGHT_ON
"Q" ANSI_HIGHLIGHT_OFF
"> Quit; <" ANSI_HIGHLIGHT_ON
"+" ANSI_HIGHLIGHT_OFF
"> Increase delay; <" ANSI_HIGHLIGHT_ON
"-" ANSI_HIGHLIGHT_OFF
"> Decrease delay; <" ANSI_HIGHLIGHT_ON
"SPACE" ANSI_HIGHLIGHT_OFF
"> Refresh");
713 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
720 log_info("Exiting.");
725 group_hashmap_free(a
);
726 group_hashmap_free(b
);
728 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;