| #include <ctype.h> | |
| #include <dirent.h> | |
| #include <fcntl.h> | |
| #include <pwd.h> | |
| #include <signal.h> | |
| #include <stdio.h> | |
| #include <stdint.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| struct thread_info { | |
| int pid; | |
| int tid; | |
| char name[64]; | |
| unsigned long long exec_time; | |
| unsigned long long delay_time; | |
| unsigned long long run_count; | |
| }; | |
| struct thread_table { | |
| size_t allocated; | |
| size_t active; | |
| struct thread_info *data; | |
| }; | |
| enum { | |
| FLAG_BATCH = 1U << 0, | |
| FLAG_HIDE_IDLE = 1U << 1, | |
| FLAG_SHOW_THREADS = 1U << 2, | |
| FLAG_USE_ALTERNATE_SCREEN = 1U << 3, | |
| }; | |
| static int time_dp = 9; | |
| static int time_div = 1; | |
| #define NS_TO_S_D(ns) \ | |
| (uint32_t)((ns) / 1000000000), time_dp, ((uint32_t)((ns) % 1000000000) / time_div) | |
| struct thread_table processes; | |
| struct thread_table last_processes; | |
| struct thread_table threads; | |
| struct thread_table last_threads; | |
| static void grow_table(struct thread_table *table) | |
| { | |
| size_t size = table->allocated; | |
| struct thread_info *new_table; | |
| if (size < 128) | |
| size = 128; | |
| else | |
| size *= 2; | |
| new_table = realloc(table->data, size * sizeof(*table->data)); | |
| if (new_table == NULL) { | |
| fprintf(stderr, "out of memory\n"); | |
| exit(1); | |
| } | |
| table->data = new_table; | |
| table->allocated = size; | |
| } | |
| static struct thread_info *get_item(struct thread_table *table) | |
| { | |
| if (table->active >= table->allocated) | |
| grow_table(table); | |
| return table->data + table->active; | |
| } | |
| static void commit_item(struct thread_table *table) | |
| { | |
| table->active++; | |
| } | |
| static int read_line(char *line, size_t line_size) | |
| { | |
| int fd; | |
| int len; | |
| fd = open(line, O_RDONLY); | |
| if(fd == 0) | |
| return -1; | |
| len = read(fd, line, line_size - 1); | |
| close(fd); | |
| if (len <= 0) | |
| return -1; | |
| line[len] = '\0'; | |
| return 0; | |
| } | |
| static void add_thread(int pid, int tid, struct thread_info *proc_info) | |
| { | |
| char line[1024]; | |
| char *name, *name_end; | |
| size_t name_len; | |
| struct thread_info *info; | |
| if(tid == 0) | |
| info = get_item(&processes); | |
| else | |
| info = get_item(&threads); | |
| info->pid = pid; | |
| info->tid = tid; | |
| if(tid) | |
| sprintf(line, "/proc/%d/task/%d/schedstat", pid, tid); | |
| else | |
| sprintf(line, "/proc/%d/schedstat", pid); | |
| if (read_line(line, sizeof(line))) | |
| return; | |
| if(sscanf(line, "%llu %llu %llu", | |
| &info->exec_time, &info->delay_time, &info->run_count) != 3) | |
| return; | |
| if (proc_info) { | |
| proc_info->exec_time += info->exec_time; | |
| proc_info->delay_time += info->delay_time; | |
| proc_info->run_count += info->run_count; | |
| } | |
| name = NULL; | |
| if (!tid) { | |
| sprintf(line, "/proc/%d/cmdline", pid); | |
| if (read_line(line, sizeof(line)) == 0 && line[0]) { | |
| name = line; | |
| name_len = strlen(name); | |
| } | |
| } | |
| if (!name) { | |
| if (tid) | |
| sprintf(line, "/proc/%d/task/%d/stat", pid, tid); | |
| else | |
| sprintf(line, "/proc/%d/stat", pid); | |
| if (read_line(line, sizeof(line))) | |
| return; | |
| name = strchr(line, '('); | |
| if (name == NULL) | |
| return; | |
| name_end = strchr(name, ')'); | |
| if (name_end == NULL) | |
| return; | |
| name++; | |
| name_len = name_end - name; | |
| } | |
| if (name_len >= sizeof(info->name)) | |
| name_len = sizeof(info->name) - 1; | |
| memcpy(info->name, name, name_len); | |
| info->name[name_len] = '\0'; | |
| if(tid == 0) | |
| commit_item(&processes); | |
| else | |
| commit_item(&threads); | |
| } | |
| static void add_threads(int pid, struct thread_info *proc_info) | |
| { | |
| char path[1024]; | |
| DIR *d; | |
| struct dirent *de; | |
| sprintf(path, "/proc/%d/task", pid); | |
| d = opendir(path); | |
| if(d == 0) return; | |
| while((de = readdir(d)) != 0){ | |
| if(isdigit(de->d_name[0])){ | |
| int tid = atoi(de->d_name); | |
| add_thread(pid, tid, proc_info); | |
| } | |
| } | |
| closedir(d); | |
| } | |
| static void print_threads(int pid, uint32_t flags) | |
| { | |
| size_t i, j; | |
| for (i = 0; i < last_threads.active; i++) { | |
| int epid = last_threads.data[i].pid; | |
| int tid = last_threads.data[i].tid; | |
| if (epid != pid) | |
| continue; | |
| for (j = 0; j < threads.active; j++) | |
| if (tid == threads.data[j].tid) | |
| break; | |
| if (j == threads.active) | |
| printf(" %5u died\n", tid); | |
| else if (!(flags & FLAG_HIDE_IDLE) || threads.data[j].run_count - last_threads.data[i].run_count) | |
| printf(" %5u %2u.%0*u %2u.%0*u %5llu %5u.%0*u %5u.%0*u %7llu %s\n", tid, | |
| NS_TO_S_D(threads.data[j].exec_time - last_threads.data[i].exec_time), | |
| NS_TO_S_D(threads.data[j].delay_time - last_threads.data[i].delay_time), | |
| threads.data[j].run_count - last_threads.data[i].run_count, | |
| NS_TO_S_D(threads.data[j].exec_time), NS_TO_S_D(threads.data[j].delay_time), | |
| threads.data[j].run_count, threads.data[j].name); | |
| } | |
| } | |
| static void update_table(DIR *d, uint32_t flags) | |
| { | |
| size_t i, j; | |
| struct dirent *de; | |
| rewinddir(d); | |
| while((de = readdir(d)) != 0){ | |
| if(isdigit(de->d_name[0])){ | |
| int pid = atoi(de->d_name); | |
| struct thread_info *proc_info; | |
| add_thread(pid, 0, NULL); | |
| proc_info = &processes.data[processes.active - 1]; | |
| proc_info->exec_time = 0; | |
| proc_info->delay_time = 0; | |
| proc_info->run_count = 0; | |
| add_threads(pid, proc_info); | |
| } | |
| } | |
| if (!(flags & FLAG_BATCH)) | |
| printf("\e[H\e[0J"); | |
| printf("Processes: %zu, Threads %zu\n", processes.active, threads.active); | |
| switch (time_dp) { | |
| case 3: | |
| printf(" TID --- SINCE LAST ---- ---------- TOTAL ----------\n"); | |
| printf(" PID EXEC_T DELAY SCHED EXEC_TIME DELAY_TIM SCHED NAME\n"); | |
| break; | |
| case 6: | |
| printf(" TID ------ SINCE LAST ------- ------------ TOTAL -----------\n"); | |
| printf(" PID EXEC_TIME DELAY_TIM SCHED EXEC_TIME DELAY_TIME SCHED NAME\n"); | |
| break; | |
| default: | |
| printf(" TID -------- SINCE LAST -------- ------------- TOTAL -------------\n"); | |
| printf(" PID EXEC_TIME DELAY_TIME SCHED EXEC_TIME DELAY_TIME SCHED NAME\n"); | |
| break; | |
| } | |
| for (i = 0; i < last_processes.active; i++) { | |
| int pid = last_processes.data[i].pid; | |
| for (j = 0; j < processes.active; j++) | |
| if (pid == processes.data[j].pid) | |
| break; | |
| if (j == processes.active) | |
| printf("%5u died\n", pid); | |
| else if (!(flags & FLAG_HIDE_IDLE) || processes.data[j].run_count - last_processes.data[i].run_count) { | |
| printf("%5u %2u.%0*u %2u.%0*u %5llu %5u.%0*u %5u.%0*u %7llu %s\n", pid, | |
| NS_TO_S_D(processes.data[j].exec_time - last_processes.data[i].exec_time), | |
| NS_TO_S_D(processes.data[j].delay_time - last_processes.data[i].delay_time), | |
| processes.data[j].run_count - last_processes.data[i].run_count, | |
| NS_TO_S_D(processes.data[j].exec_time), NS_TO_S_D(processes.data[j].delay_time), | |
| processes.data[j].run_count, processes.data[j].name); | |
| if (flags & FLAG_SHOW_THREADS) | |
| print_threads(pid, flags); | |
| } | |
| } | |
| { | |
| struct thread_table tmp; | |
| tmp = last_processes; | |
| last_processes = processes; | |
| processes = tmp; | |
| processes.active = 0; | |
| tmp = last_threads; | |
| last_threads = threads; | |
| threads = tmp; | |
| threads.active = 0; | |
| } | |
| } | |
| void | |
| sig_abort(int signum) | |
| { | |
| printf("\e[?47l"); | |
| exit(0); | |
| } | |
| int schedtop_main(int argc, char **argv) | |
| { | |
| int c; | |
| DIR *d; | |
| uint32_t flags = 0; | |
| int delay = 3000000; | |
| float delay_f; | |
| while(1) { | |
| c = getopt(argc, argv, "d:ibtamun"); | |
| if (c == EOF) | |
| break; | |
| switch (c) { | |
| case 'd': | |
| delay_f = atof(optarg); | |
| delay = delay_f * 1000000; | |
| break; | |
| case 'b': | |
| flags |= FLAG_BATCH; | |
| break; | |
| case 'i': | |
| flags |= FLAG_HIDE_IDLE; | |
| break; | |
| case 't': | |
| flags |= FLAG_SHOW_THREADS; | |
| break; | |
| case 'a': | |
| flags |= FLAG_USE_ALTERNATE_SCREEN; | |
| break; | |
| case 'm': | |
| time_dp = 3; | |
| time_div = 1000000; | |
| break; | |
| case 'u': | |
| time_dp = 6; | |
| time_div = 1000; | |
| break; | |
| case 'n': | |
| time_dp = 9; | |
| time_div = 1; | |
| break; | |
| } | |
| } | |
| d = opendir("/proc"); | |
| if(d == 0) return -1; | |
| if (!(flags & FLAG_BATCH)) { | |
| if(flags & FLAG_USE_ALTERNATE_SCREEN) { | |
| signal(SIGINT, sig_abort); | |
| signal(SIGPIPE, sig_abort); | |
| signal(SIGTERM, sig_abort); | |
| printf("\e7\e[?47h"); | |
| } | |
| printf("\e[2J"); | |
| } | |
| while (1) { | |
| update_table(d, flags); | |
| usleep(delay); | |
| } | |
| closedir(d); | |
| return 0; | |
| } |