ad1e7137c5ab5f9a1c7d46de7b8b02622dadc2b0
[lttngtop.git] / src / lttngtop.c
1 /*
2 * Copyright (C) 2011 Julien Desfossez
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License Version 2 as
6 * published by the Free Software Foundation;
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
16 * MA 02111-1307, USA.
17 */
18
19 #define _GNU_SOURCE
20 #include <config.h>
21 #include <stdio.h>
22 #include <stdint.h>
23 #include <babeltrace/babeltrace.h>
24 #include <babeltrace/ctf/events.h>
25 #include <babeltrace/ctf/callbacks.h>
26 #include <fcntl.h>
27 #include <pthread.h>
28 #include <popt.h>
29 #include <stdlib.h>
30 #include <ftw.h>
31 #include <dirent.h>
32 #include <ctype.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35 #include <string.h>
36 #include <errno.h>
37 #include <sys/types.h>
38 #include <fts.h>
39
40 #include "lttngtoptypes.h"
41 #include "cputop.h"
42 #include "iostreamtop.h"
43 #include "cursesdisplay.h"
44 #include "common.h"
45
46 #define DEFAULT_FILE_ARRAY_SIZE 1
47
48 const char *opt_input_path;
49
50 struct lttngtop *copy;
51 pthread_t display_thread;
52 pthread_t timer_thread;
53
54 unsigned long refresh_display = 1 * NSEC_PER_SEC;
55 unsigned long last_display_update = 0;
56 int quit = 0;
57
58 enum {
59 OPT_NONE = 0,
60 OPT_HELP,
61 OPT_LIST,
62 OPT_VERBOSE,
63 OPT_DEBUG,
64 OPT_NAMES,
65 };
66
67 static struct poptOption long_options[] = {
68 /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
69 { "help", 'h', POPT_ARG_NONE, NULL, OPT_HELP, NULL, NULL },
70 { NULL, 0, 0, NULL, 0, NULL, NULL },
71 };
72
73 void *refresh_thread(void *p)
74 {
75 while (1) {
76 sem_wait(&pause_sem);
77 sem_post(&pause_sem);
78 sem_post(&timer);
79 sleep(refresh_display/NSEC_PER_SEC);
80 }
81 }
82
83 void *ncurses_display(void *p)
84 {
85 unsigned int current_display_index = 0;
86
87 sem_wait(&bootstrap);
88 init_ncurses();
89
90 while (1) {
91 sem_wait(&timer);
92 sem_wait(&goodtodisplay);
93 sem_wait(&pause_sem);
94
95 copy = g_ptr_array_index(copies, current_display_index);
96 if (copy)
97 display(current_display_index++);
98
99 sem_post(&goodtoupdate);
100 sem_post(&pause_sem);
101
102 if (quit) {
103 reset_ncurses();
104 pthread_exit(0);
105 }
106 }
107 }
108
109 /*
110 * hook on each event to check the timestamp and refresh the display if
111 * necessary
112 */
113 enum bt_cb_ret check_timestamp(struct bt_ctf_event *call_data, void *private_data)
114 {
115 unsigned long timestamp;
116
117 timestamp = bt_ctf_get_timestamp(call_data);
118 if (timestamp == -1ULL)
119 goto error;
120
121 if (last_display_update == 0)
122 last_display_update = timestamp;
123
124 if (timestamp - last_display_update >= refresh_display) {
125 sem_wait(&goodtoupdate);
126 g_ptr_array_add(copies, get_copy_lttngtop(last_display_update,
127 timestamp));
128 sem_post(&goodtodisplay);
129 sem_post(&bootstrap);
130 last_display_update = timestamp;
131 }
132 return BT_CB_OK;
133
134 error:
135 fprintf(stderr, "check_timestamp callback error\n");
136 return BT_CB_ERROR_STOP;
137 }
138
139 /*
140 * get_perf_counter : get or create and return a perf_counter struct for
141 * either a process or a cpu (only one of the 2 parameters mandatory)
142 */
143 struct perfcounter *get_perf_counter(const char *name, struct processtop *proc,
144 struct cputime *cpu)
145 {
146 struct perfcounter *ret, *global;
147 GHashTable *table;
148
149 if (proc)
150 table = proc->perf;
151 else if (cpu)
152 table = cpu->perf;
153 else
154 goto error;
155
156 ret = g_hash_table_lookup(table, (gpointer) name);
157 if (ret)
158 goto end;
159
160 ret = g_new0(struct perfcounter, 1);
161 /* by default, make it visible in the UI */
162 ret->visible = 1;
163 g_hash_table_insert(table, (gpointer) name, ret);
164
165 global = g_hash_table_lookup(lttngtop.perf_list, (gpointer) name);
166 if (!global) {
167 global = g_new0(struct perfcounter, 1);
168 memcpy(global, ret, sizeof(struct perfcounter));
169 /* by default, sort on the first perf context */
170 if (g_hash_table_size(lttngtop.perf_list) == 0)
171 global->sort = 1;
172 g_hash_table_insert(lttngtop.perf_list, (gpointer) name, global);
173 }
174
175 end:
176 return ret;
177
178 error:
179 return NULL;
180 }
181
182 void update_perf_value(struct processtop *proc, struct cputime *cpu,
183 const char *name, int value)
184 {
185 struct perfcounter *cpu_perf, *process_perf;
186
187 cpu_perf = get_perf_counter(name, NULL, cpu);
188 if (cpu_perf->count < value) {
189 process_perf = get_perf_counter(name, proc, NULL);
190 process_perf->count += value - cpu_perf->count;
191 cpu_perf->count = value;
192 }
193 }
194
195 void extract_perf_counter_scope(struct bt_ctf_event *event,
196 struct definition *scope,
197 struct processtop *proc,
198 struct cputime *cpu)
199 {
200 struct definition const * const *list = NULL;
201 unsigned int count;
202 int i, ret;
203
204 if (!scope)
205 goto end;
206
207 ret = bt_ctf_get_field_list(event, scope, &list, &count);
208 if (ret < 0)
209 goto end;
210
211 for (i = 0; i < count; i++) {
212 const char *name = bt_ctf_field_name(list[i]);
213 if (strncmp(name, "_perf_", 6) == 0) {
214 int value = bt_ctf_get_uint64(list[i]);
215 if (bt_ctf_field_get_error())
216 continue;
217 update_perf_value(proc, cpu, name, value);
218 }
219 }
220
221 end:
222 return;
223 }
224
225 void update_perf_counter(struct processtop *proc, struct bt_ctf_event *event)
226 {
227 struct definition *scope;
228 uint64_t cpu_id;
229 struct cputime *cpu;
230
231 scope = bt_ctf_get_top_level_scope(event, BT_STREAM_PACKET_CONTEXT);
232 cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(event, scope, "cpu_id"));
233 if (bt_ctf_field_get_error()) {
234 fprintf(stderr, "[error] get cpu_id\n");
235 goto end;
236 }
237 cpu = get_cpu(cpu_id);
238
239 scope = bt_ctf_get_top_level_scope(event, BT_STREAM_EVENT_CONTEXT);
240 extract_perf_counter_scope(event, scope, proc, cpu);
241
242 scope = bt_ctf_get_top_level_scope(event, BT_STREAM_PACKET_CONTEXT);
243 extract_perf_counter_scope(event, scope, proc, cpu);
244
245 scope = bt_ctf_get_top_level_scope(event, BT_EVENT_CONTEXT);
246 extract_perf_counter_scope(event, scope, proc, cpu);
247
248 end:
249 return;
250 }
251
252 enum bt_cb_ret fix_process_table(struct bt_ctf_event *call_data,
253 void *private_data)
254 {
255 int pid, tid, ppid;
256 char *comm;
257 struct processtop *parent, *child;
258 struct definition *scope;
259 unsigned long timestamp;
260
261 /* FIXME : check context pid, tid, ppid and comm */
262
263 timestamp = bt_ctf_get_timestamp(call_data);
264 if (timestamp == -1ULL)
265 goto error;
266
267 scope = bt_ctf_get_top_level_scope(call_data, BT_STREAM_EVENT_CONTEXT);
268
269 pid = bt_ctf_get_int64(bt_ctf_get_field(call_data, scope, "_pid"));
270 if (bt_ctf_field_get_error()) {
271 // fprintf(stderr, "Missing pid context info\n");
272 goto error;
273 }
274 tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, scope, "_tid"));
275 if (bt_ctf_field_get_error()) {
276 // fprintf(stderr, "Missing tid context info\n");
277 goto error;
278 }
279 ppid = bt_ctf_get_int64(bt_ctf_get_field(call_data, scope, "_ppid"));
280 if (bt_ctf_field_get_error()) {
281 // fprintf(stderr, "Missing ppid context info\n");
282 goto error;
283 }
284 comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, scope, "_procname"));
285 if (bt_ctf_field_get_error()) {
286 // fprintf(stderr, "Missing procname context info\n");
287 goto error;
288 }
289
290 /* find or create the current process */
291 child = find_process_tid(&lttngtop, tid, comm);
292 if (!child)
293 child = add_proc(&lttngtop, tid, comm, timestamp);
294 update_proc(child, pid, tid, ppid, comm);
295
296 if (pid != tid) {
297 /* find or create the parent */
298 parent = find_process_tid(&lttngtop, pid, comm);
299 if (!parent) {
300 parent = add_proc(&lttngtop, pid, comm, timestamp);
301 parent->pid = pid;
302 }
303
304 /* attach the parent to the current process */
305 child->threadparent = parent;
306 add_thread(parent, child);
307 }
308
309 update_perf_counter(child, call_data);
310
311 return BT_CB_OK;
312
313 error:
314 return BT_CB_ERROR_STOP;
315 }
316
317 void init_lttngtop()
318 {
319 copies = g_ptr_array_new();
320 lttngtop.perf_list = g_hash_table_new(g_direct_hash, g_direct_equal);
321
322 sem_init(&goodtodisplay, 0, 0);
323 sem_init(&goodtoupdate, 0, 1);
324 sem_init(&timer, 0, 1);
325 sem_init(&bootstrap, 0, 0);
326 sem_init(&pause_sem, 0, 1);
327 sem_init(&end_trace_sem, 0, 0);
328
329 lttngtop.process_table = g_ptr_array_new();
330 lttngtop.files_table = g_ptr_array_new();
331 lttngtop.cpu_table = g_ptr_array_new();
332 }
333
334 void usage(FILE *fd)
335 {
336
337 }
338
339 /*
340 * Return 0 if caller should continue, < 0 if caller should return
341 * error, > 0 if caller should exit without reporting error.
342 */
343 static int parse_options(int argc, char **argv)
344 {
345 poptContext pc;
346 int opt, ret = 0;
347
348 if (argc == 1) {
349 usage(stdout);
350 return 1; /* exit cleanly */
351 }
352
353 pc = poptGetContext(NULL, argc, (const char **) argv, long_options, 0);
354 poptReadDefaultConfig(pc, 0);
355
356 while ((opt = poptGetNextOpt(pc)) != -1) {
357 switch (opt) {
358 case OPT_HELP:
359 usage(stdout);
360 ret = 1; /* exit cleanly */
361 goto end;
362 case OPT_LIST:
363 // list_formats(stdout);
364 ret = 1;
365 goto end;
366 case OPT_VERBOSE:
367 // babeltrace_verbose = 1;
368 break;
369 case OPT_DEBUG:
370 // babeltrace_debug = 1;
371 break;
372 case OPT_NAMES:
373 // opt_field_names = 1;
374 break;
375 default:
376 ret = -EINVAL;
377 goto end;
378 }
379 }
380
381 opt_input_path = poptGetArg(pc);
382 if (!opt_input_path) {
383 ret = -EINVAL;
384 goto end;
385 }
386 end:
387 if (pc) {
388 poptFreeContext(pc);
389 }
390 return ret;
391 }
392
393 void iter_trace(struct bt_context *bt_ctx)
394 {
395 struct bt_ctf_iter *iter;
396 struct bt_iter_pos begin_pos;
397 struct bt_ctf_event *event;
398 int ret = 0;
399
400 begin_pos.type = BT_SEEK_BEGIN;
401 iter = bt_ctf_iter_create(bt_ctx, &begin_pos, NULL);
402
403 /* at each event check if we need to refresh */
404 bt_ctf_iter_add_callback(iter, 0, NULL, 0,
405 check_timestamp,
406 NULL, NULL, NULL);
407 /* at each event, verify the status of the process table */
408 bt_ctf_iter_add_callback(iter, 0, NULL, 0,
409 fix_process_table,
410 NULL, NULL, NULL);
411 /* to handle the scheduling events */
412 bt_ctf_iter_add_callback(iter,
413 g_quark_from_static_string("sched_switch"),
414 NULL, 0, handle_sched_switch, NULL, NULL, NULL);
415 /* to clean up the process table */
416 bt_ctf_iter_add_callback(iter,
417 g_quark_from_static_string("sched_process_free"),
418 NULL, 0, handle_sched_process_free, NULL, NULL, NULL);
419
420 /* for IO top */
421 bt_ctf_iter_add_callback(iter,
422 g_quark_from_static_string("exit_syscall"),
423 NULL, 0, handle_exit_syscall, NULL, NULL, NULL);
424 bt_ctf_iter_add_callback(iter,
425 g_quark_from_static_string("sys_write"),
426 NULL, 0, handle_sys_write, NULL, NULL, NULL);
427 bt_ctf_iter_add_callback(iter,
428 g_quark_from_static_string("sys_read"),
429 NULL, 0, handle_sys_read, NULL, NULL, NULL);
430 while ((event = bt_ctf_iter_read_event(iter)) != NULL) {
431 ret = bt_iter_next(bt_ctf_get_iter(iter));
432 if (ret < 0)
433 goto end_iter;
434 }
435
436 /* block until quit, we reached the end of the trace */
437 sem_wait(&end_trace_sem);
438
439 end_iter:
440 bt_iter_destroy(bt_ctf_get_iter(iter));
441 }
442
443 /*
444 * bt_context_add_traces_recursive: Open a trace recursively
445 * (copied from BSD code in converter/babeltrace.c)
446 *
447 * Find each trace present in the subdirectory starting from the given
448 * path, and add them to the context. The packet_seek parameter can be
449 * NULL: this specify to use the default format packet_seek.
450 *
451 * Return: 0 on success, nonzero on failure.
452 * Unable to open toplevel: failure.
453 * Unable to open some subdirectory or file: warn and continue;
454 */
455 int bt_context_add_traces_recursive(struct bt_context *ctx, const char *path,
456 const char *format_str,
457 void (*packet_seek)(struct stream_pos *pos,
458 size_t offset, int whence))
459 {
460 FTS *tree;
461 FTSENT *node;
462 GArray *trace_ids;
463 char lpath[PATH_MAX];
464 char * const paths[2] = { lpath, NULL };
465 int ret;
466
467 /*
468 * Need to copy path, because fts_open can change it.
469 * It is the pointer array, not the strings, that are constant.
470 */
471 strncpy(lpath, path, PATH_MAX);
472 lpath[PATH_MAX - 1] = '\0';
473
474 tree = fts_open(paths, FTS_NOCHDIR | FTS_LOGICAL, 0);
475 if (tree == NULL) {
476 fprintf(stderr, "[error] [Context] Cannot traverse \"%s\" for reading.\n",
477 path);
478 return -EINVAL;
479 }
480
481 trace_ids = g_array_new(FALSE, TRUE, sizeof(int));
482
483 while ((node = fts_read(tree))) {
484 int dirfd, metafd;
485
486 if (!(node->fts_info & FTS_D))
487 continue;
488
489 dirfd = open(node->fts_accpath, 0);
490 if (dirfd < 0) {
491 fprintf(stderr, "[error] [Context] Unable to open trace "
492 "directory file descriptor.\n");
493 ret = dirfd;
494 goto error;
495 }
496 metafd = openat(dirfd, "metadata", O_RDONLY);
497 if (metafd < 0) {
498 ret = close(dirfd);
499 if (ret < 0) {
500 perror("close");
501 goto error;
502 }
503 } else {
504 int trace_id;
505
506 ret = close(metafd);
507 if (ret < 0) {
508 perror("close");
509 goto error;
510 }
511 ret = close(dirfd);
512 if (ret < 0) {
513 perror("close");
514 goto error;
515 }
516
517 trace_id = bt_context_add_trace(ctx,
518 node->fts_accpath, format_str,
519 packet_seek, NULL, NULL);
520 if (trace_id < 0) {
521 fprintf(stderr, "[error] [Context] opening trace \"%s\" from %s "
522 "for reading.\n", node->fts_accpath, path);
523 ret = trace_id;
524 goto error;
525 }
526 g_array_append_val(trace_ids, trace_id);
527 }
528 }
529
530 g_array_free(trace_ids, TRUE);
531 return 0;
532
533 error:
534 return ret;
535 }
536
537 int main(int argc, char **argv)
538 {
539 int ret;
540 struct bt_context *bt_ctx = NULL;
541
542 ret = parse_options(argc, argv);
543 if (ret < 0) {
544 fprintf(stdout, "Error parsing options.\n\n");
545 usage(stdout);
546 exit(EXIT_FAILURE);
547 } else if (ret > 0) {
548 exit(EXIT_SUCCESS);
549 }
550
551 init_lttngtop();
552
553 bt_ctx = bt_context_create();
554 ret = bt_context_add_traces_recursive(bt_ctx, opt_input_path, "ctf", NULL);
555 if (ret < 0) {
556 printf("[error] Opening the trace\n");
557 goto end;
558 }
559
560 pthread_create(&display_thread, NULL, ncurses_display, (void *) NULL);
561 pthread_create(&timer_thread, NULL, refresh_thread, (void *) NULL);
562
563 iter_trace(bt_ctx);
564
565 quit = 1;
566 pthread_join(display_thread, NULL);
567
568 end:
569 return 0;
570 }
This page took 0.038918 seconds and 3 git commands to generate.