1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <pretty/sizes.h>
6 #include <task-utils/get.h>
7 #include <task-utils/walker.h>
8 #include <zircon/listnode.h>
9 #include <zircon/status.h>
10 #include <zircon/syscalls.h>
11 #include <zircon/syscalls/exception.h>
12 #include <zircon/syscalls/object.h>
13 #include <zircon/time.h>
14 #include <zircon/types.h>
15 
16 #include <fcntl.h>
17 #include <inttypes.h>
18 #include <math.h>
19 #include <stdbool.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24 
25 enum sort_order {
26     UNSORTED,
27     SORT_TIME_DELTA
28 };
29 
30 typedef struct {
31     struct list_node node;
32 
33     // has it been seen this pass?
34     bool scanned;
35     zx_duration_t delta_time;
36 
37     // information about the thread
38     zx_koid_t proc_koid;
39     zx_koid_t koid;
40     zx_info_thread_t info;
41     zx_info_thread_stats_t stats;
42     char name[ZX_MAX_NAME_LEN];
43     char proc_name[ZX_MAX_NAME_LEN];
44 } thread_info_t;
45 
46 // arguments
47 static zx_duration_t delay = ZX_SEC(1);
48 static int count = -1;
49 static bool print_all = false;
50 static bool raw_time = false;
51 static enum sort_order sort_order = SORT_TIME_DELTA;
52 
53 // active locals
54 static struct list_node thread_list = LIST_INITIAL_VALUE(thread_list);
55 static char last_process_name[ZX_MAX_NAME_LEN];
56 static zx_koid_t last_process_scanned;
57 
58 // Return text representation of thread state.
state_string(const zx_info_thread_t * info)59 static const char* state_string(const zx_info_thread_t* info) {
60     if (info->wait_exception_port_type != ZX_EXCEPTION_PORT_TYPE_NONE) {
61         return "excp";
62     } else {
63         switch (ZX_THREAD_STATE_BASIC(info->state)) {
64         case ZX_THREAD_STATE_NEW:
65             return "new";
66         case ZX_THREAD_STATE_RUNNING:
67             return "run";
68         case ZX_THREAD_STATE_SUSPENDED:
69             return "susp";
70         case ZX_THREAD_STATE_BLOCKED:
71             return "block";
72         case ZX_THREAD_STATE_DYING:
73             return "dying";
74         case ZX_THREAD_STATE_DEAD:
75             return "dead";
76         default:
77             return "???";
78         }
79     }
80 }
81 
process_callback(void * unused_ctx,int depth,zx_handle_t proc,zx_koid_t koid,zx_koid_t parent_koid)82 static zx_status_t process_callback(void* unused_ctx, int depth,
83                                     zx_handle_t proc,
84                                     zx_koid_t koid, zx_koid_t parent_koid) {
85     last_process_scanned = koid;
86 
87     zx_status_t status = zx_object_get_property(
88         proc, ZX_PROP_NAME, &last_process_name, sizeof(last_process_name));
89     return status;
90 }
91 
92 // Adds a thread's information to the thread_list
thread_callback(void * unused_ctx,int depth,zx_handle_t thread,zx_koid_t koid,zx_koid_t parent_koid)93 static zx_status_t thread_callback(void* unused_ctx, int depth,
94                                    zx_handle_t thread,
95                                    zx_koid_t koid, zx_koid_t parent_koid) {
96     thread_info_t e = {};
97 
98     e.koid = koid;
99     e.scanned = true;
100 
101     e.proc_koid = last_process_scanned;
102     strlcpy(e.proc_name, last_process_name, sizeof(e.proc_name));
103 
104     zx_status_t status =
105         zx_object_get_property(thread, ZX_PROP_NAME, e.name, sizeof(e.name));
106     if (status != ZX_OK) {
107         return status;
108     }
109     status = zx_object_get_info(
110         thread, ZX_INFO_THREAD, &e.info, sizeof(e.info), NULL, NULL);
111     if (status != ZX_OK) {
112         return status;
113     }
114     status = zx_object_get_info(
115         thread, ZX_INFO_THREAD_STATS, &e.stats, sizeof(e.stats), NULL, NULL);
116     if (status != ZX_OK) {
117         return status;
118     }
119 
120     // see if this thread is in the list
121     thread_info_t* temp;
122     list_for_every_entry (&thread_list, temp, thread_info_t, node) {
123         if (e.koid == temp->koid) {
124             // mark it scanned, compute the delta time,
125             // and copy the new state over
126             temp->scanned = true;
127             temp->delta_time =
128                 zx_duration_sub_duration(e.stats.total_runtime, temp->stats.total_runtime);
129             temp->info = e.info;
130             temp->stats = e.stats;
131             return ZX_OK;
132         }
133     }
134 
135     // it wasn't in the list, add it
136     thread_info_t* new_entry = malloc(sizeof(thread_info_t));
137     *new_entry = e;
138 
139     list_add_tail(&thread_list, &new_entry->node);
140 
141     return ZX_OK;
142 }
143 
sort_threads(enum sort_order order)144 static void sort_threads(enum sort_order order) {
145     if (order == UNSORTED)
146         return;
147 
148     struct list_node new_list = LIST_INITIAL_VALUE(new_list);
149 
150     // cheezy sort into second list, then swap back to first
151     thread_info_t* e;
152     while ((e = list_remove_head_type(&thread_list, thread_info_t, node))) {
153         thread_info_t* t;
154 
155         bool found = false;
156         list_for_every_entry (&new_list, t, thread_info_t, node) {
157             if (order == SORT_TIME_DELTA) {
158                 if (e->delta_time > t->delta_time) {
159                     list_add_before(&t->node, &e->node);
160                     found = true;
161                     break;
162                 }
163             }
164         }
165 
166         // walked off the end
167         if (!found)
168             list_add_tail(&new_list, &e->node);
169     }
170 
171     list_move(&new_list, &thread_list);
172 }
173 
print_threads(void)174 static void print_threads(void) {
175     thread_info_t* e;
176     printf("%8s %8s %10s %5s %s\n",
177            "PID", "TID", raw_time ? "TIME_NS" : "TIME%", "STATE", "NAME");
178 
179     int i = 0;
180     list_for_every_entry (&thread_list, e, thread_info_t, node) {
181         // only print threads that are active
182         if (!print_all && e->delta_time == 0)
183             continue;
184 
185         if (!raw_time) {
186             double percent = 0;
187             if (e->delta_time > 0)
188                 percent = e->delta_time / (double)delay * 100;
189 
190             printf("%8lu %8lu %10.2f %5s %s:%s\n",
191                    e->proc_koid, e->koid, percent, state_string(&e->info),
192                    e->proc_name, e->name);
193         } else {
194             printf("%8lu %8lu %10lu %5s %s:%s\n",
195                    e->proc_koid, e->koid, e->delta_time, state_string(&e->info),
196                    e->proc_name, e->name);
197         }
198 
199         // only print the first count items (or all, if count < 0)
200         if (++i == count)
201             break;
202     }
203 }
204 
print_help(FILE * f)205 static void print_help(FILE* f) {
206     fprintf(f, "Usage: top [options]\n");
207     fprintf(f, "Options:\n");
208     fprintf(f, " -a              Print all threads, even if inactive\n");
209     fprintf(f, " -c <count>      Print the first count threads (default infinity)\n");
210     fprintf(f, " -d <delay>      Delay in seconds (default 1 second)\n");
211     fprintf(f, " -j <jobid>      Show only threads that belong to a given jobid\n");
212     fprintf(f, " -n <times>      Run this many times and then exit\n");
213     fprintf(f, " -o <sort field> Sort by different fields (default is time)\n");
214     fprintf(f, " -r              Print raw time in nanoseconds\n");
215     fprintf(f, "\nSupported sort fields:\n");
216     fprintf(f, "\tnone : no sorting, in job order\n");
217     fprintf(f, "\ttime : sort by delta time between scans\n");
218 }
219 
main(int argc,char ** argv)220 int main(int argc, char** argv) {
221     zx_handle_t target_job = ZX_HANDLE_INVALID;
222 
223     int num_loops = -1;
224     for (int i = 1; i < argc; ++i) {
225         const char* arg = argv[i];
226         if (!strcmp(arg, "--help") || !strcmp(arg, "-h")) {
227             print_help(stdout);
228             return 0;
229         }
230         if (!strcmp(arg, "-a")) {
231             print_all = true;
232         } else if (!strcmp(arg, "-d")) {
233             delay = 0;
234             if (i + 1 < argc) {
235                 delay = ZX_SEC(atoi(argv[i + 1]));
236             }
237             if (delay == 0) {
238                 fprintf(stderr, "Bad -d value '%s'\n", argv[i + 1]);
239                 print_help(stderr);
240                 return 1;
241             }
242             i++;
243         } else if (!strcmp(arg, "-n")) {
244             num_loops = 0;
245             if (i + 1 < argc) {
246                 num_loops = atoi(argv[i + 1]);
247             }
248             if (num_loops == 0) {
249                 fprintf(stderr, "Bad -n value '%s'\n", argv[i + 1]);
250                 print_help(stderr);
251                 return 1;
252             }
253             i++;
254         } else if (!strcmp(arg, "-c")) {
255             count = 0;
256             if (i + 1 < argc) {
257                 count = atoi(argv[i + 1]);
258             }
259             if (count == 0) {
260                 fprintf(stderr, "Bad count\n");
261                 print_help(stderr);
262                 return 1;
263             }
264             i++;
265         } else if (!strcmp(arg, "-o")) {
266             if (i + 1 >= argc) {
267                 fprintf(stderr, "Bad sort field\n");
268                 print_help(stderr);
269                 return 1;
270             } else if (!strcmp(argv[i + 1], "none")) {
271                 sort_order = UNSORTED;
272             } else if (!strcmp(argv[i + 1], "time")) {
273                 sort_order = SORT_TIME_DELTA;
274             } else {
275                 fprintf(stderr, "Bad sort field\n");
276                 print_help(stderr);
277                 return 1;
278             }
279             i++;
280         } else if (!strcmp(arg, "-r")) {
281             raw_time = true;
282         } else if (!strcmp(arg, "-j")) {
283             if (i + 1 >= argc) {
284                 fprintf(stderr, "Bad job field\n");
285                 print_help(stderr);
286                 return 1;
287             }
288             int jobid = atoi(argv[i + 1]);
289             zx_obj_type_t type;
290             zx_status_t status = get_task_by_koid(jobid, &type, &target_job);
291             if (status != ZX_OK) {
292                 fprintf(stderr, "ERROR: get_task_by_koid failed: %s (%d)\n",
293                         zx_status_get_string(status), status);
294                 return 1;
295             }
296             if (type != ZX_OBJ_TYPE_JOB) {
297                 fprintf(stderr, "ERROR: object with koid %d is not a job\n",
298                         jobid);
299                 return 1;
300             }
301             i++;
302         } else {
303             fprintf(stderr, "Unknown option: %s\n", arg);
304             print_help(stderr);
305             return 1;
306         }
307     }
308 
309     // set stdin to non blocking
310     fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
311 
312     int ret = 0;
313     bool first_run = true;
314     for (;;) {
315         zx_time_t next_deadline = zx_deadline_after(delay);
316 
317         // mark all active threads as not scanned
318         thread_info_t* e;
319         list_for_every_entry (&thread_list, e, thread_info_t, node) {
320             e->scanned = false;
321         }
322 
323         // If we have a target job, only walk the target subtree. Otherwise walk from root.
324         zx_status_t status;
325         if (target_job != ZX_HANDLE_INVALID) {
326             status = walk_job_tree(target_job, NULL, process_callback, thread_callback, NULL);
327         } else {
328             status = walk_root_job_tree(NULL, process_callback, thread_callback, NULL);
329         }
330         if (status != ZX_OK) {
331             fprintf(stderr, "WARNING: walking the job tree failed: %s (%d)\n",
332                     zx_status_get_string(status), status);
333             ret = 1;
334             goto finish;
335         }
336 
337         // remove every entry that hasn't been scanned this pass
338         thread_info_t* temp;
339         list_for_every_entry_safe (&thread_list, e, temp, thread_info_t, node) {
340             if (!e->scanned) {
341                 list_delete(&e->node);
342                 free(e);
343             }
344         }
345 
346         if (first_run) {
347             // We don't have data until after we scan twice, since we're
348             // computing deltas.
349             first_run = false;
350             continue;
351         }
352 
353         // sort the list
354         sort_threads(sort_order);
355 
356         // dump the list of threads
357         print_threads();
358 
359         if (num_loops > 0) {
360             if (--num_loops == 0) {
361                 break;
362             }
363         } else {
364             // TODO: replace once ctrl-c works in the shell
365             char c;
366             int err;
367             while ((err = read(STDIN_FILENO, &c, 1)) > 0) {
368                 if (c == 0x3) {
369                     ret = 0;
370                     goto finish;
371                 }
372             }
373         }
374 
375         zx_nanosleep(next_deadline);
376     }
377 
378 finish:
379     if (target_job != ZX_HANDLE_INVALID) {
380         zx_handle_close(target_job);
381     }
382     return ret;
383 }
384