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