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
= 0;
64 static bool arg_batch
= false;
65 static usec_t arg_delay
= 1*USEC_PER_SEC
;
73 } arg_order
= ORDER_CPU
;
78 } arg_cpu_type
= CPU_PERCENT
;
80 static void group_free(Group
*g
) {
87 static void group_hashmap_clear(Hashmap
*h
) {
90 while ((g
= hashmap_steal_first(h
)))
94 static void group_hashmap_free(Hashmap
*h
) {
95 group_hashmap_clear(h
);
99 static int process(const char *controller
, const char *path
, Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
110 g
= hashmap_get(a
, path
);
112 g
= hashmap_get(b
, path
);
118 g
->path
= strdup(path
);
124 r
= hashmap_put(a
, g
->path
, g
);
130 r
= hashmap_move_one(a
, b
, path
);
133 g
->cpu_valid
= g
->memory_valid
= g
->io_valid
= g
->n_tasks_valid
= false;
137 /* Regardless which controller, let's find the maximum number
138 * of processes in any of it */
140 r
= cg_enumerate_processes(controller
, path
, &f
);
145 while (cg_read_pid(f
, &pid
) > 0)
150 if (g
->n_tasks_valid
)
151 g
->n_tasks
= MAX(g
->n_tasks
, n
);
155 g
->n_tasks_valid
= true;
158 if (streq(controller
, "cpuacct")) {
163 r
= cg_get_path(controller
, path
, "cpuacct.usage", &p
);
167 r
= read_one_line_file(p
, &v
);
172 r
= safe_atou64(v
, &new_usage
);
177 assert_se(clock_gettime(CLOCK_MONOTONIC
, &ts
) == 0);
179 if (g
->cpu_iteration
== iteration
- 1) {
182 x
= ((uint64_t) ts
.tv_sec
* 1000000000ULL + (uint64_t) ts
.tv_nsec
) -
183 ((uint64_t) g
->cpu_timestamp
.tv_sec
* 1000000000ULL + (uint64_t) g
->cpu_timestamp
.tv_nsec
);
185 y
= new_usage
- g
->cpu_usage
;
188 g
->cpu_fraction
= (double) y
/ (double) x
;
193 g
->cpu_usage
= new_usage
;
194 g
->cpu_timestamp
= ts
;
195 g
->cpu_iteration
= iteration
;
197 } else if (streq(controller
, "memory")) {
200 r
= cg_get_path(controller
, path
, "memory.usage_in_bytes", &p
);
204 r
= read_one_line_file(p
, &v
);
209 r
= safe_atou64(v
, &g
->memory
);
215 g
->memory_valid
= true;
217 } else if (streq(controller
, "blkio")) {
219 uint64_t wr
= 0, rd
= 0;
222 r
= cg_get_path(controller
, path
, "blkio.io_service_bytes", &p
);
233 char line
[LINE_MAX
], *l
;
236 if (!fgets(line
, sizeof(line
), f
))
240 l
+= strcspn(l
, WHITESPACE
);
241 l
+= strspn(l
, WHITESPACE
);
243 if (first_word(l
, "Read")) {
246 } else if (first_word(l
, "Write")) {
252 l
+= strspn(l
, WHITESPACE
);
253 r
= safe_atou64(l
, &k
);
262 assert_se(clock_gettime(CLOCK_MONOTONIC
, &ts
) == 0);
264 if (g
->io_iteration
== iteration
- 1) {
267 x
= ((uint64_t) ts
.tv_sec
* 1000000000ULL + (uint64_t) ts
.tv_nsec
) -
268 ((uint64_t) g
->io_timestamp
.tv_sec
* 1000000000ULL + (uint64_t) g
->io_timestamp
.tv_nsec
);
270 yr
= rd
- g
->io_input
;
271 yw
= wr
- g
->io_output
;
273 if (yr
> 0 || yw
> 0) {
274 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
275 g
->io_output_bps
= (yw
* 1000000000ULL) / x
;
283 g
->io_timestamp
= ts
;
284 g
->io_iteration
= iteration
;
290 static int refresh_one(
291 const char *controller
,
305 if (depth
> arg_depth
)
308 r
= process(controller
, path
, a
, b
, iteration
);
312 r
= cg_enumerate_subgroups(controller
, path
, &d
);
323 r
= cg_read_subgroup(d
, &fn
);
327 p
= strjoin(path
, "/", fn
, NULL
);
335 path_kill_slashes(p
);
337 r
= refresh_one(controller
, p
, a
, b
, iteration
, depth
+ 1);
351 static int refresh(Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
356 r
= refresh_one("name=systemd", "/", a
, b
, iteration
, 0);
360 r
= refresh_one("cpuacct", "/", a
, b
, iteration
, 0);
364 r
= refresh_one("memory", "/", a
, b
, iteration
, 0);
369 r
= refresh_one("blkio", "/", a
, b
, iteration
, 0);
376 static int group_compare(const void*a
, const void *b
) {
377 const Group
*x
= *(Group
**)a
, *y
= *(Group
**)b
;
379 if (path_startswith(y
->path
, x
->path
))
381 if (path_startswith(x
->path
, y
->path
))
384 if (arg_order
== ORDER_CPU
) {
385 if (arg_cpu_type
== CPU_PERCENT
) {
386 if (x
->cpu_valid
&& y
->cpu_valid
) {
387 if (x
->cpu_fraction
> y
->cpu_fraction
)
389 else if (x
->cpu_fraction
< y
->cpu_fraction
)
391 } else if (x
->cpu_valid
)
393 else if (y
->cpu_valid
)
396 if (x
->cpu_usage
> y
->cpu_usage
)
398 else if (x
->cpu_usage
< y
->cpu_usage
)
403 if (arg_order
== ORDER_TASKS
) {
405 if (x
->n_tasks_valid
&& y
->n_tasks_valid
) {
406 if (x
->n_tasks
> y
->n_tasks
)
408 else if (x
->n_tasks
< y
->n_tasks
)
410 } else if (x
->n_tasks_valid
)
412 else if (y
->n_tasks_valid
)
416 if (arg_order
== ORDER_MEMORY
) {
417 if (x
->memory_valid
&& y
->memory_valid
) {
418 if (x
->memory
> y
->memory
)
420 else if (x
->memory
< y
->memory
)
422 } else if (x
->memory_valid
)
424 else if (y
->memory_valid
)
428 if (arg_order
== ORDER_IO
) {
429 if (x
->io_valid
&& y
->io_valid
) {
430 if (x
->io_input_bps
+ x
->io_output_bps
> y
->io_input_bps
+ y
->io_output_bps
)
432 else if (x
->io_input_bps
+ x
->io_output_bps
< y
->io_input_bps
+ y
->io_output_bps
)
434 } else if (x
->io_valid
)
436 else if (y
->io_valid
)
440 return strcmp(x
->path
, y
->path
);
443 #define ON ANSI_HIGHLIGHT_ON
444 #define OFF ANSI_HIGHLIGHT_OFF
446 static int display(Hashmap
*a
) {
451 unsigned rows
, n
= 0, j
, maxtcpu
= 0, maxtpath
= 3; /* 3 for ellipsize() to work properly */
452 char buffer
[MAX3(21, FORMAT_BYTES_MAX
, FORMAT_TIMESPAN_MAX
)];
456 /* Set cursor to top left corner and clear screen */
461 array
= alloca(sizeof(Group
*) * hashmap_size(a
));
463 HASHMAP_FOREACH(g
, a
, i
)
464 if (g
->n_tasks_valid
|| g
->cpu_valid
|| g
->memory_valid
|| g
->io_valid
)
467 qsort_safe(array
, n
, sizeof(Group
*), group_compare
);
469 /* Find the longest names in one run */
470 for (j
= 0; j
< n
; j
++) {
471 unsigned cputlen
, pathtlen
;
473 format_timespan(buffer
, sizeof(buffer
), (nsec_t
) (array
[j
]->cpu_usage
/ NSEC_PER_USEC
), 0);
474 cputlen
= strlen(buffer
);
475 maxtcpu
= MAX(maxtcpu
, cputlen
);
476 pathtlen
= strlen(array
[j
]->path
);
477 maxtpath
= MAX(maxtpath
, pathtlen
);
480 if (arg_cpu_type
== CPU_PERCENT
)
481 snprintf(buffer
, sizeof(buffer
), "%6s", "%CPU");
483 snprintf(buffer
, sizeof(buffer
), "%*s", maxtcpu
, "CPU Time");
490 path_columns
= columns() - 36 - strlen(buffer
);
491 if (path_columns
< 10)
494 printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
495 arg_order
== ORDER_PATH
? ON
: "", path_columns
, "Path",
496 arg_order
== ORDER_PATH
? OFF
: "",
497 arg_order
== ORDER_TASKS
? ON
: "", "Tasks",
498 arg_order
== ORDER_TASKS
? OFF
: "",
499 arg_order
== ORDER_CPU
? ON
: "", buffer
,
500 arg_order
== ORDER_CPU
? OFF
: "",
501 arg_order
== ORDER_MEMORY
? ON
: "", "Memory",
502 arg_order
== ORDER_MEMORY
? OFF
: "",
503 arg_order
== ORDER_IO
? ON
: "", "Input/s",
504 arg_order
== ORDER_IO
? OFF
: "",
505 arg_order
== ORDER_IO
? ON
: "", "Output/s",
506 arg_order
== ORDER_IO
? OFF
: "");
508 path_columns
= maxtpath
;
510 for (j
= 0; j
< n
; j
++) {
513 if (on_tty() && j
+ 5 > rows
)
518 p
= ellipsize(g
->path
, path_columns
, 33);
519 printf("%-*s", path_columns
, p
? p
: g
->path
);
522 if (g
->n_tasks_valid
)
523 printf(" %7u", g
->n_tasks
);
527 if (arg_cpu_type
== CPU_PERCENT
) {
529 printf(" %6.1f", g
->cpu_fraction
*100);
533 printf(" %*s", maxtcpu
, format_timespan(buffer
, sizeof(buffer
), (nsec_t
) (g
->cpu_usage
/ NSEC_PER_USEC
), 0));
536 printf(" %8s", format_bytes(buffer
, sizeof(buffer
), g
->memory
));
542 format_bytes(buffer
, sizeof(buffer
), g
->io_input_bps
));
544 format_bytes(buffer
, sizeof(buffer
), g
->io_output_bps
));
546 fputs(" - -", stdout
);
554 static void help(void) {
555 printf("%s [OPTIONS...]\n\n"
556 "Show top control groups by their resource usage.\n\n"
557 " -h --help Show this help\n"
558 " --version Print version and exit\n"
559 " -p Order by path\n"
560 " -t Order by number of tasks\n"
561 " -c Order by CPU load\n"
562 " -m Order by memory load\n"
563 " -i Order by IO load\n"
564 " --cpu[=TYPE] Show CPU usage as time or percentage (default)\n"
565 " -d --delay=DELAY Delay between updates\n"
566 " -n --iterations=N Run for N iterations before exiting\n"
567 " -b --batch Run in batch mode, accepting no input\n"
568 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
569 , program_invocation_short_name
, arg_depth
);
572 static int parse_argv(int argc
, char *argv
[]) {
580 static const struct option options
[] = {
581 { "help", no_argument
, NULL
, 'h' },
582 { "version", no_argument
, NULL
, ARG_VERSION
},
583 { "delay", required_argument
, NULL
, 'd' },
584 { "iterations", required_argument
, NULL
, 'n' },
585 { "batch", no_argument
, NULL
, 'b' },
586 { "depth", required_argument
, NULL
, ARG_DEPTH
},
587 { "cpu", optional_argument
, NULL
, ARG_CPU_TYPE
},
597 while ((c
= getopt_long(argc
, argv
, "hptcmin:bd:", options
, NULL
)) >= 0)
606 puts(PACKAGE_STRING
);
607 puts(SYSTEMD_FEATURES
);
612 if (strcmp(optarg
, "time") == 0)
613 arg_cpu_type
= CPU_TIME
;
614 else if (strcmp(optarg
, "percentage") == 0)
615 arg_cpu_type
= CPU_PERCENT
;
622 r
= safe_atou(optarg
, &arg_depth
);
624 log_error("Failed to parse depth parameter.");
631 r
= parse_sec(optarg
, &arg_delay
);
632 if (r
< 0 || arg_delay
<= 0) {
633 log_error("Failed to parse delay parameter.");
640 r
= safe_atou(optarg
, &arg_iterations
);
642 log_error("Failed to parse iterations parameter.");
653 arg_order
= ORDER_PATH
;
657 arg_order
= ORDER_TASKS
;
661 arg_order
= ORDER_CPU
;
665 arg_order
= ORDER_MEMORY
;
669 arg_order
= ORDER_IO
;
676 assert_not_reached("Unhandled option");
680 log_error("Too many arguments.");
687 int main(int argc
, char *argv
[]) {
689 Hashmap
*a
= NULL
, *b
= NULL
;
690 unsigned iteration
= 0;
691 usec_t last_refresh
= 0;
692 bool quit
= false, immediate_refresh
= false;
694 log_parse_environment();
697 r
= parse_argv(argc
, argv
);
701 a
= hashmap_new(&string_hash_ops
);
702 b
= hashmap_new(&string_hash_ops
);
708 signal(SIGWINCH
, columns_lines_cache_reset
);
717 char h
[FORMAT_TIMESPAN_MAX
];
719 t
= now(CLOCK_MONOTONIC
);
721 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
723 r
= refresh(a
, b
, iteration
++);
727 group_hashmap_clear(b
);
734 immediate_refresh
= false;
741 if (arg_iterations
&& iteration
>= arg_iterations
)
745 usleep(last_refresh
+ arg_delay
- t
);
747 r
= read_one_char(stdin
, &key
,
748 last_refresh
+ arg_delay
- t
, NULL
);
752 log_error_errno(r
, "Couldn't read key: %m");
757 fputs("\r \r", stdout
);
766 immediate_refresh
= true;
774 arg_order
= ORDER_PATH
;
778 arg_order
= ORDER_TASKS
;
782 arg_order
= ORDER_CPU
;
786 arg_order
= ORDER_MEMORY
;
790 arg_order
= ORDER_IO
;
794 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
798 if (arg_delay
< USEC_PER_SEC
)
799 arg_delay
+= USEC_PER_MSEC
*250;
801 arg_delay
+= USEC_PER_SEC
;
803 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
809 if (arg_delay
<= USEC_PER_MSEC
*500)
810 arg_delay
= USEC_PER_MSEC
*250;
811 else if (arg_delay
< USEC_PER_MSEC
*1250)
812 arg_delay
-= USEC_PER_MSEC
*250;
814 arg_delay
-= USEC_PER_SEC
;
816 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
824 "\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"
825 "\t<" ON
"+" OFF
"> Increase delay; <" ON
"-" OFF
"> Decrease delay; <" ON
"%%" OFF
"> Toggle time\n"
826 "\t<" ON
"q" OFF
"> Quit; <" ON
"SPACE" OFF
"> Refresh");
832 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
842 group_hashmap_free(a
);
843 group_hashmap_free(b
);
846 log_error_errno(r
, "Exiting with failure: %m");