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"
34 #include "cgroup-util.h"
38 typedef struct Group
{
48 unsigned cpu_iteration
;
50 struct timespec cpu_timestamp
;
55 unsigned io_iteration
;
56 uint64_t io_input
, io_output
;
57 struct timespec io_timestamp
;
58 uint64_t io_input_bps
, io_output_bps
;
61 static unsigned arg_depth
= 3;
62 static unsigned arg_iterations
= 0;
63 static bool arg_batch
= false;
64 static usec_t arg_delay
= 1*USEC_PER_SEC
;
72 } arg_order
= ORDER_CPU
;
77 } arg_cpu_type
= CPU_PERCENT
;
79 static void group_free(Group
*g
) {
86 static void group_hashmap_clear(Hashmap
*h
) {
89 while ((g
= hashmap_steal_first(h
)))
93 static void group_hashmap_free(Hashmap
*h
) {
94 group_hashmap_clear(h
);
98 static int process(const char *controller
, const char *path
, Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
109 g
= hashmap_get(a
, path
);
111 g
= hashmap_get(b
, path
);
117 g
->path
= strdup(path
);
123 r
= hashmap_put(a
, g
->path
, g
);
129 assert_se(hashmap_move_one(a
, b
, path
) == 0);
130 g
->cpu_valid
= g
->memory_valid
= g
->io_valid
= g
->n_tasks_valid
= false;
134 /* Regardless which controller, let's find the maximum number
135 * of processes in any of it */
137 r
= cg_enumerate_tasks(controller
, path
, &f
);
142 while (cg_read_pid(f
, &pid
) > 0)
147 if (g
->n_tasks_valid
)
148 g
->n_tasks
= MAX(g
->n_tasks
, n
);
152 g
->n_tasks_valid
= true;
155 if (streq(controller
, "cpuacct")) {
160 r
= cg_get_path(controller
, path
, "cpuacct.usage", &p
);
164 r
= read_one_line_file(p
, &v
);
169 r
= safe_atou64(v
, &new_usage
);
174 assert_se(clock_gettime(CLOCK_MONOTONIC
, &ts
) == 0);
176 if (g
->cpu_iteration
== iteration
- 1) {
179 x
= ((uint64_t) ts
.tv_sec
* 1000000000ULL + (uint64_t) ts
.tv_nsec
) -
180 ((uint64_t) g
->cpu_timestamp
.tv_sec
* 1000000000ULL + (uint64_t) g
->cpu_timestamp
.tv_nsec
);
182 y
= new_usage
- g
->cpu_usage
;
185 g
->cpu_fraction
= (double) y
/ (double) x
;
190 g
->cpu_usage
= new_usage
;
191 g
->cpu_timestamp
= ts
;
192 g
->cpu_iteration
= iteration
;
194 } else if (streq(controller
, "memory")) {
197 r
= cg_get_path(controller
, path
, "memory.usage_in_bytes", &p
);
201 r
= read_one_line_file(p
, &v
);
206 r
= safe_atou64(v
, &g
->memory
);
212 g
->memory_valid
= true;
214 } else if (streq(controller
, "blkio")) {
216 uint64_t wr
= 0, rd
= 0;
219 r
= cg_get_path(controller
, path
, "blkio.io_service_bytes", &p
);
230 char line
[LINE_MAX
], *l
;
233 if (!fgets(line
, sizeof(line
), f
))
237 l
+= strcspn(l
, WHITESPACE
);
238 l
+= strspn(l
, WHITESPACE
);
240 if (first_word(l
, "Read")) {
243 } else if (first_word(l
, "Write")) {
249 l
+= strspn(l
, WHITESPACE
);
250 r
= safe_atou64(l
, &k
);
259 assert_se(clock_gettime(CLOCK_MONOTONIC
, &ts
) == 0);
261 if (g
->io_iteration
== iteration
- 1) {
264 x
= ((uint64_t) ts
.tv_sec
* 1000000000ULL + (uint64_t) ts
.tv_nsec
) -
265 ((uint64_t) g
->io_timestamp
.tv_sec
* 1000000000ULL + (uint64_t) g
->io_timestamp
.tv_nsec
);
267 yr
= rd
- g
->io_input
;
268 yw
= wr
- g
->io_output
;
270 if (yr
> 0 || yw
> 0) {
271 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
272 g
->io_output_bps
= (yw
* 1000000000ULL) / x
;
280 g
->io_timestamp
= ts
;
281 g
->io_iteration
= iteration
;
287 static int refresh_one(
288 const char *controller
,
302 if (depth
> arg_depth
)
305 r
= process(controller
, path
, a
, b
, iteration
);
309 r
= cg_enumerate_subgroups(controller
, path
, &d
);
320 r
= cg_read_subgroup(d
, &fn
);
324 p
= strjoin(path
, "/", fn
, NULL
);
332 path_kill_slashes(p
);
334 r
= refresh_one(controller
, p
, a
, b
, iteration
, depth
+ 1);
348 static int refresh(Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
353 r
= refresh_one("name=systemd", "/", a
, b
, iteration
, 0);
357 r
= refresh_one("cpuacct", "/", a
, b
, iteration
, 0);
361 r
= refresh_one("memory", "/", a
, b
, iteration
, 0);
366 r
= refresh_one("blkio", "/", a
, b
, iteration
, 0);
373 static int group_compare(const void*a
, const void *b
) {
374 const Group
*x
= *(Group
**)a
, *y
= *(Group
**)b
;
376 if (path_startswith(y
->path
, x
->path
))
378 if (path_startswith(x
->path
, y
->path
))
381 if (arg_order
== ORDER_CPU
) {
382 if (arg_cpu_type
== CPU_PERCENT
) {
383 if (x
->cpu_valid
&& y
->cpu_valid
) {
384 if (x
->cpu_fraction
> y
->cpu_fraction
)
386 else if (x
->cpu_fraction
< y
->cpu_fraction
)
388 } else if (x
->cpu_valid
)
390 else if (y
->cpu_valid
)
393 if (x
->cpu_usage
> y
->cpu_usage
)
395 else if (x
->cpu_usage
< y
->cpu_usage
)
400 if (arg_order
== ORDER_TASKS
) {
402 if (x
->n_tasks_valid
&& y
->n_tasks_valid
) {
403 if (x
->n_tasks
> y
->n_tasks
)
405 else if (x
->n_tasks
< y
->n_tasks
)
407 } else if (x
->n_tasks_valid
)
409 else if (y
->n_tasks_valid
)
413 if (arg_order
== ORDER_MEMORY
) {
414 if (x
->memory_valid
&& y
->memory_valid
) {
415 if (x
->memory
> y
->memory
)
417 else if (x
->memory
< y
->memory
)
419 } else if (x
->memory_valid
)
421 else if (y
->memory_valid
)
425 if (arg_order
== ORDER_IO
) {
426 if (x
->io_valid
&& y
->io_valid
) {
427 if (x
->io_input_bps
+ x
->io_output_bps
> y
->io_input_bps
+ y
->io_output_bps
)
429 else if (x
->io_input_bps
+ x
->io_output_bps
< y
->io_input_bps
+ y
->io_output_bps
)
431 } else if (x
->io_valid
)
433 else if (y
->io_valid
)
437 return strcmp(x
->path
, y
->path
);
440 #define ON ANSI_HIGHLIGHT_ON
441 #define OFF ANSI_HIGHLIGHT_OFF
443 static int display(Hashmap
*a
) {
448 unsigned rows
, n
= 0, j
, maxtcpu
= 0, maxtpath
= 0;
453 /* Set cursor to top left corner and clear screen */
458 array
= alloca(sizeof(Group
*) * hashmap_size(a
));
460 HASHMAP_FOREACH(g
, a
, i
)
461 if (g
->n_tasks_valid
|| g
->cpu_valid
|| g
->memory_valid
|| g
->io_valid
)
464 qsort(array
, n
, sizeof(Group
*), group_compare
);
466 /* Find the longest names in one run */
467 for (j
= 0; j
< n
; j
++) {
468 unsigned cputlen
, pathtlen
;
469 snprintf(cpu_title
, sizeof(cpu_title
), "%"PRIu64
, array
[j
]->cpu_usage
);
470 cputlen
= strlen(cpu_title
);
471 maxtcpu
= MAX(maxtcpu
, cputlen
);
472 pathtlen
= strlen(array
[j
]->path
);
473 maxtpath
= MAX(maxtpath
, pathtlen
);
476 if (arg_cpu_type
== CPU_PERCENT
)
477 snprintf(cpu_title
, sizeof(cpu_title
), "%6s", "%CPU");
479 snprintf(cpu_title
, sizeof(cpu_title
), "%*s", maxtcpu
, "CPU Time");
486 path_columns
= columns() - 36 - strlen(cpu_title
);
487 if (path_columns
< 10)
490 printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
491 arg_order
== ORDER_PATH
? ON
: "", path_columns
, "Path",
492 arg_order
== ORDER_PATH
? OFF
: "",
493 arg_order
== ORDER_TASKS
? ON
: "", "Tasks",
494 arg_order
== ORDER_TASKS
? OFF
: "",
495 arg_order
== ORDER_CPU
? ON
: "", cpu_title
,
496 arg_order
== ORDER_CPU
? OFF
: "",
497 arg_order
== ORDER_MEMORY
? ON
: "", "Memory",
498 arg_order
== ORDER_MEMORY
? OFF
: "",
499 arg_order
== ORDER_IO
? ON
: "", "Input/s",
500 arg_order
== ORDER_IO
? OFF
: "",
501 arg_order
== ORDER_IO
? ON
: "", "Output/s",
502 arg_order
== ORDER_IO
? OFF
: "");
504 path_columns
= maxtpath
;
506 for (j
= 0; j
< n
; j
++) {
508 char m
[FORMAT_BYTES_MAX
];
510 if (on_tty() && j
+ 5 > rows
)
515 p
= ellipsize(g
->path
, path_columns
, 33);
516 printf("%-*s", path_columns
, p
? p
: g
->path
);
519 if (g
->n_tasks_valid
)
520 printf(" %7u", g
->n_tasks
);
524 if (arg_cpu_type
== CPU_PERCENT
)
526 printf(" %6.1f", g
->cpu_fraction
*100);
530 printf(" %*"PRIu64
, maxtcpu
, g
->cpu_usage
);
533 printf(" %8s", format_bytes(m
, sizeof(m
), g
->memory
));
539 format_bytes(m
, sizeof(m
), g
->io_input_bps
));
541 format_bytes(m
, sizeof(m
), g
->io_output_bps
));
543 fputs(" - -", stdout
);
551 static void help(void) {
553 printf("%s [OPTIONS...]\n\n"
554 "Show top control groups by their resource usage.\n\n"
555 " -h --help Show this help\n"
556 " --version Print version and exit\n"
557 " -p Order by path\n"
558 " -t Order by number of tasks\n"
559 " -c Order by CPU load\n"
560 " -m Order by memory load\n"
561 " -i Order by IO load\n"
562 " --cpu[=TYPE] Show CPU usage as time or percentage (default)\n"
563 " -d --delay=DELAY Delay between updates\n"
564 " -n --iterations=N Run for N iterations before exiting\n"
565 " -b --batch Run in batch mode, accepting no input\n"
566 " --depth=DEPTH Maximum traversal depth (default: %d)\n",
567 program_invocation_short_name
, arg_depth
);
570 static void version(void) {
571 puts(PACKAGE_STRING
" cgtop");
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 { "depth", required_argument
, NULL
, ARG_DEPTH
},
589 { "cpu", optional_argument
, NULL
, ARG_CPU_TYPE
},
599 while ((c
= getopt_long(argc
, argv
, "hptcmin:bd:", options
, NULL
)) >= 0) {
613 if (strcmp(optarg
, "time") == 0)
614 arg_cpu_type
= CPU_TIME
;
615 else if (strcmp(optarg
, "percentage") == 0)
616 arg_cpu_type
= CPU_PERCENT
;
623 r
= safe_atou(optarg
, &arg_depth
);
625 log_error("Failed to parse depth parameter.");
632 r
= parse_sec(optarg
, &arg_delay
);
633 if (r
< 0 || arg_delay
<= 0) {
634 log_error("Failed to parse delay parameter.");
641 r
= safe_atou(optarg
, &arg_iterations
);
643 log_error("Failed to parse iterations parameter.");
654 arg_order
= ORDER_PATH
;
658 arg_order
= ORDER_TASKS
;
662 arg_order
= ORDER_CPU
;
666 arg_order
= ORDER_MEMORY
;
670 arg_order
= ORDER_IO
;
677 log_error("Unknown option code %c", c
);
683 log_error("Too many arguments.");
690 int main(int argc
, char *argv
[]) {
692 Hashmap
*a
= NULL
, *b
= NULL
;
693 unsigned iteration
= 0;
694 usec_t last_refresh
= 0;
695 bool quit
= false, immediate_refresh
= false;
697 log_parse_environment();
700 r
= parse_argv(argc
, argv
);
704 a
= hashmap_new(string_hash_func
, string_compare_func
);
705 b
= hashmap_new(string_hash_func
, string_compare_func
);
711 signal(SIGWINCH
, columns_lines_cache_reset
);
720 char h
[FORMAT_TIMESPAN_MAX
];
722 t
= now(CLOCK_MONOTONIC
);
724 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
726 r
= refresh(a
, b
, iteration
++);
730 group_hashmap_clear(b
);
737 immediate_refresh
= false;
744 if (arg_iterations
&& iteration
>= arg_iterations
)
748 usleep(last_refresh
+ arg_delay
- t
);
750 r
= read_one_char(stdin
, &key
,
751 last_refresh
+ arg_delay
- t
, NULL
);
755 log_error("Couldn't read key: %s", strerror(-r
));
760 fputs("\r \r", stdout
);
769 immediate_refresh
= true;
777 arg_order
= ORDER_PATH
;
781 arg_order
= ORDER_TASKS
;
785 arg_order
= ORDER_CPU
;
789 arg_order
= ORDER_MEMORY
;
793 arg_order
= ORDER_IO
;
797 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
801 if (arg_delay
< USEC_PER_SEC
)
802 arg_delay
+= USEC_PER_MSEC
*250;
804 arg_delay
+= USEC_PER_SEC
;
806 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
));
812 if (arg_delay
<= USEC_PER_MSEC
*500)
813 arg_delay
= USEC_PER_MSEC
*250;
814 else if (arg_delay
< USEC_PER_MSEC
*1250)
815 arg_delay
-= USEC_PER_MSEC
*250;
817 arg_delay
-= USEC_PER_SEC
;
819 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
));
827 "\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"
828 "\t<" ON
"+" OFF
"> Increase delay; <" ON
"-" OFF
"> Decrease delay; <" ON
"%%" OFF
"> Toggle time\n"
829 "\t<" ON
"Q" OFF
"> Quit; <" ON
"SPACE" OFF
"> Refresh");
835 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
845 group_hashmap_free(a
);
846 group_hashmap_free(b
);
849 log_error("Exiting with failure: %s", strerror(-r
));