/*
This file is part of GNUnet.
Copyright (C) 2008--2013 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
GNUnet is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
SPDX-License-Identifier: AGPL3.0-or-later
*/
/**
* @file testbed/gnunet-service-testbed_cpustatus.c
* @brief calls to determine current CPU load
* @author Tzvetan Horozov
* @author Christian Grothoff
* @author Igor Wronsky
* @author Alex Harper (OS X portion)
* @author Sree Harsha Totakura
*/
#include "platform.h"
#include "gnunet_util_lib.h"
#include "gnunet-service-testbed_meminfo.h"
#if SOLARIS
#if HAVE_KSTAT_H
#include
#endif
#if HAVE_SYS_SYSINFO_H
#include
#endif
#if HAVE_KVM_H
#include
#endif
#endif
#ifdef BSD
#if HAVE_KVM_H
#include
#endif
#endif
#ifdef OSX
#include
static processor_cpu_load_info_t prev_cpu_load;
#endif
#define DEBUG_STATUSCALLS GNUNET_NO
#ifdef __linux__
static FILE *proc_stat;
#endif
/**
* Current CPU load, as percentage of CPU cycles not idle or
* blocked on IO.
*/
static int currentCPULoad;
static double agedCPULoad = -1;
/**
* Current IO load, as percentage of CPU cycles blocked on IO.
*/
static int currentIOLoad;
static double agedIOLoad = -1;
/**
* hanlde to the file to write the load statistics to
*/
struct GNUNET_BIO_WriteHandle *bw;
struct GNUNET_SCHEDULER_Task *sample_load_task_id;
#ifdef OSX
static int
initMachCpuStats ()
{
unsigned int cpu_count;
processor_cpu_load_info_t cpu_load;
mach_msg_type_number_t cpu_msg_count;
kern_return_t kret;
int i, j;
kret = host_processor_info (mach_host_self (),
PROCESSOR_CPU_LOAD_INFO,
&cpu_count,
(processor_info_array_t *) &cpu_load,
&cpu_msg_count);
if (kret != KERN_SUCCESS)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "host_processor_info failed.");
return GNUNET_SYSERR;
}
prev_cpu_load = GNUNET_malloc (cpu_count * sizeof(*prev_cpu_load));
for (i = 0; i < cpu_count; i++)
{
for (j = 0; j < CPU_STATE_MAX; j++)
{
prev_cpu_load[i].cpu_ticks[j] = cpu_load[i].cpu_ticks[j];
}
}
vm_deallocate (mach_task_self (),
(vm_address_t) cpu_load,
(vm_size_t) (cpu_msg_count * sizeof(*cpu_load)));
return GNUNET_OK;
}
#endif
/**
* Update the currentCPU and currentIO load (and on Linux, memory) values.
*
* Before its first invocation the method initStatusCalls() must be called.
* If there is an error the method returns -1.
*/
static int
updateUsage ()
{
currentIOLoad = -1;
currentCPULoad = -1;
#ifdef __linux__
/* under linux, first try %idle/usage using /proc/stat;
if that does not work, disable /proc/stat for the future
by closing the file and use the next-best method. */
if (proc_stat != NULL)
{
static unsigned long long last_cpu_results[5] = { 0, 0, 0, 0, 0 };
static int have_last_cpu = GNUNET_NO;
int ret;
char line[256];
unsigned long long user_read, system_read, nice_read, idle_read,
iowait_read;
unsigned long long user, system, nice, idle, iowait;
unsigned long long usage_time = 0, total_time = 1;
/* Get the first line with the data */
rewind (proc_stat);
fflush (proc_stat);
if (NULL == fgets (line, 256, proc_stat))
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"fgets", "/proc/stat");
proc_stat = NULL; /* don't try again */
}
else
{
iowait_read = 0;
ret = sscanf (line, "%*s %llu %llu %llu %llu %llu",
&user_read,
&system_read, &nice_read, &idle_read, &iowait_read);
if (ret < 4)
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"fgets-sscanf", "/proc/stat");
fclose (proc_stat);
proc_stat = NULL; /* don't try again */
have_last_cpu = GNUNET_NO;
}
else
{
/* Store the current usage */
user = user_read - last_cpu_results[0];
system = system_read - last_cpu_results[1];
nice = nice_read - last_cpu_results[2];
idle = idle_read - last_cpu_results[3];
iowait = iowait_read - last_cpu_results[4];
/* Calculate the % usage */
usage_time = user + system + nice;
total_time = usage_time + idle + iowait;
if ((total_time > 0) && (have_last_cpu == GNUNET_YES))
{
currentCPULoad = (int) (100L * usage_time / total_time);
if (ret > 4)
currentIOLoad = (int) (100L * iowait / total_time);
else
currentIOLoad = -1; /* 2.4 kernel */
}
/* Store the values for the next calculation */
last_cpu_results[0] = user_read;
last_cpu_results[1] = system_read;
last_cpu_results[2] = nice_read;
last_cpu_results[3] = idle_read;
last_cpu_results[4] = iowait_read;
have_last_cpu = GNUNET_YES;
return GNUNET_OK;
}
}
}
#endif
#ifdef OSX
{
unsigned int cpu_count;
processor_cpu_load_info_t cpu_load;
mach_msg_type_number_t cpu_msg_count;
unsigned long long t_sys, t_user, t_nice, t_idle, t_total;
unsigned long long t_idle_all, t_total_all;
kern_return_t kret;
int i, j;
t_idle_all = t_total_all = 0;
kret = host_processor_info (mach_host_self (), PROCESSOR_CPU_LOAD_INFO,
&cpu_count,
(processor_info_array_t *) &cpu_load,
&cpu_msg_count);
if (kret == KERN_SUCCESS)
{
for (i = 0; i < cpu_count; i++)
{
if (cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM] >=
prev_cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM])
{
t_sys = cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM]
- prev_cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM];
}
else
{
t_sys = cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM]
+ (ULONG_MAX - prev_cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM]
+ 1);
}
if (cpu_load[i].cpu_ticks[CPU_STATE_USER] >=
prev_cpu_load[i].cpu_ticks[CPU_STATE_USER])
{
t_user = cpu_load[i].cpu_ticks[CPU_STATE_USER]
- prev_cpu_load[i].cpu_ticks[CPU_STATE_USER];
}
else
{
t_user = cpu_load[i].cpu_ticks[CPU_STATE_USER]
+ (ULONG_MAX - prev_cpu_load[i].cpu_ticks[CPU_STATE_USER]
+ 1);
}
if (cpu_load[i].cpu_ticks[CPU_STATE_NICE] >=
prev_cpu_load[i].cpu_ticks[CPU_STATE_NICE])
{
t_nice = cpu_load[i].cpu_ticks[CPU_STATE_NICE]
- prev_cpu_load[i].cpu_ticks[CPU_STATE_NICE];
}
else
{
t_nice = cpu_load[i].cpu_ticks[CPU_STATE_NICE]
+ (ULONG_MAX - prev_cpu_load[i].cpu_ticks[CPU_STATE_NICE]
+ 1);
}
if (cpu_load[i].cpu_ticks[CPU_STATE_IDLE] >=
prev_cpu_load[i].cpu_ticks[CPU_STATE_IDLE])
{
t_idle = cpu_load[i].cpu_ticks[CPU_STATE_IDLE]
- prev_cpu_load[i].cpu_ticks[CPU_STATE_IDLE];
}
else
{
t_idle = cpu_load[i].cpu_ticks[CPU_STATE_IDLE]
+ (ULONG_MAX - prev_cpu_load[i].cpu_ticks[CPU_STATE_IDLE]
+ 1);
}
t_total = t_sys + t_user + t_nice + t_idle;
t_idle_all += t_idle;
t_total_all += t_total;
}
for (i = 0; i < cpu_count; i++)
{
for (j = 0; j < CPU_STATE_MAX; j++)
{
prev_cpu_load[i].cpu_ticks[j] = cpu_load[i].cpu_ticks[j];
}
}
if (t_total_all > 0)
currentCPULoad = 100 - (100 * t_idle_all) / t_total_all;
else
currentCPULoad = -1;
vm_deallocate (mach_task_self (),
(vm_address_t) cpu_load,
(vm_size_t) (cpu_msg_count * sizeof(*cpu_load)));
currentIOLoad = -1; /* FIXME-OSX! */
return GNUNET_OK;
}
else
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "host_processor_info failed.");
return GNUNET_SYSERR;
}
}
#endif
/* try kstat (Solaris only) */
#if SOLARIS && HAVE_KSTAT_H && HAVE_SYS_SYSINFO_H
{
static long long last_idlecount;
static long long last_totalcount;
static int kstat_once; /* if open fails, don't keep
trying */
kstat_ctl_t *kc;
kstat_t *khelper;
long long idlecount;
long long totalcount;
long long deltaidle;
long long deltatotal;
if (kstat_once == 1)
goto ABORT_KSTAT;
kc = kstat_open ();
if (kc == NULL)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kstat_close");
goto ABORT_KSTAT;
}
idlecount = 0;
totalcount = 0;
for (khelper = kc->kc_chain; khelper != NULL; khelper = khelper->ks_next)
{
cpu_stat_t stats;
if (0 != strncmp (khelper->ks_name, "cpu_stat", strlen ("cpu_stat")))
continue;
if (khelper->ks_data_size > sizeof(cpu_stat_t))
continue; /* better save then sorry! */
if (-1 != kstat_read (kc, khelper, &stats))
{
idlecount += stats.cpu_sysinfo.cpu[CPU_IDLE];
totalcount
+= stats.cpu_sysinfo.cpu[CPU_IDLE]
+ stats.cpu_sysinfo.cpu[CPU_USER]
+ stats.cpu_sysinfo.cpu[CPU_KERNEL]
+ stats.cpu_sysinfo.cpu[CPU_WAIT];
}
}
if (0 != kstat_close (kc))
GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kstat_close");
if ((idlecount == 0) && (totalcount == 0))
goto ABORT_KSTAT; /* no stats found => abort */
deltaidle = idlecount - last_idlecount;
deltatotal = totalcount - last_totalcount;
if ((deltatotal > 0) && (last_totalcount > 0))
{
currentCPULoad = (unsigned int) (100.0 * deltaidle / deltatotal);
if (currentCPULoad > 100)
currentCPULoad = 100; /* odd */
if (currentCPULoad < 0)
currentCPULoad = 0; /* odd */
currentCPULoad = 100 - currentCPULoad; /* computed idle-load before! */
}
else
currentCPULoad = -1;
currentIOLoad = -1; /* FIXME-SOLARIS! */
last_idlecount = idlecount;
last_totalcount = totalcount;
return GNUNET_OK;
ABORT_KSTAT:
kstat_once = 1; /* failed, don't try again */
return GNUNET_SYSERR;
}
#endif
/* insert methods better than getloadavg for
other platforms HERE! */
/* ok, maybe we have getloadavg on this platform */
#if HAVE_GETLOADAVG
{
static int warnOnce = 0;
double loadavg;
if (1 != getloadavg (&loadavg, 1))
{
/* only warn once, if there is a problem with
getloadavg, we're going to hit it frequently... */
if (warnOnce == 0)
{
warnOnce = 1;
GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "getloadavg");
}
return GNUNET_SYSERR;
}
else
{
/* success with getloadavg */
currentCPULoad = (int) (100 * loadavg);
currentIOLoad = -1; /* FIXME */
return GNUNET_OK;
}
}
#endif
/* loadaverage not defined and no platform
specific alternative defined
=> default: error
*/
return GNUNET_SYSERR;
}
/**
* Update load values (if enough time has expired),
* including computation of averages. Code assumes
* that lock has already been obtained.
*/
static void
updateAgedLoad ()
{
static struct GNUNET_TIME_Absolute lastCall;
struct GNUNET_TIME_Relative age;
age = GNUNET_TIME_absolute_get_duration (lastCall);
if ((agedCPULoad == -1)
|| (age.rel_value_us > 500000))
{
/* use smoothing, but do NOT update lastRet at frequencies higher
than 500ms; this makes the smoothing (mostly) independent from
the frequency at which getCPULoad is called (and we don't spend
more time measuring CPU than actually computing something). */
lastCall = GNUNET_TIME_absolute_get ();
updateUsage ();
if (currentCPULoad == -1)
{
agedCPULoad = -1;
}
else
{
if (agedCPULoad == -1)
{
agedCPULoad = currentCPULoad;
}
else
{
/* for CPU, we don't do the 'fast increase' since CPU is much
more jitterish to begin with */
agedCPULoad = (agedCPULoad * 31 + currentCPULoad) / 32;
}
}
if (currentIOLoad == -1)
{
agedIOLoad = -1;
}
else
{
if (agedIOLoad == -1)
{
agedIOLoad = currentIOLoad;
}
else
{
/* for IO, we don't do the 'fast increase' since IO is much
more jitterish to begin with */
agedIOLoad = (agedIOLoad * 31 + currentIOLoad) / 32;
}
}
}
}
/**
* Get the load of the CPU relative to what is allowed.
* @return the CPU load as a percentage of allowed
* (100 is equivalent to full load)
*/
static int
cpu_get_load ()
{
updateAgedLoad ();
return (int) agedCPULoad;
}
/**
* Get the load of the CPU relative to what is allowed.
* @return the CPU load as a percentage of allowed
* (100 is equivalent to full load)
*/
static int
disk_get_load ()
{
updateAgedLoad ();
return (int) agedIOLoad;
}
/**
* Get the percentage of memory used
*
* @return the percentage of memory used
*/
static unsigned int
mem_get_usage ()
{
double percentage;
meminfo ();
percentage = (((double) kb_main_used) / ((double) kb_main_total) * 100.0);
return (unsigned int) percentage;
}
#ifdef __linux__
#include
/**
* Returns the number of processes
*
* @return the number of processes
*/
static unsigned int
get_nproc ()
{
DIR *dir;
struct dirent *ent;
unsigned int nproc;
dir = opendir ("/proc");
if (NULL == dir)
return 0;
nproc = 0;
while (NULL != (ent = readdir (dir)))
{
if ((*ent->d_name > '0') && (*ent->d_name <= '9'))
nproc++;
}
closedir (dir);
return nproc;
}
#endif
static void
sample_load_task (void *cls)
{
struct GNUNET_TIME_Absolute now;
char *str;
int nbs;
int ld_cpu;
int ld_disk;
unsigned int mem_usage;
unsigned int nproc;
sample_load_task_id = NULL;
ld_cpu = cpu_get_load ();
ld_disk = disk_get_load ();
if ((-1 == ld_cpu) || (-1 == ld_disk))
goto reschedule;
mem_usage = mem_get_usage ();
#ifdef __linux__
nproc = get_nproc ();
#else
nproc = 0;
#endif
now = GNUNET_TIME_absolute_get ();
nbs = GNUNET_asprintf (&str, "%llu %d %d %u %u\n", now.abs_value_us / 1000LL
/ 1000LL,
ld_cpu, ld_disk, mem_usage, nproc);
if (0 < nbs)
{
GNUNET_BIO_write (bw, "sample load task", str, nbs);
}
else
GNUNET_break (0);
GNUNET_free (str);
reschedule:
sample_load_task_id =
GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
&sample_load_task, NULL);
}
/**
* Initialize logging CPU and IO statisticfs. Checks the configuration for
* "STATS_DIR" and logs to a file in that directory. The file is name is
* generated from the hostname and the process's PID.
*/
void
GST_stats_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
char *hostname;
char *stats_dir;
char *fn;
size_t len;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (cfg, "testbed",
"STATS_DIR", &stats_dir))
return;
len = GNUNET_OS_get_hostname_max_length ();
hostname = GNUNET_malloc (len);
if (0 != gethostname (hostname, len))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "gethostname");
GNUNET_free (stats_dir);
GNUNET_free (hostname);
return;
}
fn = NULL;
(void) GNUNET_asprintf (&fn, "%s/%.*s-%jd.dat", stats_dir, len,
hostname, (intmax_t) getpid ());
GNUNET_free (stats_dir);
GNUNET_free (hostname);
if (NULL == (bw = GNUNET_BIO_write_open_file (fn)))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_ ("Cannot open %s for writing load statistics. "
"Not logging load statistics\n"), fn);
GNUNET_free (fn);
return;
}
GNUNET_free (fn);
sample_load_task_id = GNUNET_SCHEDULER_add_now (&sample_load_task, NULL);
#ifdef __linux__
proc_stat = fopen ("/proc/stat", "r");
if (NULL == proc_stat)
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"fopen", "/proc/stat");
#elif OSX
initMachCpuStats ();
#endif
updateUsage (); /* initialize */
}
/**
* Shutdown the status calls module.
*/
void
GST_stats_destroy ()
{
if (NULL == bw)
return;
#ifdef __linux__
if (proc_stat != NULL)
{
fclose (proc_stat);
proc_stat = NULL;
}
#elif OSX
GNUNET_free (prev_cpu_load);
#endif
if (NULL != sample_load_task_id)
{
GNUNET_SCHEDULER_cancel (sample_load_task_id);
sample_load_task_id = NULL;
}
GNUNET_break (GNUNET_OK == GNUNET_BIO_write_close (bw, NULL));
bw = NULL;
}
/* end of cpustatus.c */