Skip to content

Commit 0e51c9c

Browse files
mgcaoacrnsi-robot
authored andcommitted
hv: shell: add cmd to sample vmexit data per-vCPU
this feature is used to sample vmexit data per virtual CPU of VM, command used in HV console as following: 1. vmexit clear : to clear current vmexit buffer 2. vmexit [vm_id] : output vmexit info per-vCPU of one or all VMs 3. vmexit enable | disable, by default enabled Tracked-On: #5232 Signed-off-by: Minggui Cao <[email protected]>
1 parent 77cf205 commit 0e51c9c

File tree

8 files changed

+353
-2
lines changed

8 files changed

+353
-2
lines changed

hypervisor/arch/x86/guest/vmexit.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
* According to "SDM APPENDIX C VMX BASIC EXIT REASONS",
2929
* there are 65 Basic Exit Reasons.
3030
*/
31-
#define NR_VMX_EXIT_REASONS 70U
3231

3332
static int32_t triple_fault_vmexit_handler(struct acrn_vcpu *vcpu);
3433
static int32_t unhandled_vmexit_handler(struct acrn_vcpu *vcpu);

hypervisor/common/hv_main.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ void vcpu_thread(struct thread_object *obj)
4545
profiling_vmenter_handler(vcpu);
4646

4747
TRACE_2L(TRACE_VM_ENTER, 0UL, 0UL);
48+
sample_vmexit_end(vcpu);
49+
4850
ret = run_vcpu(vcpu);
4951
if (ret != 0) {
5052
pr_fatal("vcpu resume failed");
@@ -55,6 +57,7 @@ void vcpu_thread(struct thread_object *obj)
5557
continue;
5658
}
5759
TRACE_2L(TRACE_VM_EXIT, vcpu->arch.exit_reason, vcpu_get_rip(vcpu));
60+
sample_vmexit_begin(vcpu);
5861

5962
profiling_pre_vmexit_handler(vcpu);
6063

hypervisor/debug/shell.c

Lines changed: 323 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@
2222
#include <shell.h>
2323
#include <asm/guest/vmcs.h>
2424
#include <asm/host_pm.h>
25+
#include <asm/tsc.h>
2526

2627
#define TEMP_STR_SIZE 60U
2728
#define MAX_STR_SIZE 256U
2829
#define SHELL_PROMPT_STR "ACRN:\\>"
2930

30-
#define SHELL_LOG_BUF_SIZE (PAGE_SIZE * MAX_PCPU_NUM / 2U)
31+
#define SHELL_LOG_BUF_SIZE (PAGE_SIZE * MAX_PCPU_NUM)
3132
static char shell_log_buf[SHELL_LOG_BUF_SIZE];
3233

3334
/* Input Line Other - Switch to the "other" input line (there are only two
@@ -52,6 +53,7 @@ static int32_t shell_cpuid(int32_t argc, char **argv);
5253
static int32_t shell_reboot(int32_t argc, char **argv);
5354
static int32_t shell_rdmsr(int32_t argc, char **argv);
5455
static int32_t shell_wrmsr(int32_t argc, char **argv);
56+
static int32_t shell_show_vmexit_profile(__unused int argc, __unused char **argv);
5557

5658
static struct shell_cmd shell_cmds[] = {
5759
{
@@ -156,6 +158,12 @@ static struct shell_cmd shell_cmds[] = {
156158
.help_str = SHELL_CMD_WRMSR_HELP,
157159
.fcn = shell_wrmsr,
158160
},
161+
{
162+
.str = SHELL_CMD_VMEXIT,
163+
.cmd_param = SHELL_CMD_VMEXIT_PARAM,
164+
.help_str = SHELL_CMD_VMEXIT_HELP,
165+
.fcn = shell_show_vmexit_profile,
166+
},
159167
};
160168

161169
/* The initial log level*/
@@ -1447,3 +1455,317 @@ static int32_t shell_wrmsr(int32_t argc, char **argv)
14471455

14481456
return ret;
14491457
}
1458+
static const char *level_info[MAX_VMEXIT_LEVEL] = {
1459+
" 0us - 2us",
1460+
" 2us - 4us",
1461+
" 4us - 8us",
1462+
" 8us - 16us",
1463+
" 16us - 32us",
1464+
" 32us - 64us",
1465+
" 64us - 128us",
1466+
" 128us - 256us",
1467+
" 256us - 512us",
1468+
" 512us -1024us",
1469+
"1024us -2048us",
1470+
"2048us -4096us",
1471+
"4096us -8192us",
1472+
"8192us - more",
1473+
};
1474+
1475+
/* if target_vm_id valid, just check this vm or check all VMs; if count is 0, just ignore it */
1476+
static bool check_vmexit_count_per_reason(uint16_t target_vm_id, uint16_t reason, uint16_t level)
1477+
{
1478+
bool has_vmexit = false;
1479+
uint16_t vm_id, vcpu_id;
1480+
struct acrn_vm *vm;
1481+
struct acrn_vcpu *vcpu;
1482+
1483+
if (target_vm_id != CONFIG_MAX_VM_NUM) {
1484+
vm = get_vm_from_vmid(target_vm_id);
1485+
foreach_vcpu(vcpu_id, vm, vcpu) {
1486+
if (vcpu->vmexit_cnt[reason][level] != 0) {
1487+
has_vmexit = true;
1488+
break;
1489+
}
1490+
}
1491+
} else {
1492+
for (vm_id = 0U; vm_id < CONFIG_MAX_VM_NUM; vm_id++) {
1493+
vm = get_vm_from_vmid(vm_id);
1494+
if (is_poweroff_vm(vm))
1495+
continue;
1496+
1497+
foreach_vcpu(vcpu_id, vm, vcpu) {
1498+
if (vcpu->vmexit_cnt[reason][level] != 0) {
1499+
has_vmexit = true;
1500+
break;
1501+
}
1502+
}
1503+
1504+
if (has_vmexit)
1505+
break;
1506+
}
1507+
}
1508+
1509+
return has_vmexit;
1510+
}
1511+
1512+
/* if target_vm_id valid, just output this vm or output all VMs */
1513+
static bool output_id_per_vm_vcpu(char **str_arg, size_t *size_max, uint16_t target_vm_id)
1514+
{
1515+
char *str = *str_arg;
1516+
size_t len, size = *size_max;
1517+
uint16_t vm_id, vcpu_id;
1518+
struct acrn_vm *vm;
1519+
struct acrn_vcpu *vcpu;
1520+
1521+
if (target_vm_id != CONFIG_MAX_VM_NUM) {
1522+
vm = get_vm_from_vmid(target_vm_id);
1523+
foreach_vcpu(vcpu_id, vm, vcpu) {
1524+
len = snprintf(str, size, " VM%u/vCPU%u", target_vm_id, vcpu_id);
1525+
if (len >= size) {
1526+
return false;
1527+
}
1528+
size -= len;
1529+
str += len;
1530+
}
1531+
} else {
1532+
for (vm_id = 0U; vm_id < CONFIG_MAX_VM_NUM; vm_id++) {
1533+
vm = get_vm_from_vmid(vm_id);
1534+
if (is_poweroff_vm(vm))
1535+
continue;
1536+
1537+
foreach_vcpu(vcpu_id, vm, vcpu) {
1538+
len = snprintf(str, size, " VM%u/vCPU%u", vm_id, vcpu_id);
1539+
if (len >= size) {
1540+
return false;
1541+
}
1542+
size -= len;
1543+
str += len;
1544+
}
1545+
}
1546+
}
1547+
1548+
*str_arg = str;
1549+
*size_max = size;
1550+
return true;
1551+
}
1552+
1553+
static bool output_vmexit_info_per_vcpu(char **str_arg, size_t *size_max, uint16_t target_vm_id,
1554+
uint16_t reason, uint16_t level)
1555+
{
1556+
char *str = *str_arg;
1557+
size_t len, size = *size_max;
1558+
uint16_t vm_id, vcpu_id;
1559+
struct acrn_vm *vm;
1560+
struct acrn_vcpu *vcpu;
1561+
1562+
if (target_vm_id != CONFIG_MAX_VM_NUM) {
1563+
vm = get_vm_from_vmid(target_vm_id);
1564+
1565+
foreach_vcpu(vcpu_id, vm, vcpu) {
1566+
/* if level is MAX, output the max time */
1567+
if (level == MAX_VMEXIT_LEVEL) {
1568+
len = snprintf(str, size, "%12lld", vcpu->vmexit_time[reason]);
1569+
} else {
1570+
len = snprintf(str, size, "%12lld", vcpu->vmexit_cnt[reason][level]);
1571+
}
1572+
1573+
if (len >= size) {
1574+
return false;
1575+
}
1576+
1577+
size -= len;
1578+
str += len;
1579+
}
1580+
} else {
1581+
for (vm_id = 0U; vm_id < CONFIG_MAX_VM_NUM; vm_id++) {
1582+
vm = get_vm_from_vmid(vm_id);
1583+
if (is_poweroff_vm(vm))
1584+
continue;
1585+
1586+
foreach_vcpu(vcpu_id, vm, vcpu) {
1587+
/* if level is MAX, output the max time */
1588+
if (level == MAX_VMEXIT_LEVEL) {
1589+
len = snprintf(str, size, "%12lld", vcpu->vmexit_time[reason]);
1590+
} else {
1591+
len = snprintf(str, size, "%12lld", vcpu->vmexit_cnt[reason][level]);
1592+
}
1593+
1594+
if (len >= size) {
1595+
return false;
1596+
}
1597+
1598+
size -= len;
1599+
str += len;
1600+
}
1601+
}
1602+
}
1603+
1604+
*str_arg = str;
1605+
*size_max = size;
1606+
return true;
1607+
}
1608+
1609+
static void get_vmexit_details_per_vcpu(char *str_arg, size_t str_max, uint16_t target_vm_id)
1610+
{
1611+
char *str = str_arg;
1612+
uint16_t reason, level;
1613+
size_t len, size = str_max;
1614+
1615+
len = snprintf(str, size, "\r\nNow=%lldus, for detailed latency of each vmexit on %s VM/vCPU:",
1616+
ticks_to_us(rdtsc()), (target_vm_id == CONFIG_MAX_VM_NUM) ? "each" : "one");
1617+
if (len >= size) {
1618+
goto overflow;
1619+
}
1620+
size -= len;
1621+
str += len;
1622+
1623+
for (reason = 0U; reason < NR_VMX_EXIT_REASONS; reason++) {
1624+
1625+
if (!check_vmexit_count_per_reason(target_vm_id, reason, TOTAL_ARRAY_LEVEL - 1))
1626+
continue;
1627+
1628+
len = snprintf(str, size, "\r\n\r\n VMEXIT/0x%02x", reason);
1629+
if (len >= size) {
1630+
goto overflow;
1631+
}
1632+
size -= len;
1633+
str += len;
1634+
1635+
if (!output_id_per_vm_vcpu(&str, &size, target_vm_id)) {
1636+
goto overflow;
1637+
}
1638+
1639+
for (level = 0; level < MAX_VMEXIT_LEVEL; level++) {
1640+
1641+
if (!check_vmexit_count_per_reason(target_vm_id, reason, level)) {
1642+
continue;
1643+
}
1644+
1645+
len = snprintf(str, size, "\r\n%s", level_info[level]);
1646+
if (len >= size) {
1647+
goto overflow;
1648+
}
1649+
size -= len;
1650+
str += len;
1651+
1652+
if (!output_vmexit_info_per_vcpu(&str, &size, target_vm_id, reason, level)) {
1653+
goto overflow;
1654+
}
1655+
}
1656+
1657+
len = snprintf(str, size, "\r\n Max Lat(us):");
1658+
if (len >= size) {
1659+
goto overflow;
1660+
}
1661+
size -= len;
1662+
str += len;
1663+
1664+
if (!output_vmexit_info_per_vcpu(&str, &size, target_vm_id, reason, MAX_VMEXIT_LEVEL)) {
1665+
goto overflow;
1666+
}
1667+
}
1668+
1669+
snprintf(str, size, "\r\n");
1670+
return;
1671+
1672+
overflow:
1673+
printf("buffer size could not be enough! please check!\n");
1674+
}
1675+
1676+
static void clear_vmexit_info_buffer(void)
1677+
{
1678+
uint16_t vm_id, vcpu_id;
1679+
struct acrn_vm *vm;
1680+
struct acrn_vcpu *vcpu;
1681+
1682+
for (vm_id = 0U; vm_id < CONFIG_MAX_VM_NUM; vm_id++) {
1683+
vm = get_vm_from_vmid(vm_id);
1684+
if (!is_poweroff_vm(vm)) {
1685+
foreach_vcpu(vcpu_id, vm, vcpu) {
1686+
memset(vcpu->vmexit_cnt, 0, sizeof(vcpu->vmexit_cnt));
1687+
memset(vcpu->vmexit_time, 0, sizeof(vcpu->vmexit_time));
1688+
}
1689+
}
1690+
}
1691+
}
1692+
1693+
/* used to control vmexit sample function */
1694+
static bool sample_vmexit_enabled = true;
1695+
1696+
static int shell_show_vmexit_profile(__unused int argc, __unused char **argv)
1697+
{
1698+
uint16_t vm_id = CONFIG_MAX_VM_NUM;
1699+
bool to_continue = true;
1700+
1701+
if (argc == 2) {
1702+
if (strcmp(argv[1], "clear") == 0) {
1703+
clear_vmexit_info_buffer();
1704+
} else if (strcmp(argv[1], "enable") == 0) {
1705+
shell_puts("\tenable vmexit sample!\n");
1706+
sample_vmexit_enabled = true;
1707+
to_continue = false;
1708+
} else if (strcmp(argv[1], "disable") == 0) {
1709+
shell_puts("\tdisable vmexit sample!\n");
1710+
sample_vmexit_enabled = false;
1711+
to_continue = false;
1712+
} else {
1713+
vm_id = (uint16_t)strtol_deci(argv[1]);
1714+
if ((vm_id >= CONFIG_MAX_VM_NUM) || is_poweroff_vm(get_vm_from_vmid(vm_id))) {
1715+
shell_puts("\tThis vm_id not existed or active! please check!\n");
1716+
to_continue = false;
1717+
}
1718+
}
1719+
}
1720+
1721+
if (to_continue && !sample_vmexit_enabled) {
1722+
shell_puts("\tvmexit sample is disabled!\n");
1723+
to_continue = false;
1724+
}
1725+
1726+
if (to_continue) {
1727+
get_vmexit_details_per_vcpu(shell_log_buf, SHELL_LOG_BUF_SIZE, vm_id);
1728+
shell_puts(shell_log_buf);
1729+
}
1730+
1731+
return 0;
1732+
}
1733+
1734+
/* to sample vmexit tsc data */
1735+
void sample_vmexit_end(struct acrn_vcpu *vcpu)
1736+
{
1737+
uint32_t basic_exit_reason = vcpu->arch.exit_reason & 0xFFFFUL;
1738+
1739+
if (!sample_vmexit_enabled)
1740+
return;
1741+
1742+
if (vcpu->vmexit_begin != 0UL) {
1743+
uint64_t delta = rdtsc() - vcpu->vmexit_begin;
1744+
uint32_t us = (uint32_t)ticks_to_us(delta);
1745+
uint16_t fls = (uint16_t)(fls32(us) + 1); /* to avoid us = 0 case, then fls=0xFFFF */
1746+
uint16_t index = 0;
1747+
1748+
if (fls >= MAX_VMEXIT_LEVEL) {
1749+
index = MAX_VMEXIT_LEVEL - 1;
1750+
} else if (fls > 0) { /* if fls == 0, it means the us = 0 */
1751+
index = fls - 1;
1752+
}
1753+
1754+
vcpu->vmexit_cnt[basic_exit_reason][index]++;
1755+
1756+
if (us > vcpu->vmexit_time[basic_exit_reason]) {
1757+
vcpu->vmexit_time[basic_exit_reason] = us;
1758+
}
1759+
}
1760+
}
1761+
1762+
void sample_vmexit_begin(struct acrn_vcpu *vcpu)
1763+
{
1764+
uint32_t basic_exit_reason = vcpu->arch.exit_reason & 0xFFFFUL;
1765+
1766+
if (!sample_vmexit_enabled)
1767+
return;
1768+
1769+
vcpu->vmexit_begin = rdtsc();
1770+
vcpu->vmexit_cnt[basic_exit_reason][TOTAL_ARRAY_LEVEL - 1]++;
1771+
}

hypervisor/debug/shell_priv.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,8 @@ struct shell {
106106
#define SHELL_CMD_WRMSR_PARAM "[-p<pcpu_id>] <msr_index> <value>"
107107
#define SHELL_CMD_WRMSR_HELP "Write value (in hexadecimal) to the MSR at msr_index (in hexadecimal) for CPU"\
108108
" ID pcpu_id"
109+
#define SHELL_CMD_VMEXIT "vmexit"
110+
#define SHELL_CMD_VMEXIT_PARAM NULL
111+
#define SHELL_CMD_VMEXIT_HELP "profiling vmexit, use: vmexit [clear | enable | disable | vm_id] enabled by default"
112+
109113
#endif /* SHELL_PRIV_H */

0 commit comments

Comments
 (0)