| #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; |
| } |