Lines Matching +full:cpu +full:- +full:cfg

1 // SPDX-License-Identifier: GPL-2.0
3 * delaytop.c - system-wide delay monitoring tool.
5 * This tool provides real-time monitoring and statistics of
6 * system, container, and task-level delays, including CPU,
7 * memory, IO, and IRQ. It supports both interactive (top-like),
12 * - Collects per-task delay accounting statistics via taskstats.
13 * - Collects system-wide PSI information.
14 * - Supports sorting, filtering.
15 * - Supports both interactive (screen refresh).
21 * gcc -I/usr/src/linux/include delaytop.c -o delaytop
48 #define PSI_CPU_PATH "/proc/pressure/cpu"
53 #define NLA_NEXT(na) ((struct nlattr *)((char *)(na) + NLA_ALIGN((na)->nla_len)))
55 #define NLA_PAYLOAD(len) (len - NLA_HDRLEN)
58 #define GENLMSG_PAYLOAD(glh) (NLMSG_PAYLOAD(glh, 0) - GENL_HDRLEN)
71 #define PSI_LINE_FORMAT "%-12s %6.1f%%/%6.1f%%/%6.1f%%/%8llu(ms)\n"
160 static struct config cfg; variable
167 SORT_FIELD(cpu, c, MODE_DEFAULT),
181 static int nl_sd = -1;
184 /* Set terminal to non-canonical mode for q-to-quit */
205 for (field = sort_fields; field->name != NULL; field++) { in get_field_by_cmd_char()
206 if (field->cmd_char[0] == ch) in get_field_by_cmd_char()
219 for (field = sort_fields; field->name != NULL; field++) { in get_field_by_name()
220 field_len = strlen(field->name); in get_field_by_name()
223 if (strncmp(field->name, name, field_len) == 0) in get_field_by_name()
233 return field ? field->name : "UNKNOWN"; in get_name_by_field()
244 for (field = sort_fields; field->name != NULL; field++) { in display_available_fields()
245 if (!(field->supported_modes & mode)) in display_available_fields()
247 strncat(buf, "|", MAX_BUF_LEN - strlen(buf) - 1); in display_available_fields()
248 strncat(buf, field->name, MAX_BUF_LEN - strlen(buf) - 1); in display_available_fields()
249 buf[MAX_BUF_LEN - 1] = '\0'; in display_available_fields()
260 " -h, --help Show this help message and exit\n" in usage()
261 " -d, --delay=SECONDS Set refresh interval (default: 2 seconds, min: 1)\n" in usage()
262 " -n, --iterations=COUNT Set number of updates (default: 0 = infinite)\n" in usage()
263 " -P, --processes=NUMBER Set maximum number of processes to show (default: 20, max: 1000)\n" in usage()
264 " -o, --once Display once and exit\n" in usage()
265 " -p, --pid=PID Monitor only the specified PID\n" in usage()
266 " -C, --container=PATH Monitor the container at specified cgroup path\n" in usage()
267 " -s, --sort=FIELD Sort by delay field (default: cpu)\n" in usage()
268 " -M, --memverbose Display memory detailed information\n"); in usage()
291 cfg.delay = 2; in parse_args()
292 cfg.iterations = 0; in parse_args()
293 cfg.max_processes = 20; in parse_args()
294 cfg.sort_field = &sort_fields[0]; /* Default sorted by CPU delay */ in parse_args()
295 cfg.output_one_time = 0; in parse_args()
296 cfg.monitor_pid = 0; /* 0 means monitor all PIDs */ in parse_args()
297 cfg.container_path = NULL; in parse_args()
298 cfg.display_mode = MODE_DEFAULT; in parse_args()
304 if (c == -1) in parse_args()
312 cfg.delay = atoi(optarg); in parse_args()
313 if (cfg.delay < 1) { in parse_args()
319 cfg.iterations = atoi(optarg); in parse_args()
320 if (cfg.iterations < 0) { in parse_args()
326 cfg.monitor_pid = atoi(optarg); in parse_args()
327 if (cfg.monitor_pid < 1) { in parse_args()
333 cfg.output_one_time = 1; in parse_args()
336 cfg.max_processes = atoi(optarg); in parse_args()
337 if (cfg.max_processes < 1) { in parse_args()
341 if (cfg.max_processes > MAX_TASKS) { in parse_args()
344 cfg.max_processes = MAX_TASKS; in parse_args()
348 cfg.container_path = strdup(optarg); in parse_args()
364 cfg.sort_field = field; in parse_args()
367 cfg.display_mode = MODE_MEMVERBOSE; in parse_args()
368 cfg.sort_field = get_field_by_name("mem"); in parse_args()
371 fprintf(stderr, "Try 'delaytop --help' for more information.\n"); in parse_args()
380 t->mem_delay_total = t->swapin_delay_total + in set_mem_delay_total()
381 t->freepages_delay_total + in set_mem_delay_total()
382 t->thrashing_delay_total + in set_mem_delay_total()
383 t->compact_delay_total + in set_mem_delay_total()
384 t->wpcopy_delay_total; in set_mem_delay_total()
389 t->mem_count = t->swapin_count + in set_mem_count()
390 t->freepages_count + in set_mem_count()
391 t->thrashing_count + in set_mem_count()
392 t->compact_count + in set_mem_count()
393 t->wpcopy_count; in set_mem_count()
404 return -1; in create_nl_socket()
412 return -1; in create_nl_socket()
442 na->nla_type = nla_type; in send_cmd()
443 na->nla_len = nla_len + NLA_HDRLEN; in send_cmd()
445 msg.n.nlmsg_len += NLMSG_ALIGN(na->nla_len); in send_cmd()
455 buflen -= r; in send_cmd()
457 return -1; in send_cmd()
476 strncpy(name, TASKSTATS_GENL_NAME, sizeof(name) - 1); in get_family_id()
477 name[sizeof(name) - 1] = '\0'; in get_family_id()
494 na = (struct nlattr *) ((char *) na + NLA_ALIGN(na->nla_len)); in get_family_id()
495 if (na->nla_type == CTRL_ATTR_FAMILY_ID) in get_family_id()
511 return -1; in read_psi_stats()
517 /* CPU pressure */ in read_psi_stats()
526 fprintf(stderr, "Failed to parse CPU some PSI data\n"); in read_psi_stats()
534 fprintf(stderr, "Failed to parse CPU full PSI data\n"); in read_psi_stats()
621 /* Return error count: 0 means success, >0 means warnings, -1 means fatal error */ in read_psi_stats()
633 int ret = -1; in read_comm()
646 if (len > 0 && comm_buf[len - 1] == '\n') in read_comm()
647 comm_buf[len - 1] = '\0'; in read_comm()
688 if (na->nla_type == TASKSTATS_TYPE_AGGR_PID) { in fetch_and_fill_task_info()
690 nested_len = NLA_PAYLOAD(na->nla_len); in fetch_and_fill_task_info()
692 if (nested->nla_type == TASKSTATS_TYPE_STATS) { in fetch_and_fill_task_info()
698 TASK_COMM_LEN - 1); in fetch_and_fill_task_info()
699 tasks[task_count].command[TASK_COMM_LEN - 1] = '\0'; in fetch_and_fill_task_info()
722 nested_len -= NLA_ALIGN(nested->nla_len); in fetch_and_fill_task_info()
726 nl_len -= NLA_ALIGN(na->nla_len); in fetch_and_fill_task_info()
740 if (cfg.monitor_pid > 0) { in get_task_delays()
741 if (read_comm(cfg.monitor_pid, comm, sizeof(comm)) == 0) in get_task_delays()
742 fetch_and_fill_task_info(cfg.monitor_pid, comm); in get_task_delays()
753 if (!isdigit(entry->d_name[0])) in get_task_delays()
755 pid = atoi(entry->d_name); in get_task_delays()
784 total1 = *(unsigned long long *)((char *)t1 + cfg.sort_field->total_offset); in compare_tasks()
785 total2 = *(unsigned long long *)((char *)t2 + cfg.sort_field->total_offset); in compare_tasks()
786 count1 = *(unsigned long *)((char *)t1 + cfg.sort_field->count_offset); in compare_tasks()
787 count2 = *(unsigned long *)((char *)t2 + cfg.sort_field->count_offset); in compare_tasks()
792 return avg2 > avg1 ? 1 : -1; in compare_tasks()
818 if (!cfg.container_path) in get_container_stats()
822 cfd = open(cfg.container_path, O_RDONLY); in get_container_stats()
824 fprintf(stderr, "Error opening container path: %s\n", cfg.container_path); in get_container_stats()
848 if (na->nla_type == CGROUPSTATS_TYPE_CGROUP_STATS) { in get_container_stats()
860 nl_len -= NLA_ALIGN(na->nla_len); in get_container_stats()
861 na = (struct nlattr *) ((char *) na + NLA_ALIGN(na->nla_len)); in get_container_stats()
880 /* PSI output (one-line, no cat style) */ in display_results()
886 "CPU some:", in display_results()
892 "CPU full:", in display_results()
929 if (cfg.container_path) { in display_results()
930 suc &= BOOL_FPRINT(out, "Container Information (%s):\n", cfg.container_path); in display_results()
941 if (cfg.display_mode == MODE_MEMVERBOSE) in display_results()
946 "sort selection: [c]CPU [i]IO [m]MEM [q]IRQ\n"); in display_results()
951 cfg.max_processes, get_name_by_field(cfg.sort_field)); in display_results()
953 suc &= BOOL_FPRINT(out, "%8s %8s %-17s", "PID", "TGID", "COMMAND"); in display_results()
954 if (cfg.display_mode == MODE_MEMVERBOSE) { in display_results()
958 suc &= BOOL_FPRINT(out, "-----------------------"); in display_results()
959 suc &= BOOL_FPRINT(out, "-----------------------"); in display_results()
960 suc &= BOOL_FPRINT(out, "-----------------------"); in display_results()
961 suc &= BOOL_FPRINT(out, "---------------------\n"); in display_results()
964 "CPU(ms)", "IO(ms)", "IRQ(ms)", "MEM(ms)"); in display_results()
965 suc &= BOOL_FPRINT(out, "-----------------------"); in display_results()
966 suc &= BOOL_FPRINT(out, "-----------------------"); in display_results()
967 suc &= BOOL_FPRINT(out, "--------------------------\n"); in display_results()
970 count = task_count < cfg.max_processes ? task_count : cfg.max_processes; in display_results()
973 suc &= BOOL_FPRINT(out, "%8d %8d %-15s", in display_results()
975 if (cfg.display_mode == MODE_MEMVERBOSE) { in display_results()
985 TASK_AVG(tasks[i], cpu), in display_results()
998 /* Check for keyboard input with timeout based on cfg.delay */
1001 struct timeval tv = {cfg.delay, 0}; in check_for_keypress()
1024 cfg.display_mode = modes[cur_index]; in toggle_display_mode()
1035 if (field && (field->supported_modes & cfg.display_mode)) in handle_keypress()
1036 cfg.sort_field = field; in handle_keypress()
1047 for (field = sort_fields; field->name != NULL; field++) { in handle_keypress()
1048 if (field->supported_modes & cfg.display_mode) { in handle_keypress()
1049 cfg.sort_field = field; in handle_keypress()
1090 /* Set terminal to non-canonical mode for interaction */ in main()
1095 /* Auto-switch sort field when not matching display mode */ in main()
1096 if (!(cfg.sort_field->supported_modes & cfg.display_mode)) { in main()
1097 for (field = sort_fields; field->name != NULL; field++) { in main()
1098 if (field->supported_modes & cfg.display_mode) { in main()
1099 cfg.sort_field = field; in main()
1100 printf("Auto-switched sort field to: %s\n", field->name); in main()
1110 if (cfg.container_path) in main()
1123 if (cfg.iterations > 0 && ++iterations >= cfg.iterations) in main()
1127 if (cfg.output_one_time) in main()
1141 if (cfg.container_path) in main()
1142 free(cfg.container_path); in main()