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>



Reply via email to