From: "Vanderson M. do Rosario" <vanderson...@gmail.com> These commands allow the exploration of TBs generated by the TCG. Understand which one hotter, with more guest/host instructions... and examine their guest code.
The goal of this command is to allow the dynamic exploration of TCG behavior and code quality. Therefore, for now, a corresponding QMP command is not worthwhile. Example of output: ------------------------------ TB id:0 | phys:0xa21f562e virt:0x0000000000000000 flags:0x00028010 0 inv/1 | exec:6171503732/0 guest inst cov:94.77% | trans:1 ints: g:8 op:28 op_opt:24 spills:0 | h/g (host bytes / guest insts): 37.000000 0xa21f562e: 00002797 auipc a5,8192 # 0xa21f762e 0xa21f5632: a2278793 addi a5,a5,-1502 0xa21f5636: 639c ld a5,0(a5) 0xa21f5638: 00178713 addi a4,a5,1 0xa21f563c: 00002797 auipc a5,8192 # 0xa21f763c 0xa21f5640: a1478793 addi a5,a5,-1516 0xa21f5644: e398 sd a4,0(a5) 0xa21f5646: b7e5 j -24 # 0xa21f562e ------------------------------ Acked-by: Dr. David Alan Gilbert <dgilb...@redhat.com> Signed-off-by: Vanderson M. do Rosario <vanderson...@gmail.com> Message-Id: <20190829173437.5926-10-vanderson...@gmail.com> [AJB: fix authorship, dropped coverset] Signed-off-by: Alex Bennée <alex.ben...@linaro.org> Signed-off-by: Fei Wu <fei2...@intel.com> --- accel/tcg/monitor.c | 112 ++++++++++++++++++++++++ accel/tcg/tb-stats.c | 177 ++++++++++++++++++++++++++++++++++++++ disas/disas-mon.c | 15 +++- disas/disas.c | 2 + hmp-commands-info.hx | 16 ++++ include/disas/disas.h | 8 +- include/exec/tb-stats.h | 25 ++++++ include/monitor/hmp.h | 2 + monitor/hmp-cmds-target.c | 3 +- 9 files changed, 356 insertions(+), 4 deletions(-) diff --git a/accel/tcg/monitor.c b/accel/tcg/monitor.c index 60b66f16ff..0ce5a1cb45 100644 --- a/accel/tcg/monitor.c +++ b/accel/tcg/monitor.c @@ -8,18 +8,22 @@ #include "qemu/osdep.h" #include "qemu/accel.h" +#include "qemu/log.h" #include "qapi/error.h" #include "qapi/type-helpers.h" #include "qapi/qapi-commands-machine.h" #include "qapi/qmp/qdict.h" #include "monitor/monitor.h" #include "monitor/hmp.h" +#include "monitor/hmp-target.h" #include "sysemu/cpus.h" #include "sysemu/cpu-timers.h" #include "sysemu/tcg.h" #include "tcg/tcg.h" #include "exec/tb-stats.h" #include "exec/tb-flush.h" +#include "disas/disas.h" +#include "tb-context.h" #include "internal.h" #include "tb-context.h" @@ -185,6 +189,114 @@ void hmp_tbstats(Monitor *mon, const QDict *qdict) } +struct tblist_dump_info { + int count; + int sortedby; + Monitor *mon; +}; + +static void do_dump_tblist_info_safe(CPUState *cpu, run_on_cpu_data info) +{ + struct tblist_dump_info *tbdi = info.host_ptr; + g_autoptr(GString) buf = g_string_new(""); + + dump_tblist_info(buf, tbdi->count, tbdi->sortedby); + monitor_printf(tbdi->mon, "%s", buf->str); + + g_free(tbdi); +} + +void hmp_info_tblist(Monitor *mon, const QDict *qdict) +{ + int number_int; + const char *sortedby_str = NULL; + + if (!tcg_enabled()) { + monitor_printf(mon, "Only available with accel=tcg\n"); + return; + } + if (!tb_ctx.tb_stats.map) { + monitor_printf(mon, "no TB information recorded\n"); + return; + } + + number_int = qdict_get_try_int(qdict, "number", 10); + sortedby_str = qdict_get_try_str(qdict, "sortedby"); + + int sortedby = SORT_BY_HOTNESS; + if (sortedby_str == NULL || strcmp(sortedby_str, "hotness") == 0) { + sortedby = SORT_BY_HOTNESS; + } else if (strcmp(sortedby_str, "hg") == 0) { + sortedby = SORT_BY_HG; + } else if (strcmp(sortedby_str, "spills") == 0) { + sortedby = SORT_BY_SPILLS; + } else { + monitor_printf(mon, "valid sort options are: hotness hg spills\n"); + return; + } + + struct tblist_dump_info *tbdi = g_new(struct tblist_dump_info, 1); + tbdi->count = number_int; + tbdi->sortedby = sortedby; + tbdi->mon = mon; + async_safe_run_on_cpu(first_cpu, do_dump_tblist_info_safe, + RUN_ON_CPU_HOST_PTR(tbdi)); +} + +struct tb_dump_info { + int id; + Monitor *mon; +}; + +static void do_dump_tb_info_safe(CPUState *cpu, run_on_cpu_data info) +{ + struct tb_dump_info *tbdi = info.host_ptr; + int id = tbdi->id; + Monitor *mon = tbdi->mon; + g_autoptr(GString) buf = g_string_new(""); + + TBStatistics *tbs = get_tbstats_by_id(id); + if (tbs == NULL) { + monitor_printf(mon, "TB %d information is not recorded\n", id); + return; + } + + monitor_printf(mon, "\n------------------------------\n\n"); + + int valid_tb_num = dump_tb_info(buf, tbs, id); + monitor_printf(mon, "%s", buf->str); + + if (valid_tb_num > 0) { + for (int i = tbs->tbs->len - 1; i >= 0; --i) { + TranslationBlock *tb = g_ptr_array_index(tbs->tbs, i); + if (!(tb->cflags & CF_INVALID)) { + monitor_disas(mon, mon_get_cpu(mon), tbs->phys_pc, tb->icount, + DISAS_GRA); + break; + } + } + } + monitor_printf(mon, "\n------------------------------\n\n"); + + g_free(tbdi); +} + +void hmp_info_tb(Monitor *mon, const QDict *qdict) +{ + const int id = qdict_get_int(qdict, "id"); + + if (!tcg_enabled()) { + monitor_printf(mon, "Only available with accel=tcg\n"); + return; + } + + struct tb_dump_info *tbdi = g_new(struct tb_dump_info, 1); + tbdi->id = id; + tbdi->mon = mon; + async_safe_run_on_cpu(first_cpu, do_dump_tb_info_safe, + RUN_ON_CPU_HOST_PTR(tbdi)); +} + static void hmp_tcg_register(void) { monitor_register_hmp_info_hrt("jit", qmp_x_query_jit); diff --git a/accel/tcg/tb-stats.c b/accel/tcg/tb-stats.c index 7c7f700c89..73f0a25781 100644 --- a/accel/tcg/tb-stats.c +++ b/accel/tcg/tb-stats.c @@ -11,12 +11,16 @@ #include "disas/disas.h" #include "exec/exec-all.h" #include "tcg/tcg.h" +#include "qapi/error.h" #include "qemu/qemu-print.h" +#include "qemu/log.h" #include "exec/tb-stats.h" #include "tb-context.h" +#include "internal.h" + /* TBStatistic collection controls */ enum TBStatsStatus { TB_STATS_STOPPED = 0, @@ -26,6 +30,8 @@ enum TBStatsStatus { static enum TBStatsStatus tcg_collect_tb_stats; static uint32_t tbstats_flag; +static GPtrArray *last_search; + struct jit_profile_info { uint64_t translations; uint64_t aborted; @@ -42,6 +48,18 @@ struct jit_profile_info { #define stat_per_translation(stat, name) \ (stat->translations.total ? stat->name / stat->translations.total : 0) +TBStatistics *get_tbstats_by_id(int id) +{ + if (!last_search) { + return NULL; + } + + if (id < 0 || id >= last_search->len) { + return NULL; + } + return g_ptr_array_index(last_search, id); +} + /* accumulate the statistics from all TBs */ static void collect_jit_profile_info(void *p, uint32_t hash, void *userp) { @@ -98,6 +116,11 @@ static void free_tbstats(void *p, uint32_t hash, void *userp) void clean_tbstats(void) { + if (last_search) { + g_ptr_array_free(last_search, true); + last_search = NULL; + } + /* remove all tb_stats */ qht_iter(&tb_ctx.tb_stats, free_tbstats, NULL); qht_destroy(&tb_ctx.tb_stats); @@ -129,6 +152,154 @@ void init_tb_stats_htable(void) } } +static void collect_tb_stats(void *p, uint32_t hash, void *userp) +{ + int *count = userp; + + g_ptr_array_add(last_search, p); + (*count)++; +} + +static void count_invalid_tbs(gpointer data, gpointer user_data) +{ + TranslationBlock *tb = (TranslationBlock *) data; + unsigned *counter = (unsigned *) user_data; + if (tb->cflags & CF_INVALID) { + *counter = *counter + 1; + } +} + +int dump_tb_info(GString *buf, TBStatistics *tbs, int id) +{ + unsigned g = stat_per_translation(tbs, code.num_guest_inst); + unsigned ops = stat_per_translation(tbs, code.num_tcg_ops); + unsigned ops_opt = stat_per_translation(tbs, code.num_tcg_ops_opt); + unsigned spills = stat_per_translation(tbs, code.spills); + unsigned h = stat_per_translation(tbs, code.out_len); + unsigned act = tbs->tbs->len; + unsigned invalid = 0; + + float guest_host_prop = g ? ((float) h / g) : 0; + + g_ptr_array_foreach(tbs->tbs, &count_invalid_tbs, &invalid); + + g_string_append_printf(buf, + "TB id:%d | phys:0x"TB_PAGE_ADDR_FMT" virt:0x"TARGET_FMT_lx + " flags:0x%08x %d inv/%d\n", + id, tbs->phys_pc, tbs->pc, tbs->flags, invalid, act); + + if (tbs_stats_enabled(tbs, TB_EXEC_STATS)) { + g_string_append_printf(buf, + "\t| exec:%lu/%lu guest inst cov:%.2f%%\n", + tbs->executions.normal, + tbs->executions.atomic, tbs->executions.coverage / 100.0f); + } + + if (tbs_stats_enabled(tbs, TB_JIT_STATS)) { + g_string_append_printf(buf, + "\t| trans:%lu inst: g:%u op:%u op_opt:%u spills:%d" + "\n\t| h/g (host bytes / guest insts): %f\n", + tbs->translations.total, g, ops, ops_opt, spills, + guest_host_prop); + } + + g_string_append_printf(buf, "\n"); + + return act - invalid; +} + +static gint +inverse_sort_tbs_spills(gconstpointer p1, gconstpointer p2) +{ + const TBStatistics *tbs1 = *(TBStatistics **) p1; + const TBStatistics *tbs2 = *(TBStatistics **) p2; + unsigned long c1 = stat_per_translation(tbs1, code.spills); + unsigned long c2 = stat_per_translation(tbs2, code.spills); + return c1 < c2 ? 1 : c1 == c2 ? 0 : -1; +} + +static gint +inverse_sort_tbs_hotness(gconstpointer p1, gconstpointer p2) +{ + const TBStatistics *tbs1 = *(TBStatistics **) p1; + const TBStatistics *tbs2 = *(TBStatistics **) p2; + unsigned long c1 = stat_per_translation(tbs1, executions.normal); + unsigned long c2 = stat_per_translation(tbs2, executions.normal); + return c1 < c2 ? 1 : c1 == c2 ? 0 : -1; +} + +static gint +inverse_sort_tbs_hg(gconstpointer p1, gconstpointer p2) +{ + const TBStatistics *tbs1 = *(TBStatistics **) p1; + const TBStatistics *tbs2 = *(TBStatistics **) p2; + + if (tbs1->code.num_guest_inst == 0) { + return -1; + } + if (tbs2->code.num_guest_inst == 0) { + return 1; + } + + unsigned long c1 = tbs1->code.out_len / tbs1->code.num_guest_inst; + unsigned long c2 = tbs2->code.out_len / tbs2->code.num_guest_inst; + return c1 < c2 ? 1 : c1 == c2 ? 0 : -1; +} + +static void calculate_last_search_coverages(void) +{ + uint64_t total_exec_count = 0; + + /* Compute total execution count for all tbs */ + for (int i = 0; i < last_search->len; ++i) { + TBStatistics *tbs = g_ptr_array_index(last_search, i); + total_exec_count += + (tbs->executions.atomic + tbs->executions.normal) + * stat_per_translation(tbs, code.num_guest_inst); + } + + for (int i = 0; i < last_search->len; ++i) { + TBStatistics *tbs = g_ptr_array_index(last_search, i); + uint64_t tb_total_execs = + (tbs->executions.atomic + tbs->executions.normal) + * stat_per_translation(tbs, code.num_guest_inst); + tbs->executions.coverage = + (10000 * tb_total_execs) / (total_exec_count + 1); + } +} + +void dump_tblist_info(GString *buf, int total, int sort_by) +{ + int array_size = 0; + + if (last_search) { + g_ptr_array_free(last_search, true); + } + last_search = g_ptr_array_new(); + + qht_iter(&tb_ctx.tb_stats, collect_tb_stats, &array_size); + + calculate_last_search_coverages(); + + if (sort_by == SORT_BY_HOTNESS) { + g_ptr_array_sort(last_search, (GCompareFunc)inverse_sort_tbs_hotness); + } else if (sort_by == SORT_BY_SPILLS) { + g_ptr_array_sort(last_search, (GCompareFunc)inverse_sort_tbs_spills); + } else if (sort_by == SORT_BY_HG) { + g_ptr_array_sort(last_search, (GCompareFunc)inverse_sort_tbs_hg); + } else { + return; + } + + array_size = (array_size > total) ? total : array_size; + g_ptr_array_set_size(last_search, array_size); + + for (int i = 0; i < last_search->len; ++i) { + TBStatistics *tbs = g_ptr_array_index(last_search, i); + dump_tb_info(buf, tbs, i); + } +} + void enable_collect_tb_stats(void) { tcg_collect_tb_stats = TB_STATS_RUNNING; @@ -166,3 +337,9 @@ bool tb_stats_enabled(TranslationBlock *tb, uint32_t flag) tb->tb_stats && (tbstats_flag & flag); } + +bool tbs_stats_enabled(struct TBStatistics *tbs, uint32_t flag) +{ + return tb_stats_collection_enabled() && + (tbstats_flag & flag); +} diff --git a/disas/disas-mon.c b/disas/disas-mon.c index 48ac492c6c..e4125b4493 100644 --- a/disas/disas-mon.c +++ b/disas/disas-mon.c @@ -23,9 +23,18 @@ physical_read_memory(bfd_vma memaddr, bfd_byte *myaddr, int length, return res == MEMTX_OK ? 0 : EIO; } +static int +ram_addr_read_memory(bfd_vma memaddr, bfd_byte *myaddr, int length, + struct disassemble_info *info) +{ + void *p = qemu_map_ram_ptr(0, memaddr); + memcpy(myaddr, p, length); + return 0; +} + /* Disassembler for the monitor. */ void monitor_disas(Monitor *mon, CPUState *cpu, uint64_t pc, - int nb_insn, bool is_physical) + int nb_insn, int addr_kind) { int count, i; CPUDebug s; @@ -35,8 +44,10 @@ void monitor_disas(Monitor *mon, CPUState *cpu, uint64_t pc, s.info.fprintf_func = disas_gstring_printf; s.info.stream = (FILE *)ds; /* abuse this slot */ - if (is_physical) { + if (addr_kind == DISAS_GPA) { s.info.read_memory_func = physical_read_memory; + } else if (addr_kind == DISAS_GRA) { + s.info.read_memory_func = ram_addr_read_memory; } s.info.buffer_vma = pc; diff --git a/disas/disas.c b/disas/disas.c index 0d2d06c2ec..3b19b51fca 100644 --- a/disas/disas.c +++ b/disas/disas.c @@ -8,6 +8,8 @@ #include "hw/core/cpu.h" #include "exec/memory.h" +#include "qemu/log-for-trace.h" + /* Filled in by elfload.c. Simplistic, but will do for now. */ struct syminfo *syminfos = NULL; diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx index f5b37eb74a..ace1ecc3c5 100644 --- a/hmp-commands-info.hx +++ b/hmp-commands-info.hx @@ -262,6 +262,22 @@ ERST .params = "", .help = "show dynamic compiler info", }, + { + .name = "tb-list", + .args_type = "number:i?,sortedby:s?", + .params = "[number sortedby]", + .help = "show a [number] translated blocks sorted by [sortedby]" + "sortedby opts: hotness hg spills", + .cmd = hmp_info_tblist, + }, + { + .name = "tb", + .args_type = "id:i,flags:s?", + .params = "id [flag1,flag2,...]", + .help = "show information about one translated block by id." + "dump flags can be used to set dump code level: out_asm in_asm op", + .cmd = hmp_info_tb, + }, #endif SRST diff --git a/include/disas/disas.h b/include/disas/disas.h index 176775eff7..95854abca2 100644 --- a/include/disas/disas.h +++ b/include/disas/disas.h @@ -1,12 +1,18 @@ #ifndef QEMU_DISAS_H #define QEMU_DISAS_H +enum { + DISAS_GVA = 0, + DISAS_GPA, + DISAS_GRA, /* guest ram addr */ +}; + /* Disassemble this for me please... (debugging). */ void disas(FILE *out, const void *code, size_t size); void target_disas(FILE *out, CPUState *cpu, uint64_t code, size_t size); void monitor_disas(Monitor *mon, CPUState *cpu, uint64_t pc, - int nb_insn, bool is_physical); + int nb_insn, int addr_kind); char *plugin_disas(CPUState *cpu, uint64_t addr, size_t size); diff --git a/include/exec/tb-stats.h b/include/exec/tb-stats.h index cef177bc69..0caffdf32b 100644 --- a/include/exec/tb-stats.h +++ b/include/exec/tb-stats.h @@ -54,6 +54,8 @@ struct TBStatistics { struct { unsigned long normal; unsigned long atomic; + /* filled only when dumping x% cover set */ + uint16_t coverage; } executions; /* JIT Stats - protected by lock */ @@ -89,6 +91,7 @@ bool tb_stats_cmp(const void *ap, const void *bp); void init_tb_stats_htable(void); bool tb_stats_enabled(TranslationBlock *tb, uint32_t flag); +bool tbs_stats_enabled(struct TBStatistics *tbs, uint32_t flag); void dump_jit_profile_info(GString *buf); @@ -102,4 +105,26 @@ void clean_tbstats(void); */ void tbstats_reset_tbs(void); +/** + * dump_tbs_info: report the hottest blocks + * + * @buf: output buffer + * @total: the limit of hotblocks + * @sort_by: property in which the dump will be sorted + * + * Report the hottest blocks to either the log or monitor + */ +void dump_tblist_info(GString *buf, int total, int sort_by); + +/** + * dump_tb_info: dump information about one TB + * + * @buf: output buffer + * @tbs: the tbs to dump + * @id: the display id of the block (from previous search) + */ +int dump_tb_info(GString *buf, TBStatistics *tbs, int id); + +TBStatistics *get_tbstats_by_id(int id); + #endif diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h index 2e7f141754..acdd6f1561 100644 --- a/include/monitor/hmp.h +++ b/include/monitor/hmp.h @@ -182,5 +182,7 @@ void hmp_boot_set(Monitor *mon, const QDict *qdict); void hmp_info_mtree(Monitor *mon, const QDict *qdict); void hmp_info_cryptodev(Monitor *mon, const QDict *qdict); void hmp_tbstats(Monitor *mon, const QDict *qdict); +void hmp_info_tblist(Monitor *mon, const QDict *qdict); +void hmp_info_tb(Monitor *mon, const QDict *qdict); #endif diff --git a/monitor/hmp-cmds-target.c b/monitor/hmp-cmds-target.c index 0d3e84d960..7d1433336d 100644 --- a/monitor/hmp-cmds-target.c +++ b/monitor/hmp-cmds-target.c @@ -133,7 +133,8 @@ static void memory_dump(Monitor *mon, int count, int format, int wsize, } if (format == 'i') { - monitor_disas(mon, cs, addr, count, is_physical); + monitor_disas(mon, cs, addr, count, + is_physical ? DISAS_GPA : DISAS_GVA); return; } -- 2.25.1