| /* |
| * Simple trace backend |
| * |
| * Copyright IBM, Corp. 2010 |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2. See |
| * the COPYING file in the top-level directory. |
| * |
| */ |
| |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <time.h> |
| #include "trace.h" |
| |
| /** Trace file header event ID */ |
| #define HEADER_EVENT_ID (~(uint64_t)0) /* avoids conflicting with TraceEventIDs */ |
| |
| /** Trace file magic number */ |
| #define HEADER_MAGIC 0xf2b177cb0aa429b4ULL |
| |
| /** Trace file version number, bump if format changes */ |
| #define HEADER_VERSION 0 |
| |
| /** Trace buffer entry */ |
| typedef struct { |
| uint64_t event; |
| uint64_t timestamp_ns; |
| uint64_t x1; |
| uint64_t x2; |
| uint64_t x3; |
| uint64_t x4; |
| uint64_t x5; |
| uint64_t x6; |
| } TraceRecord; |
| |
| enum { |
| TRACE_BUF_LEN = 64 * 1024 / sizeof(TraceRecord), |
| }; |
| |
| static TraceRecord trace_buf[TRACE_BUF_LEN]; |
| static unsigned int trace_idx; |
| static FILE *trace_fp; |
| static char *trace_file_name = NULL; |
| static bool trace_file_enabled = false; |
| |
| void st_print_trace_file_status(FILE *stream, int (*stream_printf)(FILE *stream, const char *fmt, ...)) |
| { |
| stream_printf(stream, "Trace file \"%s\" %s.\n", |
| trace_file_name, trace_file_enabled ? "on" : "off"); |
| } |
| |
| static bool write_header(FILE *fp) |
| { |
| static const TraceRecord header = { |
| .event = HEADER_EVENT_ID, |
| .timestamp_ns = HEADER_MAGIC, |
| .x1 = HEADER_VERSION, |
| }; |
| |
| return fwrite(&header, sizeof header, 1, fp) == 1; |
| } |
| |
| /** |
| * set_trace_file : To set the name of a trace file. |
| * @file : pointer to the name to be set. |
| * If NULL, set to the default name-<pid> set at config time. |
| */ |
| bool st_set_trace_file(const char *file) |
| { |
| st_set_trace_file_enabled(false); |
| |
| free(trace_file_name); |
| |
| if (!file) { |
| if (asprintf(&trace_file_name, CONFIG_TRACE_FILE, getpid()) < 0) { |
| trace_file_name = NULL; |
| return false; |
| } |
| } else { |
| if (asprintf(&trace_file_name, "%s", file) < 0) { |
| trace_file_name = NULL; |
| return false; |
| } |
| } |
| |
| st_set_trace_file_enabled(true); |
| return true; |
| } |
| |
| static void flush_trace_file(void) |
| { |
| /* If the trace file is not open yet, open it now */ |
| if (!trace_fp) { |
| trace_fp = fopen(trace_file_name, "w"); |
| if (!trace_fp) { |
| /* Avoid repeatedly trying to open file on failure */ |
| trace_file_enabled = false; |
| return; |
| } |
| write_header(trace_fp); |
| } |
| |
| if (trace_fp) { |
| size_t unused; /* for when fwrite(3) is declared warn_unused_result */ |
| unused = fwrite(trace_buf, trace_idx * sizeof(trace_buf[0]), 1, trace_fp); |
| } |
| } |
| |
| void st_flush_trace_buffer(void) |
| { |
| if (trace_file_enabled) { |
| flush_trace_file(); |
| } |
| |
| /* Discard written trace records */ |
| trace_idx = 0; |
| } |
| |
| void st_set_trace_file_enabled(bool enable) |
| { |
| if (enable == trace_file_enabled) { |
| return; /* no change */ |
| } |
| |
| /* Flush/discard trace buffer */ |
| st_flush_trace_buffer(); |
| |
| /* To disable, close trace file */ |
| if (!enable) { |
| fclose(trace_fp); |
| trace_fp = NULL; |
| } |
| |
| trace_file_enabled = enable; |
| } |
| |
| static void trace(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3, |
| uint64_t x4, uint64_t x5, uint64_t x6) |
| { |
| TraceRecord *rec = &trace_buf[trace_idx]; |
| struct timespec ts; |
| |
| /* TODO Windows? It would be good to use qemu-timer here but that isn't |
| * linked into qemu-tools. Also we should avoid recursion in the tracing |
| * code, therefore it is useful to be self-contained. |
| */ |
| clock_gettime(CLOCK_MONOTONIC, &ts); |
| |
| if (!trace_list[event].state) { |
| return; |
| } |
| |
| rec->event = event; |
| rec->timestamp_ns = ts.tv_sec * 1000000000LL + ts.tv_nsec; |
| rec->x1 = x1; |
| rec->x2 = x2; |
| rec->x3 = x3; |
| rec->x4 = x4; |
| rec->x5 = x5; |
| rec->x6 = x6; |
| |
| if (++trace_idx == TRACE_BUF_LEN) { |
| st_flush_trace_buffer(); |
| } |
| } |
| |
| void trace0(TraceEventID event) |
| { |
| trace(event, 0, 0, 0, 0, 0, 0); |
| } |
| |
| void trace1(TraceEventID event, uint64_t x1) |
| { |
| trace(event, x1, 0, 0, 0, 0, 0); |
| } |
| |
| void trace2(TraceEventID event, uint64_t x1, uint64_t x2) |
| { |
| trace(event, x1, x2, 0, 0, 0, 0); |
| } |
| |
| void trace3(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3) |
| { |
| trace(event, x1, x2, x3, 0, 0, 0); |
| } |
| |
| void trace4(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4) |
| { |
| trace(event, x1, x2, x3, x4, 0, 0); |
| } |
| |
| void trace5(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5) |
| { |
| trace(event, x1, x2, x3, x4, x5, 0); |
| } |
| |
| void trace6(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6) |
| { |
| trace(event, x1, x2, x3, x4, x5, x6); |
| } |
| |
| /** |
| * Flush the trace buffer on exit |
| */ |
| static void __attribute__((constructor)) st_init(void) |
| { |
| atexit(st_flush_trace_buffer); |
| } |
| |
| void st_print_trace(FILE *stream, int (*stream_printf)(FILE *stream, const char *fmt, ...)) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < trace_idx; i++) { |
| stream_printf(stream, "Event %lu : %lx %lx %lx %lx %lx\n", |
| trace_buf[i].event, trace_buf[i].x1, trace_buf[i].x2, |
| trace_buf[i].x3, trace_buf[i].x4, trace_buf[i].x5); |
| } |
| } |
| |
| void st_print_trace_events(FILE *stream, int (*stream_printf)(FILE *stream, const char *fmt, ...)) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < NR_TRACE_EVENTS; i++) { |
| stream_printf(stream, "%s [Event ID %u] : state %u\n", |
| trace_list[i].tp_name, i, trace_list[i].state); |
| } |
| } |
| |
| static TraceEvent* find_trace_event_by_name(const char *tname) |
| { |
| unsigned int i; |
| |
| if (!tname) { |
| return NULL; |
| } |
| |
| for (i = 0; i < NR_TRACE_EVENTS; i++) { |
| if (!strcmp(trace_list[i].tp_name, tname)) { |
| return &trace_list[i]; |
| } |
| } |
| return NULL; /* indicates end of list reached without a match */ |
| } |
| |
| void st_change_trace_event_state(const char *tname, bool tstate) |
| { |
| TraceEvent *tp; |
| |
| tp = find_trace_event_by_name(tname); |
| if (tp) { |
| tp->state = tstate; |
| } |
| } |