| 1 | /* |
| 2 | * This file is part of the Linux Trace Toolkit viewer |
| 3 | * Copyright (C) 2003-2004 Michel Dagenais |
| 4 | * 2005 Mathieu Desnoyers |
| 5 | * 2011 Vincent Attard <vinc.attard@gmail.com> |
| 6 | * |
| 7 | * This program is free software; you can redistribute it and/or modify |
| 8 | * it under the terms of the GNU General Public License Version 2 as |
| 9 | * published by the Free Software Foundation; |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | * GNU General Public License for more details. |
| 15 | * |
| 16 | * You should have received a copy of the GNU General Public License |
| 17 | * along with this program; if not, write to the Free Software |
| 18 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, |
| 19 | * MA 02111-1307, USA. |
| 20 | */ |
| 21 | |
| 22 | /* |
| 23 | * Formatted dump plugin prints a formatted output of each events in a trace. |
| 24 | * The output format is defined as a parameter. It provides a default format |
| 25 | * easy to read, a "strace-like" format and the original textDump format for |
| 26 | * backward compatibility. |
| 27 | */ |
| 28 | |
| 29 | #ifdef HAVE_CONFIG_H |
| 30 | #include <config.h> |
| 31 | #endif |
| 32 | |
| 33 | #include <lttv/lttv.h> |
| 34 | #include <lttv/option.h> |
| 35 | #include <lttv/module.h> |
| 36 | #include <lttv/hook.h> |
| 37 | #include <lttv/attribute.h> |
| 38 | #include <lttv/iattribute.h> |
| 39 | #include <lttv/stats.h> |
| 40 | #include <lttv/filter.h> |
| 41 | #include <lttv/print.h> |
| 42 | #include <ltt/ltt.h> |
| 43 | #include <ltt/event.h> |
| 44 | #include <ltt/trace.h> |
| 45 | #include <stdio.h> |
| 46 | #include <inttypes.h> |
| 47 | #include <string.h> |
| 48 | #include <stdlib.h> |
| 49 | |
| 50 | static gboolean a_no_field_names; |
| 51 | static gboolean a_state; |
| 52 | static gboolean a_text; |
| 53 | static gboolean a_strace; |
| 54 | static gboolean a_meta; |
| 55 | static char *a_file_name; |
| 56 | static char *a_format; |
| 57 | |
| 58 | static LttvHooks *before_traceset; |
| 59 | static LttvHooks *event_hook; |
| 60 | |
| 61 | static const char default_format[] = |
| 62 | "channel:%c event:%e timestamp:%t elapsed:%l cpu:%u pid:%d " |
| 63 | "ppid:%i tgpid:%g process:%p state:%a payload:{ %m }"; |
| 64 | static const char textDump_format[] = |
| 65 | "%c.%e: %s.%n (%r/%c_%u), %d, %g, %p, %i, %y, %a { %m }"; |
| 66 | static const char strace_format[] = "%e(%m) %s.%n"; |
| 67 | static const char *fmt; |
| 68 | |
| 69 | static FILE *a_file; |
| 70 | |
| 71 | static GString *a_string; |
| 72 | |
| 73 | static int output_format_len; |
| 74 | |
| 75 | static gboolean open_output_file(void *hook_data, void *call_data) |
| 76 | { |
| 77 | if (a_text) { |
| 78 | /* textDump format (used with -T command option) */ |
| 79 | fmt = textDump_format; |
| 80 | } else if (a_strace) { |
| 81 | /* strace-like format (used with -S command option) */ |
| 82 | fmt = strace_format; |
| 83 | } else if (!a_format) { |
| 84 | /* Default format (used if no option) */ |
| 85 | fmt = default_format; |
| 86 | } else { |
| 87 | /* |
| 88 | * formattedDump format |
| 89 | * (used with -F command option following by the desired format) |
| 90 | */ |
| 91 | fmt = a_format; |
| 92 | } |
| 93 | |
| 94 | output_format_len = strlen(fmt); |
| 95 | |
| 96 | g_info("Open the output file"); |
| 97 | if (a_file_name == NULL) { |
| 98 | a_file = stdout; |
| 99 | } else { |
| 100 | a_file = fopen(a_file_name, "w"); |
| 101 | } |
| 102 | if (a_file == NULL) { |
| 103 | g_error("cannot open file %s", a_file_name); |
| 104 | } |
| 105 | return FALSE; |
| 106 | } |
| 107 | |
| 108 | static int write_event_content(void *hook_data, void *call_data) |
| 109 | { |
| 110 | gboolean result; |
| 111 | |
| 112 | LttvIAttribute *attributes = LTTV_IATTRIBUTE(lttv_global_attributes()); |
| 113 | |
| 114 | LttvTracefileContext *tfc = (LttvTracefileContext *)call_data; |
| 115 | |
| 116 | LttvTracefileState *tfs = (LttvTracefileState *)call_data; |
| 117 | |
| 118 | LttEvent *e; |
| 119 | |
| 120 | LttvAttributeValue value_filter; |
| 121 | |
| 122 | LttvFilter *filter; |
| 123 | |
| 124 | guint cpu = tfs->cpu; |
| 125 | LttvTraceState *ts = (LttvTraceState *)tfc->t_context; |
| 126 | LttvProcessState *process = ts->running_process[cpu]; |
| 127 | |
| 128 | e = ltt_tracefile_get_event(tfc->tf); |
| 129 | |
| 130 | result = lttv_iattribute_find_by_path(attributes, "filter/lttv_filter", |
| 131 | LTTV_POINTER, &value_filter); |
| 132 | g_assert(result); |
| 133 | filter = (LttvFilter *)*(value_filter.v_pointer); |
| 134 | |
| 135 | /* call to the filter if available */ |
| 136 | if (filter->head != NULL) { |
| 137 | if (!lttv_filter_tree_parse(filter->head, e, tfc->tf, |
| 138 | tfc->t_context->t, tfc, NULL, NULL)) { |
| 139 | return FALSE; |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | /* |
| 144 | * By default, metadata's channel won't be display: it goes directly |
| 145 | * to the next event. You can have metadata's information with -M |
| 146 | * switch (a_meta option). |
| 147 | */ |
| 148 | if (!a_meta && ltt_tracefile_name(tfs->parent.tf) == |
| 149 | g_quark_from_string("metadata")) { |
| 150 | return FALSE; |
| 151 | /* |
| 152 | * TODO: |
| 153 | * Investigate the use of filter to do it. |
| 154 | */ |
| 155 | } |
| 156 | |
| 157 | lttv_event_to_string(e, a_string, TRUE, !a_no_field_names, tfs); |
| 158 | |
| 159 | if (a_state) { |
| 160 | g_string_append_printf(a_string, "%s ", |
| 161 | g_quark_to_string(process->state->s)); |
| 162 | } |
| 163 | |
| 164 | g_string_append_printf(a_string, "\n"); |
| 165 | |
| 166 | fputs(a_string->str, a_file); |
| 167 | return FALSE; |
| 168 | } |
| 169 | |
| 170 | void lttv_event_to_string(LttEvent *e, GString *string_buffer, gboolean mandatory_fields, |
| 171 | gboolean field_names, LttvTracefileState *tfs) |
| 172 | { |
| 173 | struct marker_field *field; |
| 174 | struct marker_info *info; |
| 175 | |
| 176 | LttTime time; |
| 177 | LttTime elapse; |
| 178 | static LttTime time_prev = {0, 0}; |
| 179 | /* |
| 180 | * TODO: |
| 181 | * Added this static value into state.c and reset each time you do a |
| 182 | * seek for using it in the GUI. |
| 183 | */ |
| 184 | |
| 185 | int i; |
| 186 | guint cpu = tfs->cpu; |
| 187 | LttvTraceState *ts = (LttvTraceState *)tfs->parent.t_context; |
| 188 | LttvProcessState *process = ts->running_process[cpu]; |
| 189 | |
| 190 | info = marker_get_info_from_id(tfs->parent.tf->mdata, e->event_id); |
| 191 | if (mandatory_fields) { |
| 192 | time = ltt_event_time(e); |
| 193 | /* Calculate elapsed time between current and previous event */ |
| 194 | if (time_prev.tv_sec == 0 && time_prev.tv_nsec == 0) { |
| 195 | time_prev = ltt_event_time(e); |
| 196 | elapse.tv_sec = 0; |
| 197 | elapse.tv_nsec = 0; |
| 198 | /* |
| 199 | * TODO: |
| 200 | * Keep in mind that you should add the ability to |
| 201 | * restore the previous event time to state.c if you |
| 202 | * want to reuse this code into the GUI. |
| 203 | */ |
| 204 | } else { |
| 205 | elapse = ltt_time_sub(time, time_prev); |
| 206 | time_prev = time; |
| 207 | } |
| 208 | } |
| 209 | g_string_set_size(string_buffer, 0); |
| 210 | /* |
| 211 | * Switch case: |
| 212 | * all '%-' are replaced by the desired value in 'string_buffer' |
| 213 | */ |
| 214 | for (i = 0; i < output_format_len; i++) { |
| 215 | if (fmt[i] == '%') { |
| 216 | switch (fmt[++i]) { |
| 217 | case 't': |
| 218 | g_string_append_printf(string_buffer, |
| 219 | "%ld:%02ld:%02ld.%09ld", |
| 220 | time.tv_sec/3600, |
| 221 | (time.tv_sec%3600)/60, |
| 222 | time.tv_sec%60, |
| 223 | time.tv_nsec); |
| 224 | break; |
| 225 | case 'c': |
| 226 | g_string_append(string_buffer, |
| 227 | g_quark_to_string(ltt_tracefile_name(tfs->parent.tf))); |
| 228 | break; |
| 229 | case 'e': |
| 230 | g_string_append(string_buffer, |
| 231 | g_quark_to_string(info->name)); |
| 232 | break; |
| 233 | case 'd': |
| 234 | g_string_append_printf(string_buffer, "%u", |
| 235 | process->pid); |
| 236 | break; |
| 237 | case 's': |
| 238 | g_string_append_printf(string_buffer, "%ld", |
| 239 | time.tv_sec); |
| 240 | break; |
| 241 | case 'n': |
| 242 | g_string_append_printf(string_buffer, "%ld", |
| 243 | time.tv_nsec); |
| 244 | break; |
| 245 | case 'i': |
| 246 | g_string_append_printf(string_buffer, "%u", |
| 247 | process->ppid); |
| 248 | break; |
| 249 | case 'g': |
| 250 | g_string_append_printf(string_buffer, "%u", |
| 251 | process->tgid); |
| 252 | break; |
| 253 | case 'p': |
| 254 | g_string_append(string_buffer, |
| 255 | g_quark_to_string(process->name)); |
| 256 | break; |
| 257 | case 'u': |
| 258 | g_string_append_printf(string_buffer, "%u", cpu); |
| 259 | break; |
| 260 | case 'l': |
| 261 | g_string_append_printf(string_buffer, |
| 262 | "%ld.%09ld", |
| 263 | elapse.tv_sec, elapse.tv_nsec); |
| 264 | break; |
| 265 | case 'a': |
| 266 | g_string_append(string_buffer, |
| 267 | g_quark_to_string(process->state->t)); |
| 268 | break; |
| 269 | case 'm': |
| 270 | { |
| 271 | /* |
| 272 | * Get and print markers and tracepoints fields |
| 273 | * into 'string_buffer' |
| 274 | */ |
| 275 | if (marker_get_num_fields(info) == 0) |
| 276 | break; |
| 277 | for (field = marker_get_field(info, 0); |
| 278 | field != marker_get_field(info, marker_get_num_fields(info)); |
| 279 | field++) { |
| 280 | if (field != marker_get_field(info, 0)) { |
| 281 | g_string_append(string_buffer, ", "); |
| 282 | } |
| 283 | |
| 284 | lttv_print_field(e, field, string_buffer, field_names, tfs); |
| 285 | } |
| 286 | } |
| 287 | break; |
| 288 | case 'r': |
| 289 | g_string_append(string_buffer, g_quark_to_string( |
| 290 | ltt_trace_name(ltt_tracefile_get_trace(tfs->parent.tf)))); |
| 291 | break; |
| 292 | case '%': |
| 293 | g_string_append_c(string_buffer, '%'); |
| 294 | break; |
| 295 | case 'y': |
| 296 | g_string_append_printf(string_buffer, |
| 297 | "0x%" PRIx64, |
| 298 | process->current_function); |
| 299 | break; |
| 300 | } |
| 301 | } else { |
| 302 | /* Copy every character if different of '%' */ |
| 303 | g_string_append_c(string_buffer, fmt[i]); |
| 304 | } |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | static void init() |
| 309 | { |
| 310 | gboolean result; |
| 311 | |
| 312 | LttvAttributeValue value; |
| 313 | |
| 314 | LttvIAttribute *attributes = LTTV_IATTRIBUTE(lttv_global_attributes()); |
| 315 | |
| 316 | g_info("Init formattedDump.c"); |
| 317 | |
| 318 | a_string = g_string_new(""); |
| 319 | |
| 320 | a_file_name = NULL; |
| 321 | lttv_option_add("output", 'o', |
| 322 | "output file where the text is written", |
| 323 | "file name", |
| 324 | LTTV_OPT_STRING, &a_file_name, NULL, NULL); |
| 325 | |
| 326 | a_text = FALSE; |
| 327 | lttv_option_add("text", 'T', |
| 328 | "output the textDump format", |
| 329 | "", |
| 330 | LTTV_OPT_NONE, &a_text, NULL, NULL); |
| 331 | |
| 332 | a_strace = FALSE; |
| 333 | lttv_option_add("strace", 'S', |
| 334 | "output a \"strace-like\" format", |
| 335 | "", |
| 336 | LTTV_OPT_NONE, &a_strace, NULL, NULL); |
| 337 | |
| 338 | a_meta = FALSE; |
| 339 | lttv_option_add("metadata", 'M', |
| 340 | "add metadata information", |
| 341 | "", |
| 342 | LTTV_OPT_NONE, &a_meta, NULL, NULL); |
| 343 | |
| 344 | a_format = NULL; |
| 345 | lttv_option_add("format", 'F', |
| 346 | "output the desired format\n" |
| 347 | " FORMAT controls the output. " |
| 348 | "Interpreted sequences are:\n" |
| 349 | "\n" |
| 350 | " %c channel name\n" |
| 351 | " %p process name\n" |
| 352 | " %e event name\n" |
| 353 | " %r path to trace\n" |
| 354 | " %t timestamp (e.g., 2:08:54.025684145)\n" |
| 355 | " %s seconds\n" |
| 356 | " %n nanoseconds\n" |
| 357 | " %l elapsed time with the previous event\n" |
| 358 | " %d pid\n" |
| 359 | " %i ppid\n" |
| 360 | " %g tgid\n" |
| 361 | " %u cpu\n" |
| 362 | " %a state\n" |
| 363 | " %y memory address\n" |
| 364 | " %m markers and tracepoints fields\n", |
| 365 | "format string (e.g., \"channel:%c event:%e process:%p\")", |
| 366 | LTTV_OPT_STRING, &a_format, NULL, NULL); |
| 367 | |
| 368 | result = lttv_iattribute_find_by_path(attributes, "hooks/event", |
| 369 | LTTV_POINTER, &value); |
| 370 | g_assert(result); |
| 371 | event_hook = *(value.v_pointer); |
| 372 | g_assert(event_hook); |
| 373 | lttv_hooks_add(event_hook, write_event_content, NULL, LTTV_PRIO_DEFAULT); |
| 374 | |
| 375 | result = lttv_iattribute_find_by_path(attributes, "hooks/traceset/before", |
| 376 | LTTV_POINTER, &value); |
| 377 | g_assert(result); |
| 378 | before_traceset = *(value.v_pointer); |
| 379 | g_assert(before_traceset); |
| 380 | lttv_hooks_add(before_traceset, open_output_file, NULL, |
| 381 | LTTV_PRIO_DEFAULT); |
| 382 | |
| 383 | } |
| 384 | |
| 385 | static void destroy() |
| 386 | { |
| 387 | g_info("Destroy formattedDump"); |
| 388 | |
| 389 | lttv_option_remove("format"); |
| 390 | |
| 391 | lttv_option_remove("output"); |
| 392 | |
| 393 | lttv_option_remove("text"); |
| 394 | |
| 395 | lttv_option_remove("strace"); |
| 396 | |
| 397 | lttv_option_remove("metadata"); |
| 398 | |
| 399 | g_string_free(a_string, TRUE); |
| 400 | |
| 401 | lttv_hooks_remove_data(event_hook, write_event_content, NULL); |
| 402 | |
| 403 | lttv_hooks_remove_data(before_traceset, open_output_file, NULL); |
| 404 | |
| 405 | } |
| 406 | |
| 407 | |
| 408 | LTTV_MODULE("formattedDump", "Print events with desired format in a file", |
| 409 | "Produce a detailed formatted text printout of a trace", |
| 410 | init, destroy, "batchAnalysis", "option") |