1#include <stdio.h>
2#include <stdlib.h>
3#include <ctype.h>
4#include <fcntl.h>
5#include <unistd.h>
6
7#include <stdint.h>
8#include <string.h>
9
10#include <sys/stat.h>
11#include <sys/types.h>
12#include <dirent.h>
13#include <signal.h>
14
15#include <pwd.h>
16
17struct thread_info {
18    int pid;
19    int tid;
20    char name[64];
21    uint64_t exec_time;
22    uint64_t delay_time;
23    uint32_t run_count;
24};
25
26struct thread_table {
27    size_t allocated;
28    size_t active;
29    struct thread_info *data;
30};
31
32enum {
33    FLAG_BATCH = 1U << 0,
34    FLAG_HIDE_IDLE = 1U << 1,
35    FLAG_SHOW_THREADS = 1U << 2,
36    FLAG_USE_ALTERNATE_SCREEN = 1U << 3,
37};
38
39static int time_dp = 9;
40static int time_div = 1;
41#define NS_TO_S_D(ns) \
42    (uint32_t)((ns) / 1000000000), time_dp, ((uint32_t)((ns) % 1000000000) / time_div)
43
44struct thread_table processes;
45struct thread_table last_processes;
46struct thread_table threads;
47struct thread_table last_threads;
48
49static void grow_table(struct thread_table *table)
50{
51    size_t size = table->allocated;
52    struct thread_info *new_table;
53    if (size < 128)
54        size = 128;
55    else
56        size *= 2;
57
58    new_table = realloc(table->data, size * sizeof(*table->data));
59    if (new_table == NULL) {
60        fprintf(stderr, "out of memory\n");
61        exit(1);
62    }
63    table->data = new_table;
64    table->allocated = size;
65}
66
67static struct thread_info *get_item(struct thread_table *table)
68{
69    if (table->active >= table->allocated)
70        grow_table(table);
71    return table->data + table->active;
72}
73
74static void commit_item(struct thread_table *table)
75{
76    table->active++;
77}
78
79static int read_line(char *line, size_t line_size)
80{
81    int fd;
82    int len;
83    fd = open(line, O_RDONLY);
84    if(fd == 0)
85        return -1;
86    len = read(fd, line, line_size - 1);
87    close(fd);
88    if (len <= 0)
89        return -1;
90    line[len] = '\0';
91    return 0;
92}
93
94static void add_thread(int pid, int tid, struct thread_info *proc_info)
95{
96    char line[1024];
97    char *name, *name_end;
98    size_t name_len;
99    struct thread_info *info;
100    if(tid == 0)
101        info = get_item(&processes);
102    else
103        info = get_item(&threads);
104    info->pid = pid;
105    info->tid = tid;
106
107    if(tid)
108        sprintf(line, "/proc/%d/task/%d/schedstat", pid, tid);
109    else
110        sprintf(line, "/proc/%d/schedstat", pid);
111    if (read_line(line, sizeof(line)))
112        return;
113    if(sscanf(line, "%llu %llu %u", &info->exec_time, &info->delay_time, &info->run_count) != 3)
114        return;
115    if (proc_info) {
116        proc_info->exec_time += info->exec_time;
117        proc_info->delay_time += info->delay_time;
118        proc_info->run_count += info->run_count;
119    }
120
121    name = NULL;
122    if (!tid) {
123        sprintf(line, "/proc/%d/cmdline", pid);
124        if (read_line(line, sizeof(line)) == 0 && line[0]) {
125            name = line;
126            name_len = strlen(name);
127        }
128    }
129    if (!name) {
130        if (tid)
131            sprintf(line, "/proc/%d/task/%d/stat", pid, tid);
132        else
133            sprintf(line, "/proc/%d/stat", pid);
134        if (read_line(line, sizeof(line)))
135            return;
136        name = strchr(line, '(');
137        if (name == NULL)
138            return;
139        name_end = strchr(name, ')');
140        if (name_end == NULL)
141            return;
142        name++;
143        name_len = name_end - name;
144    }
145    if (name_len >= sizeof(info->name))
146        name_len = sizeof(info->name) - 1;
147    memcpy(info->name, name, name_len);
148    info->name[name_len] = '\0';
149    if(tid == 0)
150        commit_item(&processes);
151    else
152        commit_item(&threads);
153}
154
155static void add_threads(int pid, struct thread_info *proc_info)
156{
157    char path[1024];
158    DIR *d;
159    struct dirent *de;
160    sprintf(path, "/proc/%d/task", pid);
161    d = opendir(path);
162    if(d == 0) return;
163    while((de = readdir(d)) != 0){
164        if(isdigit(de->d_name[0])){
165            int tid = atoi(de->d_name);
166            add_thread(pid, tid, proc_info);
167        }
168    }
169    closedir(d);
170}
171
172static void print_threads(int pid, uint32_t flags)
173{
174    size_t i, j;
175    for (i = 0; i < last_threads.active; i++) {
176        int epid = last_threads.data[i].pid;
177        int tid = last_threads.data[i].tid;
178        if (epid != pid)
179            continue;
180        for (j = 0; j < threads.active; j++)
181            if (tid == threads.data[j].tid)
182                break;
183        if (j == threads.active)
184            printf(" %5u died\n", tid);
185        else if (!(flags & FLAG_HIDE_IDLE) || threads.data[j].run_count - last_threads.data[i].run_count)
186            printf(" %5u %2u.%0*u %2u.%0*u %5u %5u.%0*u %5u.%0*u %7u  %s\n", tid,
187                NS_TO_S_D(threads.data[j].exec_time - last_threads.data[i].exec_time),
188                NS_TO_S_D(threads.data[j].delay_time - last_threads.data[i].delay_time),
189                threads.data[j].run_count - last_threads.data[i].run_count,
190                NS_TO_S_D(threads.data[j].exec_time), NS_TO_S_D(threads.data[j].delay_time),
191                threads.data[j].run_count, threads.data[j].name);
192    }
193}
194
195static void update_table(DIR *d, uint32_t flags)
196{
197    size_t i, j;
198    struct dirent *de;
199
200    rewinddir(d);
201    while((de = readdir(d)) != 0){
202        if(isdigit(de->d_name[0])){
203            int pid = atoi(de->d_name);
204            struct thread_info *proc_info;
205            add_thread(pid, 0, NULL);
206            proc_info = &processes.data[processes.active - 1];
207            proc_info->exec_time = 0;
208            proc_info->delay_time = 0;
209            proc_info->run_count = 0;
210            add_threads(pid, proc_info);
211        }
212    }
213    if (!(flags & FLAG_BATCH))
214        printf("\e[H\e[0J");
215    printf("Processes: %d, Threads %d\n", processes.active, threads.active);
216    switch (time_dp) {
217    case 3:
218        printf("   TID --- SINCE LAST ---- ---------- TOTAL ----------\n");
219        printf("  PID  EXEC_T  DELAY SCHED EXEC_TIME DELAY_TIM   SCHED NAME\n");
220        break;
221    case 6:
222        printf("   TID ------ SINCE LAST -------    ------------ TOTAL -----------\n");
223        printf("  PID  EXEC_TIME DELAY_TIM SCHED    EXEC_TIME   DELAY_TIME   SCHED NAME\n");
224        break;
225    default:
226        printf("   TID    -------- SINCE LAST --------       ------------- TOTAL -------------\n");
227        printf("  PID     EXEC_TIME   DELAY_TIME SCHED       EXEC_TIME      DELAY_TIME   SCHED NAME\n");
228        break;
229    }
230    for (i = 0; i < last_processes.active; i++) {
231        int pid = last_processes.data[i].pid;
232        int tid = last_processes.data[i].tid;
233        for (j = 0; j < processes.active; j++)
234            if (pid == processes.data[j].pid)
235                break;
236        if (j == processes.active)
237            printf("%5u died\n", pid);
238        else if (!(flags & FLAG_HIDE_IDLE) || processes.data[j].run_count - last_processes.data[i].run_count) {
239            printf("%5u  %2u.%0*u %2u.%0*u %5u %5u.%0*u %5u.%0*u %7u %s\n", pid,
240                NS_TO_S_D(processes.data[j].exec_time - last_processes.data[i].exec_time),
241                NS_TO_S_D(processes.data[j].delay_time - last_processes.data[i].delay_time),
242                processes.data[j].run_count - last_processes.data[i].run_count,
243                NS_TO_S_D(processes.data[j].exec_time), NS_TO_S_D(processes.data[j].delay_time),
244                processes.data[j].run_count, processes.data[j].name);
245            if (flags & FLAG_SHOW_THREADS)
246                print_threads(pid, flags);
247        }
248    }
249
250    {
251        struct thread_table tmp;
252        tmp = last_processes;
253        last_processes = processes;
254        processes = tmp;
255        processes.active = 0;
256        tmp = last_threads;
257        last_threads = threads;
258        threads = tmp;
259        threads.active = 0;
260    }
261}
262
263void
264sig_abort(int signum)
265{
266    printf("\e[?47l");
267    exit(0);
268}
269
270
271int schedtop_main(int argc, char **argv)
272{
273    int c;
274    DIR *d;
275    struct dirent *de;
276    char *namefilter = 0;
277    int pidfilter = 0;
278    uint32_t flags = 0;
279    int delay = 3000000;
280    float delay_f;
281
282    while(1) {
283        c = getopt(argc, argv, "d:ibtamun");
284        if (c == EOF)
285            break;
286        switch (c) {
287        case 'd':
288            delay_f = atof(optarg);
289            delay = delay_f * 1000000;
290            break;
291        case 'b':
292            flags |= FLAG_BATCH;
293            break;
294        case 'i':
295            flags |= FLAG_HIDE_IDLE;
296            break;
297        case 't':
298            flags |= FLAG_SHOW_THREADS;
299            break;
300        case 'a':
301            flags |= FLAG_USE_ALTERNATE_SCREEN;
302            break;
303        case 'm':
304            time_dp = 3;
305            time_div = 1000000;
306            break;
307        case 'u':
308            time_dp = 6;
309            time_div = 1000;
310            break;
311        case 'n':
312            time_dp = 9;
313            time_div = 1;
314            break;
315        }
316    }
317
318    d = opendir("/proc");
319    if(d == 0) return -1;
320
321    if (!(flags & FLAG_BATCH)) {
322        if(flags & FLAG_USE_ALTERNATE_SCREEN) {
323            signal(SIGINT, sig_abort);
324            signal(SIGPIPE, sig_abort);
325            signal(SIGTERM, sig_abort);
326            printf("\e7\e[?47h");
327        }
328        printf("\e[2J");
329    }
330    while (1) {
331        update_table(d, flags);
332        usleep(delay);
333    }
334    closedir(d);
335    return 0;
336}
337