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