346 lines
8.9 KiB
C
346 lines
8.9 KiB
C
|
/*
|
||
|
* QEMU KVM support -- x86 virtual RAPL msr
|
||
|
*
|
||
|
* Copyright 2024 Red Hat, Inc. 2024
|
||
|
*
|
||
|
* Author:
|
||
|
* Anthony Harivel <aharivel@redhat.com>
|
||
|
*
|
||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||
|
* See the COPYING file in the top-level directory.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include "qemu/osdep.h"
|
||
|
#include "qemu/error-report.h"
|
||
|
#include "vmsr_energy.h"
|
||
|
#include "io/channel.h"
|
||
|
#include "io/channel-socket.h"
|
||
|
#include "hw/boards.h"
|
||
|
#include "cpu.h"
|
||
|
#include "host-cpu.h"
|
||
|
|
||
|
char *vmsr_compute_default_paths(void)
|
||
|
{
|
||
|
g_autofree char *state = qemu_get_local_state_dir();
|
||
|
|
||
|
return g_build_filename(state, "run", "qemu-vmsr-helper.sock", NULL);
|
||
|
}
|
||
|
|
||
|
bool is_host_cpu_intel(void)
|
||
|
{
|
||
|
int family, model, stepping;
|
||
|
char vendor[CPUID_VENDOR_SZ + 1];
|
||
|
|
||
|
host_cpu_vendor_fms(vendor, &family, &model, &stepping);
|
||
|
|
||
|
return strcmp(vendor, CPUID_VENDOR_INTEL);
|
||
|
}
|
||
|
|
||
|
int is_rapl_enabled(void)
|
||
|
{
|
||
|
const char *path = "/sys/class/powercap/intel-rapl/enabled";
|
||
|
FILE *file = fopen(path, "r");
|
||
|
int value = 0;
|
||
|
|
||
|
if (file != NULL) {
|
||
|
if (fscanf(file, "%d", &value) != 1) {
|
||
|
error_report("INTEL RAPL not enabled");
|
||
|
}
|
||
|
fclose(file);
|
||
|
} else {
|
||
|
error_report("Error opening %s", path);
|
||
|
}
|
||
|
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
QIOChannelSocket *vmsr_open_socket(const char *path)
|
||
|
{
|
||
|
g_autofree char *socket_path = NULL;
|
||
|
|
||
|
socket_path = g_strdup(path);
|
||
|
|
||
|
SocketAddress saddr = {
|
||
|
.type = SOCKET_ADDRESS_TYPE_UNIX,
|
||
|
.u.q_unix.path = socket_path
|
||
|
};
|
||
|
|
||
|
QIOChannelSocket *sioc = qio_channel_socket_new();
|
||
|
Error *local_err = NULL;
|
||
|
|
||
|
qio_channel_set_name(QIO_CHANNEL(sioc), "vmsr-helper");
|
||
|
qio_channel_socket_connect_sync(sioc,
|
||
|
&saddr,
|
||
|
&local_err);
|
||
|
if (local_err) {
|
||
|
/* Close socket. */
|
||
|
qio_channel_close(QIO_CHANNEL(sioc), NULL);
|
||
|
object_unref(OBJECT(sioc));
|
||
|
sioc = NULL;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
qio_channel_set_delay(QIO_CHANNEL(sioc), false);
|
||
|
out:
|
||
|
return sioc;
|
||
|
}
|
||
|
|
||
|
uint64_t vmsr_read_msr(uint32_t reg, uint32_t cpu_id, uint32_t tid,
|
||
|
QIOChannelSocket *sioc)
|
||
|
{
|
||
|
uint64_t data = 0;
|
||
|
int r = 0;
|
||
|
Error *local_err = NULL;
|
||
|
uint32_t buffer[3];
|
||
|
/*
|
||
|
* Send the required arguments:
|
||
|
* 1. RAPL MSR register to read
|
||
|
* 2. On which CPU ID
|
||
|
* 3. From which vCPU (Thread ID)
|
||
|
*/
|
||
|
buffer[0] = reg;
|
||
|
buffer[1] = cpu_id;
|
||
|
buffer[2] = tid;
|
||
|
|
||
|
r = qio_channel_write_all(QIO_CHANNEL(sioc),
|
||
|
(char *)buffer, sizeof(buffer),
|
||
|
&local_err);
|
||
|
if (r < 0) {
|
||
|
goto out_close;
|
||
|
}
|
||
|
|
||
|
r = qio_channel_read(QIO_CHANNEL(sioc),
|
||
|
(char *)&data, sizeof(data),
|
||
|
&local_err);
|
||
|
if (r < 0) {
|
||
|
data = 0;
|
||
|
goto out_close;
|
||
|
}
|
||
|
|
||
|
out_close:
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
/* Retrieve the max number of physical package */
|
||
|
unsigned int vmsr_get_max_physical_package(unsigned int max_cpus)
|
||
|
{
|
||
|
const char *dir = "/sys/devices/system/cpu/";
|
||
|
const char *topo_path = "topology/physical_package_id";
|
||
|
g_autofree int *uniquePackages = g_new0(int, max_cpus);
|
||
|
unsigned int packageCount = 0;
|
||
|
FILE *file = NULL;
|
||
|
|
||
|
for (int i = 0; i < max_cpus; i++) {
|
||
|
g_autofree char *filePath = NULL;
|
||
|
g_autofree char *cpuid = g_strdup_printf("cpu%d", i);
|
||
|
|
||
|
filePath = g_build_filename(dir, cpuid, topo_path, NULL);
|
||
|
|
||
|
file = fopen(filePath, "r");
|
||
|
|
||
|
if (file == NULL) {
|
||
|
error_report("Error opening physical_package_id file");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
char packageId[10];
|
||
|
if (fgets(packageId, sizeof(packageId), file) == NULL) {
|
||
|
packageCount = 0;
|
||
|
}
|
||
|
|
||
|
fclose(file);
|
||
|
|
||
|
int currentPackageId = atoi(packageId);
|
||
|
|
||
|
bool isUnique = true;
|
||
|
for (int j = 0; j < packageCount; j++) {
|
||
|
if (uniquePackages[j] == currentPackageId) {
|
||
|
isUnique = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (isUnique) {
|
||
|
uniquePackages[packageCount] = currentPackageId;
|
||
|
packageCount++;
|
||
|
|
||
|
if (packageCount >= max_cpus) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (packageCount == 0) ? 1 : packageCount;
|
||
|
}
|
||
|
|
||
|
/* Retrieve the max number of physical cpu on the host */
|
||
|
unsigned int vmsr_get_maxcpus(void)
|
||
|
{
|
||
|
GDir *dir;
|
||
|
const gchar *entry_name;
|
||
|
unsigned int cpu_count = 0;
|
||
|
const char *path = "/sys/devices/system/cpu/";
|
||
|
|
||
|
dir = g_dir_open(path, 0, NULL);
|
||
|
if (dir == NULL) {
|
||
|
error_report("Unable to open cpu directory");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
while ((entry_name = g_dir_read_name(dir)) != NULL) {
|
||
|
if (g_ascii_strncasecmp(entry_name, "cpu", 3) == 0 &&
|
||
|
isdigit(entry_name[3])) {
|
||
|
cpu_count++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
g_dir_close(dir);
|
||
|
|
||
|
return cpu_count;
|
||
|
}
|
||
|
|
||
|
/* Count the number of physical cpu on each packages */
|
||
|
unsigned int vmsr_count_cpus_per_package(unsigned int *package_count,
|
||
|
unsigned int max_pkgs)
|
||
|
{
|
||
|
g_autofree char *file_contents = NULL;
|
||
|
g_autofree char *path = NULL;
|
||
|
g_autofree char *path_name = NULL;
|
||
|
gsize length;
|
||
|
|
||
|
/* Iterate over cpus and count cpus in each package */
|
||
|
for (int cpu_id = 0; ; cpu_id++) {
|
||
|
path_name = g_strdup_printf("/sys/devices/system/cpu/cpu%d/"
|
||
|
"topology/physical_package_id", cpu_id);
|
||
|
|
||
|
path = g_build_filename(path_name, NULL);
|
||
|
|
||
|
if (!g_file_get_contents(path, &file_contents, &length, NULL)) {
|
||
|
break; /* No more cpus */
|
||
|
}
|
||
|
|
||
|
/* Get the physical package ID for this CPU */
|
||
|
int package_id = atoi(file_contents);
|
||
|
|
||
|
/* Check if the package ID is within the known number of packages */
|
||
|
if (package_id >= 0 && package_id < max_pkgs) {
|
||
|
/* If yes, count the cpu for this package*/
|
||
|
package_count[package_id]++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Get the physical package id from a given cpu id */
|
||
|
int vmsr_get_physical_package_id(int cpu_id)
|
||
|
{
|
||
|
g_autofree char *file_contents = NULL;
|
||
|
g_autofree char *file_path = NULL;
|
||
|
int package_id = -1;
|
||
|
gsize length;
|
||
|
|
||
|
file_path = g_strdup_printf("/sys/devices/system/cpu/cpu%d"
|
||
|
"/topology/physical_package_id", cpu_id);
|
||
|
|
||
|
if (!g_file_get_contents(file_path, &file_contents, &length, NULL)) {
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
package_id = atoi(file_contents);
|
||
|
|
||
|
out:
|
||
|
return package_id;
|
||
|
}
|
||
|
|
||
|
/* Read the scheduled time for a given thread of a give pid */
|
||
|
void vmsr_read_thread_stat(pid_t pid,
|
||
|
unsigned int thread_id,
|
||
|
unsigned long long *utime,
|
||
|
unsigned long long *stime,
|
||
|
unsigned int *cpu_id)
|
||
|
{
|
||
|
g_autofree char *path = NULL;
|
||
|
g_autofree char *path_name = NULL;
|
||
|
|
||
|
path_name = g_strdup_printf("/proc/%u/task/%d/stat", pid, thread_id);
|
||
|
|
||
|
path = g_build_filename(path_name, NULL);
|
||
|
|
||
|
FILE *file = fopen(path, "r");
|
||
|
if (file == NULL) {
|
||
|
pid = -1;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (fscanf(file, "%*d (%*[^)]) %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u"
|
||
|
" %llu %llu %*d %*d %*d %*d %*d %*d %*u %*u %*d %*u %*u"
|
||
|
" %*u %*u %*u %*u %*u %*u %*u %*u %*u %*d %*u %*u %u",
|
||
|
utime, stime, cpu_id) != 3)
|
||
|
{
|
||
|
pid = -1;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
fclose(file);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Read QEMU stat task folder to retrieve all QEMU threads ID */
|
||
|
pid_t *vmsr_get_thread_ids(pid_t pid, unsigned int *num_threads)
|
||
|
{
|
||
|
g_autofree char *task_path = g_strdup_printf("%d/task", pid);
|
||
|
g_autofree char *path = g_build_filename("/proc", task_path, NULL);
|
||
|
|
||
|
DIR *dir = opendir(path);
|
||
|
if (dir == NULL) {
|
||
|
error_report("Error opening /proc/qemu/task");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
pid_t *thread_ids = NULL;
|
||
|
unsigned int thread_count = 0;
|
||
|
|
||
|
g_autofree struct dirent *ent = NULL;
|
||
|
while ((ent = readdir(dir)) != NULL) {
|
||
|
if (ent->d_name[0] == '.') {
|
||
|
continue;
|
||
|
}
|
||
|
pid_t tid = atoi(ent->d_name);
|
||
|
if (pid != tid) {
|
||
|
thread_ids = g_renew(pid_t, thread_ids, (thread_count + 1));
|
||
|
thread_ids[thread_count] = tid;
|
||
|
thread_count++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
closedir(dir);
|
||
|
|
||
|
*num_threads = thread_count;
|
||
|
return thread_ids;
|
||
|
}
|
||
|
|
||
|
void vmsr_delta_ticks(vmsr_thread_stat *thd_stat, int i)
|
||
|
{
|
||
|
thd_stat[i].delta_ticks = (thd_stat[i].utime[1] + thd_stat[i].stime[1])
|
||
|
- (thd_stat[i].utime[0] + thd_stat[i].stime[0]);
|
||
|
}
|
||
|
|
||
|
double vmsr_get_ratio(uint64_t e_delta,
|
||
|
unsigned long long delta_ticks,
|
||
|
unsigned int maxticks)
|
||
|
{
|
||
|
return (e_delta / 100.0) * ((100.0 / maxticks) * delta_ticks);
|
||
|
}
|
||
|
|
||
|
void vmsr_init_topo_info(X86CPUTopoInfo *topo_info,
|
||
|
const MachineState *ms)
|
||
|
{
|
||
|
topo_info->dies_per_pkg = ms->smp.dies;
|
||
|
topo_info->modules_per_die = ms->smp.modules;
|
||
|
topo_info->cores_per_module = ms->smp.cores;
|
||
|
topo_info->threads_per_core = ms->smp.threads;
|
||
|
}
|
||
|
|