@@ -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);
@@ -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);
+}
@@ -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;
@@ -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;
@@ -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
@@ -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);
@@ -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
@@ -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
@@ -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;
}