| 1 | /* |
| 2 | * QEMU KVM support -- x86 virtual RAPL msr |
| 3 | * |
| 4 | * Copyright 2024 Red Hat, Inc. 2024 |
| 5 | * |
| 6 | * Author: |
| 7 | * Anthony Harivel <aharivel@redhat.com> |
| 8 | * |
| 9 | * This work is licensed under the terms of the GNU GPL, version 2 or later. |
| 10 | * See the COPYING file in the top-level directory. |
| 11 | * |
| 12 | */ |
| 13 | |
| 14 | #include "qemu/osdep.h" |
| 15 | #include "qemu/error-report.h" |
| 16 | #include "vmsr_energy.h" |
| 17 | #include "io/channel.h" |
| 18 | #include "io/channel-socket.h" |
| 19 | #include "hw/core/boards.h" |
| 20 | #include "cpu.h" |
| 21 | #include "host-cpu.h" |
| 22 | |
| 23 | char *vmsr_compute_default_paths(void) |
| 24 | { |
| 25 | g_autofree char *state = qemu_get_local_state_dir(); |
| 26 | |
| 27 | return g_build_filename(state, "run", "qemu-vmsr-helper.sock", NULL); |
| 28 | } |
| 29 | |
| 30 | int is_rapl_enabled(void) |
| 31 | { |
| 32 | const char *path = "/sys/class/powercap/intel-rapl/enabled"; |
| 33 | FILE *file = fopen(path, "r"); |
| 34 | int value = 0; |
| 35 | |
| 36 | if (file != NULL) { |
| 37 | if (fscanf(file, "%d", &value) != 1) { |
| 38 | error_report("INTEL RAPL not enabled"); |
| 39 | } |
| 40 | fclose(file); |
| 41 | } else { |
| 42 | error_report("Error opening %s", path); |
| 43 | } |
| 44 | |
| 45 | return value; |
| 46 | } |
| 47 | |
| 48 | QIOChannelSocket *vmsr_open_socket(const char *path) |
| 49 | { |
| 50 | g_autofree char *socket_path = NULL; |
| 51 | |
| 52 | socket_path = g_strdup(path); |
| 53 | |
| 54 | SocketAddress saddr = { |
| 55 | .type = SOCKET_ADDRESS_TYPE_UNIX, |
| 56 | .u.q_unix.path = socket_path |
| 57 | }; |
| 58 | |
| 59 | QIOChannelSocket *sioc = qio_channel_socket_new(); |
| 60 | |
| 61 | qio_channel_set_name(QIO_CHANNEL(sioc), "vmsr-helper"); |
| 62 | if (qio_channel_socket_connect_sync(sioc, &saddr, NULL) < 0) { |
| 63 | /* Close socket. */ |
| 64 | qio_channel_close(QIO_CHANNEL(sioc), NULL); |
| 65 | object_unref(OBJECT(sioc)); |
| 66 | sioc = NULL; |
| 67 | goto out; |
| 68 | } |
| 69 | |
| 70 | qio_channel_set_delay(QIO_CHANNEL(sioc), false); |
| 71 | out: |
| 72 | return sioc; |
| 73 | } |
| 74 | |
| 75 | uint64_t vmsr_read_msr(uint32_t reg, uint32_t cpu_id, uint32_t tid, |
| 76 | QIOChannelSocket *sioc) |
| 77 | { |
| 78 | uint64_t data = 0; |
| 79 | int r = 0; |
| 80 | Error *local_err = NULL; |
| 81 | uint32_t buffer[3]; |
| 82 | /* |
| 83 | * Send the required arguments: |
| 84 | * 1. RAPL MSR register to read |
| 85 | * 2. On which CPU ID |
| 86 | * 3. From which vCPU (Thread ID) |
| 87 | */ |
| 88 | buffer[0] = reg; |
| 89 | buffer[1] = cpu_id; |
| 90 | buffer[2] = tid; |
| 91 | |
| 92 | r = qio_channel_write_all(QIO_CHANNEL(sioc), |
| 93 | (char *)buffer, sizeof(buffer), |
| 94 | &local_err); |
| 95 | if (r < 0) { |
| 96 | goto out_close; |
| 97 | } |
| 98 | |
| 99 | r = qio_channel_read(QIO_CHANNEL(sioc), |
| 100 | (char *)&data, sizeof(data), |
| 101 | &local_err); |
| 102 | if (r < 0) { |
| 103 | data = 0; |
| 104 | goto out_close; |
| 105 | } |
| 106 | |
| 107 | out_close: |
| 108 | return data; |
| 109 | } |
| 110 | |
| 111 | /* Retrieve the max number of physical package */ |
| 112 | unsigned int vmsr_get_max_physical_package(unsigned int max_cpus) |
| 113 | { |
| 114 | const char *dir = "/sys/devices/system/cpu/"; |
| 115 | const char *topo_path = "topology/physical_package_id"; |
| 116 | g_autofree int *uniquePackages = g_new0(int, max_cpus); |
| 117 | unsigned int packageCount = 0; |
| 118 | FILE *file = NULL; |
| 119 | |
| 120 | for (int i = 0; i < max_cpus; i++) { |
| 121 | g_autofree char *filePath = NULL; |
| 122 | g_autofree char *cpuid = g_strdup_printf("cpu%d", i); |
| 123 | |
| 124 | filePath = g_build_filename(dir, cpuid, topo_path, NULL); |
| 125 | |
| 126 | file = fopen(filePath, "r"); |
| 127 | |
| 128 | if (file == NULL) { |
| 129 | error_report("Error opening physical_package_id file"); |
| 130 | return 0; |
| 131 | } |
| 132 | |
| 133 | char packageId[10]; |
| 134 | if (fgets(packageId, sizeof(packageId), file) == NULL) { |
| 135 | packageCount = 0; |
| 136 | } |
| 137 | |
| 138 | fclose(file); |
| 139 | |
| 140 | int currentPackageId = atoi(packageId); |
| 141 | |
| 142 | bool isUnique = true; |
| 143 | for (int j = 0; j < packageCount; j++) { |
| 144 | if (uniquePackages[j] == currentPackageId) { |
| 145 | isUnique = false; |
| 146 | break; |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | if (isUnique) { |
| 151 | uniquePackages[packageCount] = currentPackageId; |
| 152 | packageCount++; |
| 153 | |
| 154 | if (packageCount >= max_cpus) { |
| 155 | break; |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | return (packageCount == 0) ? 1 : packageCount; |
| 161 | } |
| 162 | |
| 163 | /* Retrieve the max number of physical cpu on the host */ |
| 164 | unsigned int vmsr_get_maxcpus(void) |
| 165 | { |
| 166 | GDir *dir; |
| 167 | const gchar *entry_name; |
| 168 | unsigned int cpu_count = 0; |
| 169 | const char *path = "/sys/devices/system/cpu/"; |
| 170 | |
| 171 | dir = g_dir_open(path, 0, NULL); |
| 172 | if (dir == NULL) { |
| 173 | error_report("Unable to open cpu directory"); |
| 174 | return -1; |
| 175 | } |
| 176 | |
| 177 | while ((entry_name = g_dir_read_name(dir)) != NULL) { |
| 178 | if (g_ascii_strncasecmp(entry_name, "cpu", 3) == 0 && |
| 179 | isdigit(entry_name[3])) { |
| 180 | cpu_count++; |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | g_dir_close(dir); |
| 185 | |
| 186 | return cpu_count; |
| 187 | } |
| 188 | |
| 189 | /* Count the number of physical cpu on each packages */ |
| 190 | unsigned int vmsr_count_cpus_per_package(unsigned int *package_count, |
| 191 | unsigned int max_pkgs) |
| 192 | { |
| 193 | g_autofree char *file_contents = NULL; |
| 194 | g_autofree char *path = NULL; |
| 195 | g_autofree char *path_name = NULL; |
| 196 | gsize length; |
| 197 | |
| 198 | /* Iterate over cpus and count cpus in each package */ |
| 199 | for (int cpu_id = 0; ; cpu_id++) { |
| 200 | path_name = g_strdup_printf("/sys/devices/system/cpu/cpu%d/" |
| 201 | "topology/physical_package_id", cpu_id); |
| 202 | |
| 203 | path = g_build_filename(path_name, NULL); |
| 204 | |
| 205 | if (!g_file_get_contents(path, &file_contents, &length, NULL)) { |
| 206 | break; /* No more cpus */ |
| 207 | } |
| 208 | |
| 209 | /* Get the physical package ID for this CPU */ |
| 210 | int package_id = atoi(file_contents); |
| 211 | |
| 212 | /* Check if the package ID is within the known number of packages */ |
| 213 | if (package_id >= 0 && package_id < max_pkgs) { |
| 214 | /* If yes, count the cpu for this package*/ |
| 215 | package_count[package_id]++; |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | return 0; |
| 220 | } |
| 221 | |
| 222 | /* Get the physical package id from a given cpu id */ |
| 223 | int vmsr_get_physical_package_id(int cpu_id) |
| 224 | { |
| 225 | g_autofree char *file_contents = NULL; |
| 226 | g_autofree char *file_path = NULL; |
| 227 | int package_id = -1; |
| 228 | gsize length; |
| 229 | |
| 230 | file_path = g_strdup_printf("/sys/devices/system/cpu/cpu%d" |
| 231 | "/topology/physical_package_id", cpu_id); |
| 232 | |
| 233 | if (!g_file_get_contents(file_path, &file_contents, &length, NULL)) { |
| 234 | goto out; |
| 235 | } |
| 236 | |
| 237 | package_id = atoi(file_contents); |
| 238 | |
| 239 | out: |
| 240 | return package_id; |
| 241 | } |
| 242 | |
| 243 | /* Read the scheduled time for a given thread of a give pid */ |
| 244 | void vmsr_read_thread_stat(pid_t pid, |
| 245 | unsigned int thread_id, |
| 246 | unsigned long long *utime, |
| 247 | unsigned long long *stime, |
| 248 | unsigned int *cpu_id) |
| 249 | { |
| 250 | g_autofree char *path = NULL; |
| 251 | g_autofree char *path_name = NULL; |
| 252 | |
| 253 | path_name = g_strdup_printf("/proc/%u/task/%d/stat", pid, thread_id); |
| 254 | |
| 255 | path = g_build_filename(path_name, NULL); |
| 256 | |
| 257 | FILE *file = fopen(path, "r"); |
| 258 | if (file == NULL) { |
| 259 | error_report("Error opening %s", path_name); |
| 260 | return; |
| 261 | } |
| 262 | |
| 263 | if (fscanf(file, "%*d (%*[^)]) %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u" |
| 264 | " %llu %llu %*d %*d %*d %*d %*d %*d %*u %*u %*d %*u %*u" |
| 265 | " %*u %*u %*u %*u %*u %*u %*u %*u %*u %*d %*u %*u %u", |
| 266 | utime, stime, cpu_id) != 3) |
| 267 | { |
| 268 | fclose(file); |
| 269 | error_report("Error fscanf did not report the right amount of items"); |
| 270 | return; |
| 271 | } |
| 272 | |
| 273 | fclose(file); |
| 274 | } |
| 275 | |
| 276 | /* Read QEMU stat task folder to retrieve all QEMU threads ID */ |
| 277 | pid_t *vmsr_get_thread_ids(pid_t pid, unsigned int *num_threads) |
| 278 | { |
| 279 | g_autofree char *task_path = g_strdup_printf("%d/task", pid); |
| 280 | g_autofree char *path = g_build_filename("/proc", task_path, NULL); |
| 281 | |
| 282 | DIR *dir = opendir(path); |
| 283 | if (dir == NULL) { |
| 284 | error_report("Error opening /proc/qemu/task"); |
| 285 | return NULL; |
| 286 | } |
| 287 | |
| 288 | pid_t *thread_ids = NULL; |
| 289 | unsigned int thread_count = 0; |
| 290 | |
| 291 | g_autofree struct dirent *ent = NULL; |
| 292 | while ((ent = readdir(dir)) != NULL) { |
| 293 | if (ent->d_name[0] == '.') { |
| 294 | continue; |
| 295 | } |
| 296 | pid_t tid = atoi(ent->d_name); |
| 297 | if (pid != tid) { |
| 298 | thread_ids = g_renew(pid_t, thread_ids, (thread_count + 1)); |
| 299 | thread_ids[thread_count] = tid; |
| 300 | thread_count++; |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | closedir(dir); |
| 305 | |
| 306 | *num_threads = thread_count; |
| 307 | return thread_ids; |
| 308 | } |
| 309 | |
| 310 | void vmsr_delta_ticks(vmsr_thread_stat *thd_stat, int i) |
| 311 | { |
| 312 | thd_stat[i].delta_ticks = (thd_stat[i].utime[1] + thd_stat[i].stime[1]) |
| 313 | - (thd_stat[i].utime[0] + thd_stat[i].stime[0]); |
| 314 | } |
| 315 | |
| 316 | double vmsr_get_ratio(uint64_t e_delta, |
| 317 | unsigned long long delta_ticks, |
| 318 | unsigned int maxticks) |
| 319 | { |
| 320 | return (e_delta / 100.0) * ((100.0 / maxticks) * delta_ticks); |
| 321 | } |
| 322 | |
| 323 | void vmsr_init_topo_info(X86CPUTopoInfo *topo_info, |
| 324 | const MachineState *ms) |
| 325 | { |
| 326 | topo_info->dies_per_pkg = ms->smp.dies; |
| 327 | topo_info->modules_per_die = ms->smp.modules; |
| 328 | topo_info->cores_per_module = ms->smp.cores; |
| 329 | topo_info->threads_per_core = ms->smp.threads; |
| 330 | } |
| 331 |