/* * QEMU KVM support -- x86 virtual RAPL msr * * Copyright 2024 Red Hat, Inc. 2024 * * Author: * Anthony Harivel * * 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 g_str_equal(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) { error_report("Error opening %s", path_name); 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) { fclose(file); error_report("Error fscanf did not report the right amount of items"); 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; }