@@ -8,17 +8,21 @@
#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 "exec/tb-stats.h"
#include "exec/tb-flush.h"
+#include "disas/disas.h"
+#include "tb-context.h"
#include "internal.h"
#include "tb-context.h"
@@ -186,6 +190,71 @@ void hmp_tbstats(Monitor *mon, const QDict *qdict)
}
+void hmp_info_tblist(Monitor *mon, const QDict *qdict)
+{
+ int number_int;
+ const char *sortedby_str = NULL;
+ g_autoptr(GString) buf = g_string_new("");
+
+ 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;
+ }
+
+ dump_tblist_info(buf, number_int, sortedby);
+ monitor_printf(mon, "%s", buf->str);
+}
+
+void hmp_info_tb(Monitor *mon, const QDict *qdict)
+{
+ const int id = qdict_get_int(qdict, "id");
+ g_autoptr(GString) buf = g_string_new("");
+
+ if (!tcg_enabled()) {
+ monitor_printf(mon, "Only available with accel=tcg\n");
+ return;
+ }
+
+ 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) {
+ unsigned num_inst = tbs->code.num_guest_inst / tbs->translations.total;
+
+ monitor_printf(mon, "\n----------------n\n");
+ // FIXME: cannot disas
+ monitor_disas(mon, mon_get_cpu(mon), tbs->phys_pc, num_inst, true);
+ monitor_printf(mon, "\n------------------------------\n\n");
+ }
+}
+
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,10 @@ 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);
+ }
+
/* remove all tb_stats */
qht_iter(&tb_ctx.tb_stats, free_tbstats, NULL);
qht_destroy(&tb_ctx.tb_stats);
@@ -129,6 +151,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 ints: 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 +336,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);
+}
@@ -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
@@ -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