contrib/plugins/uftrace: generate additional files for uftrace

Beyond traces per cpu, uftrace expect to find some specific files.
- info: contains information about machine/program run
  those values are not impacting uftrace behaviour (only reported by
  uftrace info), and we simply added empty strings.
- memory mapping: how every binary is mapped in memory. For system mode,
  we generate an empty mapping (uftrace_symbols.py, coming in future
  commit, will take care of that). For user mode, we copy current
  /proc/self/maps. We don't need to do any special filtering, as
  reported addresses will necessarily concern guest program, and not
  QEMU and its libraries.
- task: list of tasks. We present every vcpu/privilege level as a
  separate process, as it's the best view we can have when generating a
  (visual) chrome trace. Using threads is less convenient in terms of
  UI.

Reviewed-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Message-ID: <20250902075042.223990-7-pierrick.bouvier@linaro.org>
Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
Message-ID: <20250922093711.2768983-23-alex.bennee@linaro.org>
This commit is contained in:
Pierrick Bouvier 2025-09-22 10:37:07 +01:00 committed by Alex Bennée
parent 308c20108a
commit 7278747595

View file

@ -118,6 +118,127 @@ static uint64_t gettime_ns(void)
return now_ns;
}
static void uftrace_write_map(bool system_emulation)
{
const char *path = "./uftrace.data/sid-0.map";
if (system_emulation && access(path, F_OK) == 0) {
/* do not erase existing map in system emulation, as a custom one might
* already have been generated by uftrace_symbols.py */
return;
}
FILE *sid_map = fopen(path, "w");
g_assert(sid_map);
if (system_emulation) {
fprintf(sid_map,
"# map stack on highest address possible, to prevent uftrace\n"
"# from considering any kernel address\n");
fprintf(sid_map,
"ffffffffffff-ffffffffffff rw-p 00000000 00:00 0 [stack]\n");
} else {
/* in user mode, copy /proc/self/maps instead */
FILE *self_map = fopen("/proc/self/maps", "r");
g_assert(self_map);
for (;;) {
int c = fgetc(self_map);
if (c == EOF) {
break;
}
fputc(c, sid_map);
}
fclose(self_map);
}
fclose(sid_map);
}
static void uftrace_write_task(const GArray *traces)
{
FILE *task = fopen("./uftrace.data/task.txt", "w");
g_assert(task);
for (int i = 0; i < traces->len; ++i) {
Trace *t = g_array_index(traces, Trace*, i);
fprintf(task, "SESS timestamp=0.0 pid=%"PRIu32" sid=0 exename=\"%s\"\n",
t->id, t->name->str);
fprintf(task, "TASK timestamp=0.0 tid=%"PRIu32" pid=%"PRIu32"\n",
t->id, t->id);
}
fclose(task);
}
static void uftrace_write_info(const GArray *traces)
{
g_autoptr(GString) taskinfo_tids = g_string_new("taskinfo:tids=");
for (int i = 0; i < traces->len; ++i) {
Trace *t = g_array_index(traces, Trace*, i);
const char *delim = i > 0 ? "," : "";
g_string_append_printf(taskinfo_tids, "%s%"PRIu32, delim, t->id);
}
g_autoptr(GString) taskinfo_nr_tid = g_string_new("taskinfo:nr_tid=");
g_string_append_printf(taskinfo_nr_tid, "%d", traces->len);
FILE *info = fopen("./uftrace.data/info", "w");
g_assert(info);
/*
* $ uftrace dump --debug
* uftrace file header: magic = 4674726163652100
* uftrace file header: version = 4
* uftrace file header: header size = 40
* uftrace file header: endian = 1 (little)
* uftrace file header: class = 2 (64 bit)
* uftrace file header: features = 0x1263 (PLTHOOK | ...
* uftrace file header: info = 0x7bff (EXE_NAME | ...
* <0000000000000000>: 46 74 72 61 63 65 21 00 04 00 00 00 28 00 01 02
* <0000000000000010>: 63 12 00 00 00 00 00 00 ff 7b 00 00 00 00 00 00
* <0000000000000020>: 00 04 00 00 00 00 00 00
*/
const uint8_t header[] = {0x46, 0x74, 0x72, 0x61, 0x63, 0x65, 0x21, 0x00,
0x04, 0x00, 0x00, 0x00, 0x28, 0x00, 0x01, 0x02,
0x63, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
size_t wrote = fwrite(header, sizeof(header), 1, info);
g_assert(wrote == 1);
const char *info_data[] = {
"exename:",
"build_id:0000000000000000000000000000000000000000",
"exit_status:",
"cmdline:",
"cpuinfo:lines=2",
"cpuinfo:nr_cpus=",
"cpuinfo:desc=",
"meminfo:",
"osinfo:lines=3",
"osinfo:kernel=",
"osinfo:hostname=",
"osinfo:distro=",
"taskinfo:lines=2",
taskinfo_nr_tid->str,
taskinfo_tids->str,
"usageinfo:lines=6",
"usageinfo:systime=",
"usageinfo:usrtime=",
"usageinfo:ctxsw=",
"usageinfo:maxrss=",
"usageinfo:pagefault=",
"usageinfo:iops=",
"loadinfo:",
"record_date:",
"elapsed_time:",
"pattern_type:regex",
"uftrace_version:",
"utc_offset:",
0};
const char **info_data_it = info_data;
while (*(info_data_it)) {
fprintf(info, "%s\n", *info_data_it);
++info_data_it;
}
fclose(info);
}
static Callstack *callstack_new(void)
{
Callstack *cs = g_new0(Callstack, 1);
@ -612,14 +733,22 @@ static void vcpu_end(unsigned int vcpu_index)
static void at_exit(qemu_plugin_id_t id, void *data)
{
bool system_emulation = (bool) data;
g_autoptr(GArray) traces = g_array_new(0, 0, sizeof(Trace *));
for (size_t i = 0; i < qemu_plugin_num_vcpus(); ++i) {
Cpu *cpu = qemu_plugin_scoreboard_find(score, i);
for (size_t j = 0; j < cpu->traces->len; ++j) {
Trace *t = g_array_index(cpu->traces, Trace*, j);
trace_flush(t, true);
g_array_append_val(traces, t);
}
}
uftrace_write_map(system_emulation);
uftrace_write_info(traces);
uftrace_write_task(traces);
for (size_t i = 0; i < qemu_plugin_num_vcpus(); ++i) {
vcpu_end(i);
}
@ -656,7 +785,7 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
score = qemu_plugin_scoreboard_new(sizeof(Cpu));
qemu_plugin_register_vcpu_init_cb(id, vcpu_init);
qemu_plugin_register_atexit_cb(id, at_exit, NULL);
qemu_plugin_register_atexit_cb(id, at_exit, (void *) info->system_emulation);
qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
return 0;