mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 02:01:24 +01:00
tools/xentop: Add physical CPU statistics support
Introduce a new '-p/--pcpus' flag to display physical CPU utilization metrics using xc_interface. This provides hypervisor-level CPU usage insights alongside existing domain statistics. This helps correlate VM resource usage with host CPU load patterns. Changes: - Add pcpu.c/pcpu.h for PCPU stat collection logic - Link against libxenctrl for xc_handle access - Extend CLI with '-p' flag and output formatting - Forward declare xenstat_handle to access xc_handle - Include cleanup for PCPU resources in exit handler Example usage: xentop -p # Shows physical CPU stats xentop -bp # Batch mode xentop -d 1 -p # With different delays Example output with '-p': NAME STATE CPU(%) MEM(%) VCPUS ... Domain-0 -----r 17.0 6.3 8 DomD -----r 767.0 38.1 8 Physical CPU Usage: ┌───────┬────────┐ │ Core │ Usage │ ├───────┼────────┤ │ 0 │ 98.1% │ │ ... │ ... │ │ 7 │ 97.3% │ └───────┴────────┘ Signed-off-by: Jahan Murudi <jahan.murudi.zg@renesas.com> Reviewed-by: Anthony PERARD <anthony.perard@vates.tech>
This commit is contained in:
1 parent
ccb11468b6
commit
45bb8868ef
4 files changed
+222
-20
No files matched your search
@@ -15,6 +15,7 @@ include $(XEN_ROOT)/tools/Rules.mk
|
||||
|
||||
CFLAGS += -DGCC_PRINTF $(CFLAGS_libxenstat)
|
||||
LDLIBS += $(LDLIBS_libxenstat) $(CURSES_LIBS) $(TINFO_LIBS) $(SOCKET_LIBS) -lm
|
||||
LDLIBS += $(LDLIBS_libxenctrl)
|
||||
CFLAGS += -DHOST_$(XEN_OS)
|
||||
|
||||
# Include configure output (config.h)
|
||||
@@ -25,8 +26,8 @@ TARGETS := xentop
|
||||
.PHONY: all
|
||||
all: $(TARGETS)
|
||||
|
||||
xentop: xentop.o
|
||||
$(CC) $(LDFLAGS) -o $@ $< $(LDLIBS) $(APPEND_LDFLAGS)
|
||||
xentop: xentop.o pcpu.o
|
||||
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) $(APPEND_LDFLAGS)
|
||||
|
||||
.PHONY: install
|
||||
install: all
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2025 Renesas Electronics Corporation
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/time.h>
|
||||
#include <xenctrl.h>
|
||||
#include "pcpu.h"
|
||||
|
||||
#define MAX_PCPUS 128
|
||||
|
||||
typedef struct {
|
||||
float usage_pct;
|
||||
} pcpu_stat_t;
|
||||
|
||||
static pcpu_stat_t *pcpu_stats = NULL;
|
||||
static uint64_t *prev_idle = NULL;
|
||||
static int allocated_pcpus = 0;
|
||||
static xc_interface *xc_handle = NULL;
|
||||
static uint64_t prev_global_time = 0;
|
||||
|
||||
static void report_pcpu_error(const char *context)
|
||||
{
|
||||
fprintf(stderr, "PCPU error: %s (%s)\n", context, strerror(errno));
|
||||
}
|
||||
|
||||
int update_pcpu_stats(const struct timeval *now, unsigned int delay_sec)
|
||||
{
|
||||
struct xen_sysctl_cpuinfo info[MAX_PCPUS];
|
||||
int detected_cpus = 0;
|
||||
int ret, i;
|
||||
uint64_t current_time = (uint64_t)now->tv_sec * 1000000ULL + now->tv_usec;
|
||||
uint64_t time_diff;
|
||||
|
||||
if (!xc_handle) {
|
||||
xc_handle = xc_interface_open(NULL, NULL, 0);
|
||||
if (!xc_handle) {
|
||||
report_pcpu_error("xc_interface_open failed");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ret = xc_getcpuinfo(xc_handle, MAX_PCPUS, info, &detected_cpus);
|
||||
if (ret < 0) {
|
||||
report_pcpu_error("xc_getcpuinfo failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Allocate/reallocate memory if needed */
|
||||
if (!pcpu_stats || detected_cpus > allocated_pcpus) {
|
||||
pcpu_stat_t *new_stats = realloc(pcpu_stats,
|
||||
detected_cpus * sizeof(*pcpu_stats));
|
||||
if (!new_stats) goto alloc_error;
|
||||
|
||||
pcpu_stats = new_stats;
|
||||
|
||||
uint64_t *new_prev_idle = realloc(prev_idle,
|
||||
detected_cpus * sizeof(*prev_idle));
|
||||
if (!new_prev_idle) goto alloc_error;
|
||||
|
||||
prev_idle = new_prev_idle;
|
||||
allocated_pcpus = detected_cpus;
|
||||
|
||||
/* Initialize new entries */
|
||||
for (i = 0; i < detected_cpus; i++) {
|
||||
prev_idle[i] = info[i].idletime / 1000; /* ns->us */
|
||||
pcpu_stats[i].usage_pct = 0.0;
|
||||
}
|
||||
|
||||
/* Set initial global time reference */
|
||||
prev_global_time = current_time;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Calculate time difference since last update */
|
||||
time_diff = current_time - prev_global_time;
|
||||
|
||||
/* Calculate CPU usage for each core */
|
||||
for (i = 0; i < detected_cpus; i++) {
|
||||
uint64_t current_idle = info[i].idletime / 1000;
|
||||
uint64_t idle_diff = current_idle - prev_idle[i];
|
||||
|
||||
if (time_diff > 0) {
|
||||
double usage = 100.0 * (1.0 - ((double)idle_diff / time_diff));
|
||||
/* Clamp between 0-100% */
|
||||
pcpu_stats[i].usage_pct = (usage < 0) ? 0.0 :
|
||||
(usage > 100) ? 100.0 : usage;
|
||||
} else {
|
||||
pcpu_stats[i].usage_pct = 0.0;
|
||||
}
|
||||
|
||||
prev_idle[i] = current_idle;
|
||||
}
|
||||
|
||||
/* Update global time reference for next calculation */
|
||||
prev_global_time = current_time;
|
||||
|
||||
return 0;
|
||||
|
||||
alloc_error:
|
||||
free_pcpu_stats();
|
||||
errno = ENOMEM;
|
||||
report_pcpu_error("memory allocation failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Accessor functions for xentop.c */
|
||||
int get_pcpu_count(void)
|
||||
{
|
||||
return allocated_pcpus;
|
||||
}
|
||||
|
||||
float get_pcpu_usage(int cpu_index)
|
||||
{
|
||||
if (!pcpu_stats || cpu_index < 0 || cpu_index >= allocated_pcpus) {
|
||||
return 0.0;
|
||||
}
|
||||
return pcpu_stats[cpu_index].usage_pct;
|
||||
}
|
||||
|
||||
int has_pcpu_data(void)
|
||||
{
|
||||
return (pcpu_stats != NULL && allocated_pcpus > 0);
|
||||
}
|
||||
|
||||
void free_pcpu_stats(void)
|
||||
{
|
||||
if (xc_handle) {
|
||||
xc_interface_close(xc_handle);
|
||||
xc_handle = NULL;
|
||||
}
|
||||
free(pcpu_stats);
|
||||
pcpu_stats = NULL;
|
||||
free(prev_idle);
|
||||
prev_idle = NULL;
|
||||
allocated_pcpus = 0;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2025 Renesas Electronics Corporation
|
||||
*/
|
||||
|
||||
#ifndef __XENTOP_PCPU_H__
|
||||
#define __XENTOP_PCPU_H__
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
int update_pcpu_stats(const struct timeval *now, unsigned int delay);
|
||||
int get_pcpu_count(void);
|
||||
float get_pcpu_usage(int cpu_index);
|
||||
int has_pcpu_data(void);
|
||||
void free_pcpu_stats(void);
|
||||
|
||||
#endif /* __XENTOP_PCPU_H__ */
|
||||
+61
-18
@@ -37,6 +37,7 @@
|
||||
#endif
|
||||
|
||||
#include <xenstat.h>
|
||||
#include "pcpu.h"
|
||||
|
||||
#define XENTOP_VERSION "1.0"
|
||||
|
||||
@@ -205,6 +206,7 @@ field_id sort_field = FIELD_DOMID;
|
||||
unsigned int first_domain_index = 0;
|
||||
unsigned int delay = 3;
|
||||
unsigned int batch = 0;
|
||||
static unsigned int show_pcpus = 0;
|
||||
unsigned int loop = 1;
|
||||
unsigned int iterations = 0;
|
||||
int show_vcpus = 0;
|
||||
@@ -230,22 +232,23 @@ static WINDOW *cwin;
|
||||
/* Print usage message, using given program name */
|
||||
static void usage(const char *program)
|
||||
{
|
||||
printf("Usage: %s [OPTION]\n"
|
||||
"Displays ongoing information about xen vm resources \n\n"
|
||||
"-h, --help display this help and exit\n"
|
||||
"-V, --version output version information and exit\n"
|
||||
"-d, --delay=SECONDS seconds between updates (default 3)\n"
|
||||
"-n, --networks output vif network data\n"
|
||||
"-x, --vbds output vbd block device data\n"
|
||||
"-r, --repeat-header repeat table header before each domain\n"
|
||||
"-v, --vcpus output vcpu data\n"
|
||||
"-b, --batch output in batch mode, no user input accepted\n"
|
||||
"-i, --iterations number of iterations before exiting\n"
|
||||
"-f, --full-name output the full domain name (not truncated)\n"
|
||||
"-z, --dom0-first display dom0 first (ignore sorting)\n"
|
||||
"\n" XENTOP_BUGSTO,
|
||||
program);
|
||||
return;
|
||||
printf("Usage: %s [OPTION]\n"
|
||||
"Displays ongoing information about xen vm resources \n\n"
|
||||
"-h, --help display this help and exit\n"
|
||||
"-V, --version output version information and exit\n"
|
||||
"-d, --delay=SECONDS seconds between updates (default 3)\n"
|
||||
"-n, --networks output vif network data\n"
|
||||
"-x, --vbds output vbd block device data\n"
|
||||
"-r, --repeat-header repeat table header before each domain\n"
|
||||
"-v, --vcpus output vcpu data\n"
|
||||
"-b, --batch output in batch mode, no user input accepted\n"
|
||||
"-p, --pcpus show physical CPU stats\n"
|
||||
"-i, --iterations number of iterations before exiting\n"
|
||||
"-f, --full-name output the full domain name (not truncated)\n"
|
||||
"-z, --dom0-first display dom0 first (ignore sorting)\n"
|
||||
"\n" XENTOP_BUGSTO,
|
||||
program);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Print program version information */
|
||||
@@ -267,6 +270,8 @@ static void cleanup(void)
|
||||
xenstat_free_node(cur_node);
|
||||
if(xhandle != NULL)
|
||||
xenstat_uninit(xhandle);
|
||||
|
||||
free_pcpu_stats();
|
||||
}
|
||||
|
||||
/* Display the given message and gracefully exit */
|
||||
@@ -313,6 +318,32 @@ static void print(const char *fmt, ...)
|
||||
}
|
||||
}
|
||||
|
||||
/* PCPU statistics display function */
|
||||
static void print_pcpu_stats_display(void)
|
||||
{
|
||||
int i;
|
||||
int num_cpus;
|
||||
|
||||
if (!has_pcpu_data()) {
|
||||
print("\nNo PCPU data available\n");
|
||||
return;
|
||||
}
|
||||
|
||||
num_cpus = get_pcpu_count();
|
||||
|
||||
/* Use the existing print() function which handles cursor bounds */
|
||||
print("\nPhysical CPU Usage:\n");
|
||||
print("+-------+--------+\n");
|
||||
print("| Core | Usage |\n");
|
||||
print("+-------+--------+\n");
|
||||
|
||||
for (i = 0; i < num_cpus; i++) {
|
||||
print("| %-5d | %5.1f%% |\n", i, get_pcpu_usage(i));
|
||||
}
|
||||
|
||||
print("+-------+--------+\n");
|
||||
}
|
||||
|
||||
static void xentop_attron(int attr)
|
||||
{
|
||||
if (!batch)
|
||||
@@ -1245,6 +1276,14 @@ static void top(void)
|
||||
do_vbd(domains[i]);
|
||||
}
|
||||
|
||||
if (show_pcpus) {
|
||||
if (update_pcpu_stats(&curtime, delay) == 0) {
|
||||
print_pcpu_stats_display();
|
||||
} else {
|
||||
fail("Error getting PCPU stats\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!batch)
|
||||
do_bottom_line();
|
||||
|
||||
@@ -1271,13 +1310,14 @@ int main(int argc, char **argv)
|
||||
{ "repeat-header", no_argument, NULL, 'r' },
|
||||
{ "vcpus", no_argument, NULL, 'v' },
|
||||
{ "delay", required_argument, NULL, 'd' },
|
||||
{ "batch", no_argument, NULL, 'b' },
|
||||
{ "batch", no_argument, NULL, 'b' },
|
||||
{ "pcpus", no_argument, NULL, 'p' },
|
||||
{ "iterations", required_argument, NULL, 'i' },
|
||||
{ "full-name", no_argument, NULL, 'f' },
|
||||
{ "dom0-first", no_argument, NULL, 'z' },
|
||||
{ 0, 0, 0, 0 },
|
||||
};
|
||||
const char *sopts = "hVnxrvd:bi:fz";
|
||||
const char *sopts = "hVnxrvd:bpi:fz";
|
||||
|
||||
if (atexit(cleanup) != 0)
|
||||
fail("Failed to install cleanup handler.\n");
|
||||
@@ -1312,6 +1352,9 @@ int main(int argc, char **argv)
|
||||
case 'b':
|
||||
batch = 1;
|
||||
break;
|
||||
case 'p':
|
||||
show_pcpus = 1;
|
||||
break;
|
||||
case 'i':
|
||||
iterations = atoi(optarg);
|
||||
loop = 0;
|
||||
|
||||