yapf format.
[oweals/gnunet.git] / src / testbed / gnunet-service-testbed_cpustatus.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2008--2013 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      Affero General Public License for more details.
14
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19  */
20
21 /**
22  * @file testbed/gnunet-service-testbed_cpustatus.c
23  * @brief calls to determine current CPU load
24  * @author Tzvetan Horozov
25  * @author Christian Grothoff
26  * @author Igor Wronsky
27  * @author Alex Harper (OS X portion)
28  * @author Sree Harsha Totakura
29  */
30
31 #include "platform.h"
32 #include "gnunet_util_lib.h"
33 #include "gnunet-service-testbed_meminfo.h"
34
35 #if SOLARIS
36 #if HAVE_KSTAT_H
37 #include <kstat.h>
38 #endif
39 #if HAVE_SYS_SYSINFO_H
40 #include <sys/sysinfo.h>
41 #endif
42 #if HAVE_KVM_H
43 #include <kvm.h>
44 #endif
45 #endif
46 #if SOMEBSD
47 #if HAVE_KVM_H
48 #include <kvm.h>
49 #endif
50 #endif
51
52 #ifdef OSX
53 #include <mach/mach.h>
54
55 static processor_cpu_load_info_t prev_cpu_load;
56 #endif
57
58 #define DEBUG_STATUSCALLS GNUNET_NO
59
60 #ifdef LINUX
61 static FILE *proc_stat;
62 #endif
63
64 /**
65  * Current CPU load, as percentage of CPU cycles not idle or
66  * blocked on IO.
67  */
68 static int currentCPULoad;
69
70 static double agedCPULoad = -1;
71
72 /**
73  * Current IO load, as percentage of CPU cycles blocked on IO.
74  */
75 static int currentIOLoad;
76
77 static double agedIOLoad = -1;
78
79
80 /**
81  * hanlde to the file to write the load statistics to
82  */
83 struct GNUNET_BIO_WriteHandle *bw;
84
85 struct GNUNET_SCHEDULER_Task *sample_load_task_id;
86
87
88 #ifdef OSX
89 static int
90 initMachCpuStats ()
91 {
92   unsigned int cpu_count;
93   processor_cpu_load_info_t cpu_load;
94   mach_msg_type_number_t cpu_msg_count;
95   kern_return_t kret;
96   int i, j;
97
98   kret = host_processor_info (mach_host_self (),
99                               PROCESSOR_CPU_LOAD_INFO,
100                               &cpu_count,
101                               (processor_info_array_t *) &cpu_load,
102                               &cpu_msg_count);
103   if (kret != KERN_SUCCESS)
104   {
105     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "host_processor_info failed.");
106     return GNUNET_SYSERR;
107   }
108   prev_cpu_load = GNUNET_malloc (cpu_count * sizeof(*prev_cpu_load));
109   for (i = 0; i < cpu_count; i++)
110   {
111     for (j = 0; j < CPU_STATE_MAX; j++)
112     {
113       prev_cpu_load[i].cpu_ticks[j] = cpu_load[i].cpu_ticks[j];
114     }
115   }
116   vm_deallocate (mach_task_self (),
117                  (vm_address_t) cpu_load,
118                  (vm_size_t) (cpu_msg_count * sizeof(*cpu_load)));
119   return GNUNET_OK;
120 }
121 #endif
122
123 /**
124  * Update the currentCPU and currentIO load (and on Linux, memory) values.
125  *
126  * Before its first invocation the method initStatusCalls() must be called.
127  * If there is an error the method returns -1.
128  */
129 static int
130 updateUsage ()
131 {
132   currentIOLoad = -1;
133   currentCPULoad = -1;
134 #ifdef LINUX
135   /* under linux, first try %idle/usage using /proc/stat;
136      if that does not work, disable /proc/stat for the future
137      by closing the file and use the next-best method. */
138   if (proc_stat != NULL)
139   {
140     static unsigned long long last_cpu_results[5] = { 0, 0, 0, 0, 0 };
141     static int have_last_cpu = GNUNET_NO;
142     int ret;
143     char line[256];
144     unsigned long long user_read, system_read, nice_read, idle_read,
145                        iowait_read;
146     unsigned long long user, system, nice, idle, iowait;
147     unsigned long long usage_time = 0, total_time = 1;
148
149     /* Get the first line with the data */
150     rewind (proc_stat);
151     fflush (proc_stat);
152     if (NULL == fgets (line, 256, proc_stat))
153     {
154       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
155                                 "fgets", "/proc/stat");
156       proc_stat = NULL;         /* don't try again */
157     }
158     else
159     {
160       iowait_read = 0;
161       ret = sscanf (line, "%*s %llu %llu %llu %llu %llu",
162                     &user_read,
163                     &system_read, &nice_read, &idle_read, &iowait_read);
164       if (ret < 4)
165       {
166         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
167                                   "fgets-sscanf", "/proc/stat");
168         fclose (proc_stat);
169         proc_stat = NULL;       /* don't try again */
170         have_last_cpu = GNUNET_NO;
171       }
172       else
173       {
174         /* Store the current usage */
175         user = user_read - last_cpu_results[0];
176         system = system_read - last_cpu_results[1];
177         nice = nice_read - last_cpu_results[2];
178         idle = idle_read - last_cpu_results[3];
179         iowait = iowait_read - last_cpu_results[4];
180         /* Calculate the % usage */
181         usage_time = user + system + nice;
182         total_time = usage_time + idle + iowait;
183         if ((total_time > 0) && (have_last_cpu == GNUNET_YES))
184         {
185           currentCPULoad = (int) (100L * usage_time / total_time);
186           if (ret > 4)
187             currentIOLoad = (int) (100L * iowait / total_time);
188           else
189             currentIOLoad = -1;         /* 2.4 kernel */
190         }
191         /* Store the values for the next calculation */
192         last_cpu_results[0] = user_read;
193         last_cpu_results[1] = system_read;
194         last_cpu_results[2] = nice_read;
195         last_cpu_results[3] = idle_read;
196         last_cpu_results[4] = iowait_read;
197         have_last_cpu = GNUNET_YES;
198         return GNUNET_OK;
199       }
200     }
201   }
202 #endif
203
204 #ifdef OSX
205   {
206     unsigned int cpu_count;
207     processor_cpu_load_info_t cpu_load;
208     mach_msg_type_number_t cpu_msg_count;
209     unsigned long long t_sys, t_user, t_nice, t_idle, t_total;
210     unsigned long long t_idle_all, t_total_all;
211     kern_return_t kret;
212     int i, j;
213
214     t_idle_all = t_total_all = 0;
215     kret = host_processor_info (mach_host_self (), PROCESSOR_CPU_LOAD_INFO,
216                                 &cpu_count,
217                                 (processor_info_array_t *) &cpu_load,
218                                 &cpu_msg_count);
219     if (kret == KERN_SUCCESS)
220     {
221       for (i = 0; i < cpu_count; i++)
222       {
223         if (cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM] >=
224             prev_cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM])
225         {
226           t_sys = cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM]
227                   - prev_cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM];
228         }
229         else
230         {
231           t_sys = cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM]
232                   + (ULONG_MAX - prev_cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM]
233                      + 1);
234         }
235
236         if (cpu_load[i].cpu_ticks[CPU_STATE_USER] >=
237             prev_cpu_load[i].cpu_ticks[CPU_STATE_USER])
238         {
239           t_user = cpu_load[i].cpu_ticks[CPU_STATE_USER]
240                    - prev_cpu_load[i].cpu_ticks[CPU_STATE_USER];
241         }
242         else
243         {
244           t_user = cpu_load[i].cpu_ticks[CPU_STATE_USER]
245                    + (ULONG_MAX - prev_cpu_load[i].cpu_ticks[CPU_STATE_USER]
246                       + 1);
247         }
248
249         if (cpu_load[i].cpu_ticks[CPU_STATE_NICE] >=
250             prev_cpu_load[i].cpu_ticks[CPU_STATE_NICE])
251         {
252           t_nice = cpu_load[i].cpu_ticks[CPU_STATE_NICE]
253                    - prev_cpu_load[i].cpu_ticks[CPU_STATE_NICE];
254         }
255         else
256         {
257           t_nice = cpu_load[i].cpu_ticks[CPU_STATE_NICE]
258                    + (ULONG_MAX - prev_cpu_load[i].cpu_ticks[CPU_STATE_NICE]
259                       + 1);
260         }
261
262         if (cpu_load[i].cpu_ticks[CPU_STATE_IDLE] >=
263             prev_cpu_load[i].cpu_ticks[CPU_STATE_IDLE])
264         {
265           t_idle = cpu_load[i].cpu_ticks[CPU_STATE_IDLE]
266                    - prev_cpu_load[i].cpu_ticks[CPU_STATE_IDLE];
267         }
268         else
269         {
270           t_idle = cpu_load[i].cpu_ticks[CPU_STATE_IDLE]
271                    + (ULONG_MAX - prev_cpu_load[i].cpu_ticks[CPU_STATE_IDLE]
272                       + 1);
273         }
274         t_total = t_sys + t_user + t_nice + t_idle;
275         t_idle_all += t_idle;
276         t_total_all += t_total;
277       }
278       for (i = 0; i < cpu_count; i++)
279       {
280         for (j = 0; j < CPU_STATE_MAX; j++)
281         {
282           prev_cpu_load[i].cpu_ticks[j] = cpu_load[i].cpu_ticks[j];
283         }
284       }
285       if (t_total_all > 0)
286         currentCPULoad = 100 - (100 * t_idle_all) / t_total_all;
287       else
288         currentCPULoad = -1;
289       vm_deallocate (mach_task_self (),
290                      (vm_address_t) cpu_load,
291                      (vm_size_t) (cpu_msg_count * sizeof(*cpu_load)));
292       currentIOLoad = -1;       /* FIXME-OSX! */
293       return GNUNET_OK;
294     }
295     else
296     {
297       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "host_processor_info failed.");
298       return GNUNET_SYSERR;
299     }
300   }
301 #endif
302   /* try kstat (Solaris only) */
303 #if SOLARIS && HAVE_KSTAT_H && HAVE_SYS_SYSINFO_H
304   {
305     static long long last_idlecount;
306     static long long last_totalcount;
307     static int kstat_once;      /* if open fails, don't keep
308                                    trying */
309     kstat_ctl_t *kc;
310     kstat_t *khelper;
311     long long idlecount;
312     long long totalcount;
313     long long deltaidle;
314     long long deltatotal;
315
316     if (kstat_once == 1)
317       goto ABORT_KSTAT;
318     kc = kstat_open ();
319     if (kc == NULL)
320     {
321       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kstat_close");
322       goto ABORT_KSTAT;
323     }
324
325     idlecount = 0;
326     totalcount = 0;
327     for (khelper = kc->kc_chain; khelper != NULL; khelper = khelper->ks_next)
328     {
329       cpu_stat_t stats;
330
331       if (0 != strncmp (khelper->ks_name, "cpu_stat", strlen ("cpu_stat")))
332         continue;
333       if (khelper->ks_data_size > sizeof(cpu_stat_t))
334         continue;               /* better save then sorry! */
335       if (-1 != kstat_read (kc, khelper, &stats))
336       {
337         idlecount += stats.cpu_sysinfo.cpu[CPU_IDLE];
338         totalcount
339           += stats.cpu_sysinfo.cpu[CPU_IDLE]
340              + stats.cpu_sysinfo.cpu[CPU_USER]
341              + stats.cpu_sysinfo.cpu[CPU_KERNEL]
342              + stats.cpu_sysinfo.cpu[CPU_WAIT];
343       }
344     }
345     if (0 != kstat_close (kc))
346       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kstat_close");
347     if ((idlecount == 0) && (totalcount == 0))
348       goto ABORT_KSTAT;         /* no stats found => abort */
349     deltaidle = idlecount - last_idlecount;
350     deltatotal = totalcount - last_totalcount;
351     if ((deltatotal > 0) && (last_totalcount > 0))
352     {
353       currentCPULoad = (unsigned int) (100.0 * deltaidle / deltatotal);
354       if (currentCPULoad > 100)
355         currentCPULoad = 100;   /* odd */
356       if (currentCPULoad < 0)
357         currentCPULoad = 0;     /* odd */
358       currentCPULoad = 100 - currentCPULoad;    /* computed idle-load before! */
359     }
360     else
361       currentCPULoad = -1;
362     currentIOLoad = -1;         /* FIXME-SOLARIS! */
363     last_idlecount = idlecount;
364     last_totalcount = totalcount;
365     return GNUNET_OK;
366 ABORT_KSTAT:
367     kstat_once = 1;             /* failed, don't try again */
368     return GNUNET_SYSERR;
369   }
370 #endif
371
372   /* insert methods better than getloadavg for
373      other platforms HERE! */
374
375   /* ok, maybe we have getloadavg on this platform */
376 #if HAVE_GETLOADAVG
377   {
378     static int warnOnce = 0;
379     double loadavg;
380     if (1 != getloadavg (&loadavg, 1))
381     {
382       /* only warn once, if there is a problem with
383          getloadavg, we're going to hit it frequently... */
384       if (warnOnce == 0)
385       {
386         warnOnce = 1;
387         GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "getloadavg");
388       }
389       return GNUNET_SYSERR;
390     }
391     else
392     {
393       /* success with getloadavg */
394       currentCPULoad = (int) (100 * loadavg);
395       currentIOLoad = -1;       /* FIXME */
396       return GNUNET_OK;
397     }
398   }
399 #endif
400
401   /* loadaverage not defined and no platform
402      specific alternative defined
403      => default: error
404    */
405   return GNUNET_SYSERR;
406 }
407
408
409 /**
410  * Update load values (if enough time has expired),
411  * including computation of averages.  Code assumes
412  * that lock has already been obtained.
413  */
414 static void
415 updateAgedLoad ()
416 {
417   static struct GNUNET_TIME_Absolute lastCall;
418   struct GNUNET_TIME_Relative age;
419
420   age = GNUNET_TIME_absolute_get_duration (lastCall);
421   if ((agedCPULoad == -1)
422       || (age.rel_value_us > 500000))
423   {
424     /* use smoothing, but do NOT update lastRet at frequencies higher
425        than 500ms; this makes the smoothing (mostly) independent from
426        the frequency at which getCPULoad is called (and we don't spend
427        more time measuring CPU than actually computing something). */
428     lastCall = GNUNET_TIME_absolute_get ();
429     updateUsage ();
430     if (currentCPULoad == -1)
431     {
432       agedCPULoad = -1;
433     }
434     else
435     {
436       if (agedCPULoad == -1)
437       {
438         agedCPULoad = currentCPULoad;
439       }
440       else
441       {
442         /* for CPU, we don't do the 'fast increase' since CPU is much
443            more jitterish to begin with */
444         agedCPULoad = (agedCPULoad * 31 + currentCPULoad) / 32;
445       }
446     }
447     if (currentIOLoad == -1)
448     {
449       agedIOLoad = -1;
450     }
451     else
452     {
453       if (agedIOLoad == -1)
454       {
455         agedIOLoad = currentIOLoad;
456       }
457       else
458       {
459         /* for IO, we don't do the 'fast increase' since IO is much
460            more jitterish to begin with */
461         agedIOLoad = (agedIOLoad * 31 + currentIOLoad) / 32;
462       }
463     }
464   }
465 }
466
467 /**
468  * Get the load of the CPU relative to what is allowed.
469  * @return the CPU load as a percentage of allowed
470  *        (100 is equivalent to full load)
471  */
472 static int
473 cpu_get_load ()
474 {
475   updateAgedLoad ();
476   return (int) agedCPULoad;
477 }
478
479
480 /**
481  * Get the load of the CPU relative to what is allowed.
482  * @return the CPU load as a percentage of allowed
483  *        (100 is equivalent to full load)
484  */
485 static int
486 disk_get_load ()
487 {
488   updateAgedLoad ();
489   return (int) agedIOLoad;
490 }
491
492 /**
493  * Get the percentage of memory used
494  *
495  * @return the percentage of memory used
496  */
497 static unsigned int
498 mem_get_usage ()
499 {
500   double percentage;
501
502   meminfo ();
503   percentage = (((double) kb_main_used) / ((double) kb_main_total) * 100.0);
504   return (unsigned int) percentage;
505 }
506
507
508 #ifdef LINUX
509 #include <dirent.h>
510 /**
511  * Returns the number of processes
512  *
513  * @return the number of processes
514  */
515 static unsigned int
516 get_nproc ()
517 {
518   DIR *dir;
519   struct dirent *ent;
520   unsigned int nproc;
521
522   dir = opendir ("/proc");
523   if (NULL == dir)
524     return 0;
525   nproc = 0;
526   while (NULL != (ent = readdir (dir)))
527   {
528     if ((*ent->d_name > '0') && (*ent->d_name <= '9'))
529       nproc++;
530   }
531   closedir (dir);
532   return nproc;
533 }
534 #endif
535
536
537 static void
538 sample_load_task (void *cls)
539 {
540   struct GNUNET_TIME_Absolute now;
541   char *str;
542   int nbs;
543   int ld_cpu;
544   int ld_disk;
545   unsigned int mem_usage;
546   unsigned int nproc;
547
548   sample_load_task_id = NULL;
549   ld_cpu = cpu_get_load ();
550   ld_disk = disk_get_load ();
551   if ((-1 == ld_cpu) || (-1 == ld_disk))
552     goto reschedule;
553   mem_usage = mem_get_usage ();
554 #ifdef LINUX
555   nproc = get_nproc ();
556 #else
557   nproc = 0;
558 #endif
559   now = GNUNET_TIME_absolute_get ();
560   nbs = GNUNET_asprintf (&str, "%llu %d %d %u %u\n", now.abs_value_us / 1000LL
561                          / 1000LL,
562                          ld_cpu, ld_disk, mem_usage, nproc);
563   if (0 < nbs)
564   {
565     GNUNET_BIO_write (bw, str, nbs);
566   }
567   else
568     GNUNET_break (0);
569   GNUNET_free (str);
570
571 reschedule:
572   sample_load_task_id =
573     GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
574                                   &sample_load_task, NULL);
575 }
576
577
578 /**
579  * Initialize logging CPU and IO statisticfs.  Checks the configuration for
580  * "STATS_DIR" and logs to a file in that directory.  The file is name is
581  * generated from the hostname and the process's PID.
582  */
583 void
584 GST_stats_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
585 {
586   char *hostname;
587   char *stats_dir;
588   char *fn;
589   size_t len;
590
591   if (GNUNET_OK !=
592       GNUNET_CONFIGURATION_get_value_filename (cfg, "testbed",
593                                                "STATS_DIR", &stats_dir))
594     return;
595   len = GNUNET_OS_get_hostname_max_length ();
596   hostname = GNUNET_malloc (len);
597   if (0 != gethostname (hostname, len))
598   {
599     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "gethostname");
600     GNUNET_free (stats_dir);
601     GNUNET_free (hostname);
602     return;
603   }
604   fn = NULL;
605   (void) GNUNET_asprintf (&fn, "%s/%.*s-%jd.dat", stats_dir, len,
606                           hostname, (intmax_t) getpid ());
607   GNUNET_free (stats_dir);
608   GNUNET_free (hostname);
609   if (NULL == (bw = GNUNET_BIO_write_open (fn)))
610   {
611     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
612                 _ ("Cannot open %s for writing load statistics.  "
613                    "Not logging load statistics\n"), fn);
614     GNUNET_free (fn);
615     return;
616   }
617   GNUNET_free (fn);
618   sample_load_task_id = GNUNET_SCHEDULER_add_now (&sample_load_task, NULL);
619 #ifdef LINUX
620   proc_stat = fopen ("/proc/stat", "r");
621   if (NULL == proc_stat)
622     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
623                               "fopen", "/proc/stat");
624 #elif OSX
625   initMachCpuStats ();
626 #endif
627   updateUsage ();                /* initialize */
628 }
629
630
631 /**
632  * Shutdown the status calls module.
633  */
634 void
635 GST_stats_destroy ()
636 {
637   if (NULL == bw)
638     return;
639 #ifdef LINUX
640   if (proc_stat != NULL)
641   {
642     fclose (proc_stat);
643     proc_stat = NULL;
644   }
645 #elif OSX
646   GNUNET_free_non_null (prev_cpu_load);
647 #endif
648   if (NULL != sample_load_task_id)
649   {
650     GNUNET_SCHEDULER_cancel (sample_load_task_id);
651     sample_load_task_id = NULL;
652   }
653   GNUNET_break (GNUNET_OK == GNUNET_BIO_write_close (bw));
654   bw = NULL;
655 }
656
657 /* end of cpustatus.c */