1/************************************************************************** 2 * 3 * Copyright 2013 Marek Olšák <maraeo@gmail.com> 4 * All Rights Reserved. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a 7 * copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, sub license, and/or sell copies of the Software, and to 11 * permit persons to whom the Software is furnished to do so, subject to 12 * the following conditions: 13 * 14 * The above copyright notice and this permission notice (including the 15 * next paragraph) shall be included in all copies or substantial portions 16 * of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. 21 * IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR 22 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 * 26 **************************************************************************/ 27 28/* This file contains code for reading CPU load for displaying on the HUD. 29 */ 30 31#include "hud/hud_private.h" 32#include "util/os_time.h" 33#include "os/os_thread.h" 34#include "util/u_memory.h" 35#include "util/u_queue.h" 36#include <stdio.h> 37#include <inttypes.h> 38#ifdef PIPE_OS_WINDOWS 39#include <windows.h> 40#endif 41#if defined(PIPE_OS_BSD) 42#include <sys/types.h> 43#include <sys/sysctl.h> 44#if defined(PIPE_OS_NETBSD) || defined(PIPE_OS_OPENBSD) 45#include <sys/sched.h> 46#else 47#include <sys/resource.h> 48#endif 49#endif 50 51 52#ifdef PIPE_OS_WINDOWS 53 54static inline uint64_t 55filetime_to_scalar(FILETIME ft) 56{ 57 ULARGE_INTEGER uli; 58 uli.LowPart = ft.dwLowDateTime; 59 uli.HighPart = ft.dwHighDateTime; 60 return uli.QuadPart; 61} 62 63static boolean 64get_cpu_stats(unsigned cpu_index, uint64_t *busy_time, uint64_t *total_time) 65{ 66 SYSTEM_INFO sysInfo; 67 FILETIME ftNow, ftCreation, ftExit, ftKernel, ftUser; 68 69 GetSystemInfo(&sysInfo); 70 assert(sysInfo.dwNumberOfProcessors >= 1); 71 if (cpu_index != ALL_CPUS && cpu_index >= sysInfo.dwNumberOfProcessors) { 72 /* Tell hud_get_num_cpus there are only this many CPUs. */ 73 return FALSE; 74 } 75 76 /* Get accumulated user and sys time for all threads */ 77 if (!GetProcessTimes(GetCurrentProcess(), &ftCreation, &ftExit, 78 &ftKernel, &ftUser)) 79 return FALSE; 80 81 GetSystemTimeAsFileTime(&ftNow); 82 83 *busy_time = filetime_to_scalar(ftUser) + filetime_to_scalar(ftKernel); 84 *total_time = filetime_to_scalar(ftNow) - filetime_to_scalar(ftCreation); 85 86 /* busy_time already has the time accross all cpus. 87 * XXX: if we want 100% to mean one CPU, 200% two cpus, eliminate the 88 * following line. 89 */ 90 *total_time *= sysInfo.dwNumberOfProcessors; 91 92 /* XXX: we ignore cpu_index, i.e, we assume that the individual CPU usage 93 * and the system usage are one and the same. 94 */ 95 return TRUE; 96} 97 98#elif defined(PIPE_OS_BSD) 99 100static boolean 101get_cpu_stats(unsigned cpu_index, uint64_t *busy_time, uint64_t *total_time) 102{ 103#if defined(PIPE_OS_NETBSD) || defined(PIPE_OS_OPENBSD) 104 uint64_t cp_time[CPUSTATES]; 105#else 106 long cp_time[CPUSTATES]; 107#endif 108 size_t len; 109 110 if (cpu_index == ALL_CPUS) { 111 len = sizeof(cp_time); 112 113#if defined(PIPE_OS_NETBSD) 114 int mib[] = { CTL_KERN, KERN_CP_TIME }; 115 116 if (sysctl(mib, ARRAY_SIZE(mib), cp_time, &len, NULL, 0) == -1) 117 return FALSE; 118#elif defined(PIPE_OS_OPENBSD) 119 int mib[] = { CTL_KERN, KERN_CPTIME }; 120 long sum_cp_time[CPUSTATES]; 121 122 len = sizeof(sum_cp_time); 123 if (sysctl(mib, ARRAY_SIZE(mib), sum_cp_time, &len, NULL, 0) == -1) 124 return FALSE; 125 126 for (int state = 0; state < CPUSTATES; state++) 127 cp_time[state] = sum_cp_time[state]; 128#else 129 if (sysctlbyname("kern.cp_time", cp_time, &len, NULL, 0) == -1) 130 return FALSE; 131#endif 132 } else { 133#if defined(PIPE_OS_NETBSD) 134 int mib[] = { CTL_KERN, KERN_CP_TIME, cpu_index }; 135 136 len = sizeof(cp_time); 137 if (sysctl(mib, ARRAY_SIZE(mib), cp_time, &len, NULL, 0) == -1) 138 return FALSE; 139#elif defined(PIPE_OS_OPENBSD) 140 int mib[] = { CTL_KERN, KERN_CPTIME2, cpu_index }; 141 142 len = sizeof(cp_time); 143 if (sysctl(mib, ARRAY_SIZE(mib), cp_time, &len, NULL, 0) == -1) 144 return FALSE; 145#else 146 long *cp_times = NULL; 147 148 if (sysctlbyname("kern.cp_times", NULL, &len, NULL, 0) == -1) 149 return FALSE; 150 151 if (len < (cpu_index + 1) * sizeof(cp_time)) 152 return FALSE; 153 154 cp_times = malloc(len); 155 156 if (sysctlbyname("kern.cp_times", cp_times, &len, NULL, 0) == -1) 157 return FALSE; 158 159 memcpy(cp_time, cp_times + (cpu_index * CPUSTATES), 160 sizeof(cp_time)); 161 free(cp_times); 162#endif 163 } 164 165 *busy_time = cp_time[CP_USER] + cp_time[CP_NICE] + 166 cp_time[CP_SYS] + cp_time[CP_INTR]; 167 168 *total_time = *busy_time + cp_time[CP_IDLE]; 169 170 return TRUE; 171} 172 173#else 174 175static boolean 176get_cpu_stats(unsigned cpu_index, uint64_t *busy_time, uint64_t *total_time) 177{ 178 char cpuname[32]; 179 char line[1024]; 180 FILE *f; 181 182 if (cpu_index == ALL_CPUS) 183 strcpy(cpuname, "cpu"); 184 else 185 sprintf(cpuname, "cpu%u", cpu_index); 186 187 f = fopen("/proc/stat", "r"); 188 if (!f) 189 return FALSE; 190 191 while (!feof(f) && fgets(line, sizeof(line), f)) { 192 if (strstr(line, cpuname) == line) { 193 uint64_t v[12]; 194 int i, num; 195 196 num = sscanf(line, 197 "%s %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64 198 " %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64 199 " %"PRIu64" %"PRIu64"", 200 cpuname, &v[0], &v[1], &v[2], &v[3], &v[4], &v[5], 201 &v[6], &v[7], &v[8], &v[9], &v[10], &v[11]); 202 if (num < 5) { 203 fclose(f); 204 return FALSE; 205 } 206 207 /* user + nice + system */ 208 *busy_time = v[0] + v[1] + v[2]; 209 *total_time = *busy_time; 210 211 /* ... + idle + iowait + irq + softirq + ... */ 212 for (i = 3; i < num-1; i++) { 213 *total_time += v[i]; 214 } 215 fclose(f); 216 return TRUE; 217 } 218 } 219 fclose(f); 220 return FALSE; 221} 222#endif 223 224 225struct cpu_info { 226 unsigned cpu_index; 227 uint64_t last_cpu_busy, last_cpu_total, last_time; 228}; 229 230static void 231query_cpu_load(struct hud_graph *gr, struct pipe_context *pipe) 232{ 233 struct cpu_info *info = gr->query_data; 234 uint64_t now = os_time_get(); 235 236 if (info->last_time) { 237 if (info->last_time + gr->pane->period <= now) { 238 uint64_t cpu_busy, cpu_total; 239 double cpu_load; 240 241 get_cpu_stats(info->cpu_index, &cpu_busy, &cpu_total); 242 243 cpu_load = (cpu_busy - info->last_cpu_busy) * 100 / 244 (double)(cpu_total - info->last_cpu_total); 245 hud_graph_add_value(gr, cpu_load); 246 247 info->last_cpu_busy = cpu_busy; 248 info->last_cpu_total = cpu_total; 249 info->last_time = now; 250 } 251 } 252 else { 253 /* initialize */ 254 info->last_time = now; 255 get_cpu_stats(info->cpu_index, &info->last_cpu_busy, 256 &info->last_cpu_total); 257 } 258} 259 260static void 261free_query_data(void *p, struct pipe_context *pipe) 262{ 263 FREE(p); 264} 265 266void 267hud_cpu_graph_install(struct hud_pane *pane, unsigned cpu_index) 268{ 269 struct hud_graph *gr; 270 struct cpu_info *info; 271 uint64_t busy, total; 272 273 /* see if the cpu exists */ 274 if (cpu_index != ALL_CPUS && !get_cpu_stats(cpu_index, &busy, &total)) { 275 return; 276 } 277 278 gr = CALLOC_STRUCT(hud_graph); 279 if (!gr) 280 return; 281 282 if (cpu_index == ALL_CPUS) 283 strcpy(gr->name, "cpu"); 284 else 285 sprintf(gr->name, "cpu%u", cpu_index); 286 287 gr->query_data = CALLOC_STRUCT(cpu_info); 288 if (!gr->query_data) { 289 FREE(gr); 290 return; 291 } 292 293 gr->query_new_value = query_cpu_load; 294 295 /* Don't use free() as our callback as that messes up Gallium's 296 * memory debugger. Use simple free_query_data() wrapper. 297 */ 298 gr->free_query_data = free_query_data; 299 300 info = gr->query_data; 301 info->cpu_index = cpu_index; 302 303 hud_pane_add_graph(pane, gr); 304 hud_pane_set_max_value(pane, 100); 305} 306 307int 308hud_get_num_cpus(void) 309{ 310 uint64_t busy, total; 311 int i = 0; 312 313 while (get_cpu_stats(i, &busy, &total)) 314 i++; 315 316 return i; 317} 318 319struct thread_info { 320 bool main_thread; 321 int64_t last_time; 322 int64_t last_thread_time; 323}; 324 325static void 326query_api_thread_busy_status(struct hud_graph *gr, struct pipe_context *pipe) 327{ 328 struct thread_info *info = gr->query_data; 329 int64_t now = os_time_get_nano(); 330 331 if (info->last_time) { 332 if (info->last_time + gr->pane->period*1000 <= now) { 333 int64_t thread_now; 334 335 if (info->main_thread) { 336 thread_now = util_current_thread_get_time_nano(); 337 } else { 338 struct util_queue_monitoring *mon = gr->pane->hud->monitored_queue; 339 340 if (mon && mon->queue) 341 thread_now = util_queue_get_thread_time_nano(mon->queue, 0); 342 else 343 thread_now = 0; 344 } 345 346 double percent = (thread_now - info->last_thread_time) * 100.0 / 347 (now - info->last_time); 348 349 /* Check if the context changed a thread, so that we don't show 350 * a random value. When a thread is changed, the new thread clock 351 * is different, which can result in "percent" being very high. 352 */ 353 if (percent > 100.0) 354 percent = 0.0; 355 hud_graph_add_value(gr, percent); 356 357 info->last_thread_time = thread_now; 358 info->last_time = now; 359 } 360 } else { 361 /* initialize */ 362 info->last_time = now; 363 info->last_thread_time = util_current_thread_get_time_nano(); 364 } 365} 366 367void 368hud_thread_busy_install(struct hud_pane *pane, const char *name, bool main) 369{ 370 struct hud_graph *gr; 371 372 gr = CALLOC_STRUCT(hud_graph); 373 if (!gr) 374 return; 375 376 strcpy(gr->name, name); 377 378 gr->query_data = CALLOC_STRUCT(thread_info); 379 if (!gr->query_data) { 380 FREE(gr); 381 return; 382 } 383 384 ((struct thread_info*)gr->query_data)->main_thread = main; 385 gr->query_new_value = query_api_thread_busy_status; 386 387 /* Don't use free() as our callback as that messes up Gallium's 388 * memory debugger. Use simple free_query_data() wrapper. 389 */ 390 gr->free_query_data = free_query_data; 391 392 hud_pane_add_graph(pane, gr); 393 hud_pane_set_max_value(pane, 100); 394} 395 396struct counter_info { 397 enum hud_counter counter; 398 unsigned last_value; 399 int64_t last_time; 400}; 401 402static unsigned get_counter(struct hud_graph *gr, enum hud_counter counter) 403{ 404 struct util_queue_monitoring *mon = gr->pane->hud->monitored_queue; 405 406 if (!mon || !mon->queue) 407 return 0; 408 409 switch (counter) { 410 case HUD_COUNTER_OFFLOADED: 411 return mon->num_offloaded_items; 412 case HUD_COUNTER_DIRECT: 413 return mon->num_direct_items; 414 case HUD_COUNTER_SYNCS: 415 return mon->num_syncs; 416 default: 417 assert(0); 418 return 0; 419 } 420} 421 422static void 423query_thread_counter(struct hud_graph *gr, struct pipe_context *pipe) 424{ 425 struct counter_info *info = gr->query_data; 426 int64_t now = os_time_get_nano(); 427 428 if (info->last_time) { 429 if (info->last_time + gr->pane->period*1000 <= now) { 430 unsigned current_value = get_counter(gr, info->counter); 431 432 hud_graph_add_value(gr, current_value - info->last_value); 433 info->last_value = current_value; 434 info->last_time = now; 435 } 436 } else { 437 /* initialize */ 438 info->last_value = get_counter(gr, info->counter); 439 info->last_time = now; 440 } 441} 442 443void hud_thread_counter_install(struct hud_pane *pane, const char *name, 444 enum hud_counter counter) 445{ 446 struct hud_graph *gr = CALLOC_STRUCT(hud_graph); 447 if (!gr) 448 return; 449 450 strcpy(gr->name, name); 451 452 gr->query_data = CALLOC_STRUCT(counter_info); 453 if (!gr->query_data) { 454 FREE(gr); 455 return; 456 } 457 458 ((struct counter_info*)gr->query_data)->counter = counter; 459 gr->query_new_value = query_thread_counter; 460 461 /* Don't use free() as our callback as that messes up Gallium's 462 * memory debugger. Use simple free_query_data() wrapper. 463 */ 464 gr->free_query_data = free_query_data; 465 466 hud_pane_add_graph(pane, gr); 467 hud_pane_set_max_value(pane, 100); 468} 469