]>
Commit | Line | Data |
---|---|---|
26f7227b SH |
1 | /* |
2 | * Simple trace backend | |
3 | * | |
4 | * Copyright IBM, Corp. 2010 | |
5 | * | |
6 | * This work is licensed under the terms of the GNU GPL, version 2. See | |
7 | * the COPYING file in the top-level directory. | |
8 | * | |
9 | */ | |
10 | ||
11 | #include <stdlib.h> | |
12 | #include <stdint.h> | |
13 | #include <stdio.h> | |
14 | #include <time.h> | |
0b5538c3 SH |
15 | #include <signal.h> |
16 | #include <pthread.h> | |
17 | #include "qerror.h" | |
c57c846a | 18 | #include "qemu-timer.h" |
26f7227b SH |
19 | #include "trace.h" |
20 | ||
21 | /** Trace file header event ID */ | |
22 | #define HEADER_EVENT_ID (~(uint64_t)0) /* avoids conflicting with TraceEventIDs */ | |
23 | ||
24 | /** Trace file magic number */ | |
25 | #define HEADER_MAGIC 0xf2b177cb0aa429b4ULL | |
26 | ||
27 | /** Trace file version number, bump if format changes */ | |
28 | #define HEADER_VERSION 0 | |
29 | ||
0b5538c3 SH |
30 | /** Records were dropped event ID */ |
31 | #define DROPPED_EVENT_ID (~(uint64_t)0 - 1) | |
32 | ||
33 | /** Trace record is valid */ | |
34 | #define TRACE_RECORD_VALID ((uint64_t)1 << 63) | |
35 | ||
26f7227b SH |
36 | /** Trace buffer entry */ |
37 | typedef struct { | |
38 | uint64_t event; | |
39 | uint64_t timestamp_ns; | |
40 | uint64_t x1; | |
41 | uint64_t x2; | |
42 | uint64_t x3; | |
43 | uint64_t x4; | |
44 | uint64_t x5; | |
45 | uint64_t x6; | |
46 | } TraceRecord; | |
47 | ||
48 | enum { | |
0b5538c3 SH |
49 | TRACE_BUF_LEN = 4096, |
50 | TRACE_BUF_FLUSH_THRESHOLD = TRACE_BUF_LEN / 4, | |
26f7227b SH |
51 | }; |
52 | ||
0b5538c3 SH |
53 | /* |
54 | * Trace records are written out by a dedicated thread. The thread waits for | |
55 | * records to become available, writes them out, and then waits again. | |
56 | */ | |
57 | static pthread_mutex_t trace_lock = PTHREAD_MUTEX_INITIALIZER; | |
58 | static pthread_cond_t trace_available_cond = PTHREAD_COND_INITIALIZER; | |
59 | static pthread_cond_t trace_empty_cond = PTHREAD_COND_INITIALIZER; | |
60 | static bool trace_available; | |
61 | static bool trace_writeout_enabled; | |
62 | ||
26f7227b SH |
63 | static TraceRecord trace_buf[TRACE_BUF_LEN]; |
64 | static unsigned int trace_idx; | |
65 | static FILE *trace_fp; | |
c5ceb523 | 66 | static char *trace_file_name = NULL; |
26f7227b | 67 | |
c5ceb523 | 68 | /** |
0b5538c3 SH |
69 | * Read a trace record from the trace buffer |
70 | * | |
71 | * @idx Trace buffer index | |
72 | * @record Trace record to fill | |
73 | * | |
74 | * Returns false if the record is not valid. | |
c5ceb523 | 75 | */ |
0b5538c3 | 76 | static bool get_trace_record(unsigned int idx, TraceRecord *record) |
9410b56c | 77 | { |
0b5538c3 SH |
78 | if (!(trace_buf[idx].event & TRACE_RECORD_VALID)) { |
79 | return false; | |
9410b56c PS |
80 | } |
81 | ||
0b5538c3 SH |
82 | __sync_synchronize(); /* read memory barrier before accessing record */ |
83 | ||
84 | *record = trace_buf[idx]; | |
85 | record->event &= ~TRACE_RECORD_VALID; | |
c5ceb523 | 86 | return true; |
9410b56c PS |
87 | } |
88 | ||
0b5538c3 SH |
89 | /** |
90 | * Kick writeout thread | |
91 | * | |
92 | * @wait Whether to wait for writeout thread to complete | |
93 | */ | |
94 | static void flush_trace_file(bool wait) | |
26f7227b | 95 | { |
0b5538c3 SH |
96 | pthread_mutex_lock(&trace_lock); |
97 | trace_available = true; | |
98 | pthread_cond_signal(&trace_available_cond); | |
c5ceb523 | 99 | |
0b5538c3 SH |
100 | if (wait) { |
101 | pthread_cond_wait(&trace_empty_cond, &trace_lock); | |
26f7227b | 102 | } |
0b5538c3 SH |
103 | |
104 | pthread_mutex_unlock(&trace_lock); | |
c5ceb523 SH |
105 | } |
106 | ||
0b5538c3 | 107 | static void wait_for_trace_records_available(void) |
c5ceb523 | 108 | { |
0b5538c3 SH |
109 | pthread_mutex_lock(&trace_lock); |
110 | while (!(trace_available && trace_writeout_enabled)) { | |
111 | pthread_cond_signal(&trace_empty_cond); | |
112 | pthread_cond_wait(&trace_available_cond, &trace_lock); | |
c5ceb523 | 113 | } |
0b5538c3 SH |
114 | trace_available = false; |
115 | pthread_mutex_unlock(&trace_lock); | |
26f7227b SH |
116 | } |
117 | ||
0b5538c3 | 118 | static void *writeout_thread(void *opaque) |
26f7227b | 119 | { |
0b5538c3 SH |
120 | TraceRecord record; |
121 | unsigned int writeout_idx = 0; | |
122 | unsigned int num_available, idx; | |
123 | size_t unused; | |
124 | ||
125 | for (;;) { | |
126 | wait_for_trace_records_available(); | |
127 | ||
128 | num_available = trace_idx - writeout_idx; | |
129 | if (num_available > TRACE_BUF_LEN) { | |
130 | record = (TraceRecord){ | |
131 | .event = DROPPED_EVENT_ID, | |
132 | .x1 = num_available, | |
133 | }; | |
134 | unused = fwrite(&record, sizeof(record), 1, trace_fp); | |
135 | writeout_idx += num_available; | |
136 | } | |
26f7227b | 137 | |
0b5538c3 SH |
138 | idx = writeout_idx % TRACE_BUF_LEN; |
139 | while (get_trace_record(idx, &record)) { | |
140 | trace_buf[idx].event = 0; /* clear valid bit */ | |
141 | unused = fwrite(&record, sizeof(record), 1, trace_fp); | |
142 | idx = ++writeout_idx % TRACE_BUF_LEN; | |
143 | } | |
26f7227b | 144 | |
0b5538c3 | 145 | fflush(trace_fp); |
26f7227b | 146 | } |
0b5538c3 | 147 | return NULL; |
26f7227b SH |
148 | } |
149 | ||
150 | static void trace(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3, | |
151 | uint64_t x4, uint64_t x5, uint64_t x6) | |
152 | { | |
0b5538c3 SH |
153 | unsigned int idx; |
154 | uint64_t timestamp; | |
26f7227b | 155 | |
22890ab5 PS |
156 | if (!trace_list[event].state) { |
157 | return; | |
158 | } | |
159 | ||
0b5538c3 SH |
160 | timestamp = get_clock(); |
161 | ||
162 | idx = __sync_fetch_and_add(&trace_idx, 1) % TRACE_BUF_LEN; | |
163 | trace_buf[idx] = (TraceRecord){ | |
164 | .event = event, | |
165 | .timestamp_ns = timestamp, | |
166 | .x1 = x1, | |
167 | .x2 = x2, | |
168 | .x3 = x3, | |
169 | .x4 = x4, | |
170 | .x5 = x5, | |
171 | .x6 = x6, | |
172 | }; | |
173 | __sync_synchronize(); /* write barrier before marking as valid */ | |
174 | trace_buf[idx].event |= TRACE_RECORD_VALID; | |
175 | ||
176 | if ((idx + 1) % TRACE_BUF_FLUSH_THRESHOLD == 0) { | |
177 | flush_trace_file(false); | |
26f7227b SH |
178 | } |
179 | } | |
180 | ||
181 | void trace0(TraceEventID event) | |
182 | { | |
183 | trace(event, 0, 0, 0, 0, 0, 0); | |
184 | } | |
185 | ||
186 | void trace1(TraceEventID event, uint64_t x1) | |
187 | { | |
188 | trace(event, x1, 0, 0, 0, 0, 0); | |
189 | } | |
190 | ||
191 | void trace2(TraceEventID event, uint64_t x1, uint64_t x2) | |
192 | { | |
193 | trace(event, x1, x2, 0, 0, 0, 0); | |
194 | } | |
195 | ||
196 | void trace3(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3) | |
197 | { | |
198 | trace(event, x1, x2, x3, 0, 0, 0); | |
199 | } | |
200 | ||
201 | void trace4(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4) | |
202 | { | |
203 | trace(event, x1, x2, x3, x4, 0, 0); | |
204 | } | |
205 | ||
206 | void trace5(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5) | |
207 | { | |
208 | trace(event, x1, x2, x3, x4, x5, 0); | |
209 | } | |
210 | ||
211 | void trace6(TraceEventID event, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6) | |
212 | { | |
213 | trace(event, x1, x2, x3, x4, x5, x6); | |
214 | } | |
215 | ||
0b5538c3 SH |
216 | void st_set_trace_file_enabled(bool enable) |
217 | { | |
218 | if (enable == !!trace_fp) { | |
219 | return; /* no change */ | |
220 | } | |
221 | ||
222 | /* Halt trace writeout */ | |
223 | flush_trace_file(true); | |
224 | trace_writeout_enabled = false; | |
225 | flush_trace_file(true); | |
226 | ||
227 | if (enable) { | |
228 | static const TraceRecord header = { | |
229 | .event = HEADER_EVENT_ID, | |
230 | .timestamp_ns = HEADER_MAGIC, | |
231 | .x1 = HEADER_VERSION, | |
232 | }; | |
233 | ||
234 | trace_fp = fopen(trace_file_name, "w"); | |
235 | if (!trace_fp) { | |
236 | return; | |
237 | } | |
238 | ||
239 | if (fwrite(&header, sizeof header, 1, trace_fp) != 1) { | |
240 | fclose(trace_fp); | |
241 | trace_fp = NULL; | |
242 | return; | |
243 | } | |
244 | ||
245 | /* Resume trace writeout */ | |
246 | trace_writeout_enabled = true; | |
247 | flush_trace_file(false); | |
248 | } else { | |
249 | fclose(trace_fp); | |
250 | trace_fp = NULL; | |
251 | } | |
252 | } | |
253 | ||
26f7227b | 254 | /** |
0b5538c3 SH |
255 | * Set the name of a trace file |
256 | * | |
257 | * @file The trace file name or NULL for the default name-<pid> set at | |
258 | * config time | |
26f7227b | 259 | */ |
0b5538c3 | 260 | bool st_set_trace_file(const char *file) |
26f7227b | 261 | { |
0b5538c3 SH |
262 | st_set_trace_file_enabled(false); |
263 | ||
264 | free(trace_file_name); | |
265 | ||
266 | if (!file) { | |
267 | if (asprintf(&trace_file_name, CONFIG_TRACE_FILE, getpid()) < 0) { | |
268 | trace_file_name = NULL; | |
269 | return false; | |
270 | } | |
271 | } else { | |
272 | if (asprintf(&trace_file_name, "%s", file) < 0) { | |
273 | trace_file_name = NULL; | |
274 | return false; | |
275 | } | |
276 | } | |
277 | ||
278 | st_set_trace_file_enabled(true); | |
279 | return true; | |
280 | } | |
281 | ||
282 | void st_print_trace_file_status(FILE *stream, int (*stream_printf)(FILE *stream, const char *fmt, ...)) | |
283 | { | |
284 | stream_printf(stream, "Trace file \"%s\" %s.\n", | |
285 | trace_file_name, trace_fp ? "on" : "off"); | |
26f7227b | 286 | } |
22890ab5 PS |
287 | |
288 | void st_print_trace(FILE *stream, int (*stream_printf)(FILE *stream, const char *fmt, ...)) | |
289 | { | |
290 | unsigned int i; | |
291 | ||
0b5538c3 SH |
292 | for (i = 0; i < TRACE_BUF_LEN; i++) { |
293 | TraceRecord record; | |
294 | ||
295 | if (!get_trace_record(i, &record)) { | |
296 | continue; | |
297 | } | |
a12c668f BS |
298 | stream_printf(stream, "Event %" PRIu64 " : %" PRIx64 " %" PRIx64 |
299 | " %" PRIx64 " %" PRIx64 " %" PRIx64 " %" PRIx64 "\n", | |
0b5538c3 SH |
300 | record.event, record.x1, record.x2, |
301 | record.x3, record.x4, record.x5, | |
302 | record.x6); | |
22890ab5 PS |
303 | } |
304 | } | |
305 | ||
306 | void st_print_trace_events(FILE *stream, int (*stream_printf)(FILE *stream, const char *fmt, ...)) | |
307 | { | |
308 | unsigned int i; | |
309 | ||
310 | for (i = 0; i < NR_TRACE_EVENTS; i++) { | |
311 | stream_printf(stream, "%s [Event ID %u] : state %u\n", | |
312 | trace_list[i].tp_name, i, trace_list[i].state); | |
313 | } | |
314 | } | |
315 | ||
0b5538c3 | 316 | bool st_change_trace_event_state(const char *name, bool enabled) |
22890ab5 PS |
317 | { |
318 | unsigned int i; | |
319 | ||
22890ab5 | 320 | for (i = 0; i < NR_TRACE_EVENTS; i++) { |
0b5538c3 SH |
321 | if (!strcmp(trace_list[i].tp_name, name)) { |
322 | trace_list[i].state = enabled; | |
323 | return true; | |
22890ab5 PS |
324 | } |
325 | } | |
0b5538c3 SH |
326 | return false; |
327 | } | |
328 | ||
329 | void st_flush_trace_buffer(void) | |
330 | { | |
331 | flush_trace_file(true); | |
22890ab5 PS |
332 | } |
333 | ||
0b5538c3 | 334 | void st_init(const char *file) |
22890ab5 | 335 | { |
0b5538c3 SH |
336 | pthread_t thread; |
337 | pthread_attr_t attr; | |
338 | sigset_t set, oldset; | |
339 | int ret; | |
340 | ||
341 | pthread_attr_init(&attr); | |
342 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); | |
22890ab5 | 343 | |
0b5538c3 SH |
344 | sigfillset(&set); |
345 | pthread_sigmask(SIG_SETMASK, &set, &oldset); | |
346 | ret = pthread_create(&thread, &attr, writeout_thread, NULL); | |
347 | pthread_sigmask(SIG_SETMASK, &oldset, NULL); | |
348 | ||
349 | if (ret != 0) { | |
350 | error_report("warning: unable to create trace file thread\n"); | |
351 | return; | |
22890ab5 | 352 | } |
0b5538c3 SH |
353 | |
354 | atexit(st_flush_trace_buffer); | |
355 | st_set_trace_file(file); | |
22890ab5 | 356 | } |