On 8/8/25 2:45 PM, Philippe Mathieu-Daudé wrote:
On 8/8/25 22:41, Pierrick Bouvier wrote:
We implement tracing, following uftrace format.
Trace is flushed every 32 MB, so file operations don't impact
performance at runtime.
A different trace is generated per cpu, and we ensure they have a unique
name, based on vcpu_index, while keeping room for privilege level coming
in next commit.
Uftrace format is not officially documented, but it can be found here:
https://github.com/namhyung/uftrace/blob/v0.18/libmcount/record.c#L909
Signed-off-by: Pierrick Bouvier <pierrick.bouv...@linaro.org>
---
contrib/plugins/uftrace.c | 150 +++++++++++++++++++++++++++++++++++++-
1 file changed, 149 insertions(+), 1 deletion(-)
diff --git a/contrib/plugins/uftrace.c b/contrib/plugins/uftrace.c
index bb775916270..830967c215b 100644
--- a/contrib/plugins/uftrace.c
+++ b/contrib/plugins/uftrace.c
@@ -12,6 +12,13 @@
#include <qemu-plugin.h>
#include <glib.h>
#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+
+#define MiB (INT64_C(1) << 20)
+#define NANOSECONDS_PER_SECOND 1000000000LL
#define TRACE_FLUSH_SIZE (32 * MiB)
#define TRACE_ID_SCALE 100
(and use)
Ok.
@@ -44,9 +59,41 @@ typedef struct {
struct qemu_plugin_register *reg_fp;
} Aarch64Cpu;
+typedef struct {
+ uint64_t timestamp;
+ uint64_t data;
+} UftraceEntry;
+
+typedef enum {
+ UFTRACE_ENTRY,
+ UFTRACE_EXIT,
+ UFTRACE_LOST,
+ UFTRACE_EVENT
UFTRACE_EVENT,
Ok.
(in case more types appear)
+} UftraceRecordType;
+static void trace_add_entry(Trace *t, uint64_t timestamp, uint64_t pc,
+ size_t depth, UftraceRecordType type)
+{
+ /* https://github.com/namhyung/uftrace/blob/v0.18/libmcount/record.c#L909
*/
+ const uint64_t record_magic = 0x5;
+ uint64_t data = type | record_magic << 3;
uint64_t data = type | (record_magic << 3);
There is nothing ambiguous here (by operator precedence), but I'll add
it if it can help with readability.
+ data += depth << 6;
+ data += pc << 16;
+ UftraceEntry e = {.timestamp = timestamp, .data = data};
+ g_array_append_val(t->t, e);
+ if (t->t->len * sizeof(UftraceEntry) > 32 * MiB) {
+ trace_flush(t, true);
+ }
+}
static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
@@ -297,6 +432,16 @@ static void vcpu_init(qemu_plugin_id_t id, unsigned int
vcpu_index)
cpu->ops.init(cpu);
cpu->buf = g_byte_array_new();
+ g_assert(vcpu_index < UINT32_MAX / 100);
+ /* trace_id is: cpu_number * 100 */
+ uint32_t trace_id = (vcpu_index + 1) * 100;
+
+ g_autoptr(GString) trace_name = g_string_new(NULL);
+ g_string_append_printf(trace_name, "cpu%u", vcpu_index);
g_autofree char *trace_name = g_strdup_printf("cpu%u", vcpu_index);
and pass (const?) char * to trace_new().
I tried to do something clean with GString everywhere for
dynamic/mutable strings, and const char* for immutable ones, favoring
readability and clear memory ownership over C char pointer soup. It does
not optimize anything important in this context since it's called only
once during the program execution.
Thus, I would prefer to keep it as it is if that's acceptable for you.
+ cpu->trace = trace_new(trace_id, trace_name);
+ /* create/truncate trace file */
+ trace_flush(cpu->trace, false);
+
cpu->cs = callstack_new();
}
Reviewed-by: Philippe Mathieu-Daudé <phi...@linaro.org>