diff options
Diffstat (limited to 'winsup/cygwin/loadavg.cc')
-rw-r--r-- | winsup/cygwin/loadavg.cc | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/winsup/cygwin/loadavg.cc b/winsup/cygwin/loadavg.cc new file mode 100644 index 000000000..a7e5f61dd --- /dev/null +++ b/winsup/cygwin/loadavg.cc @@ -0,0 +1,192 @@ +/* loadavg.cc: load average support. + + This file is part of Cygwin. + + This software is a copyrighted work licensed under the terms of the + Cygwin license. Please consult the file "CYGWIN_LICENSE" for + details. */ + +/* + Emulate load average + + There's a fair amount of approximation done here, so don't try to use this to + actually measure anything, but it should be good enough for programs to + throttle their activity based on load. + + A global load average estimate is maintained in shared memory. Access to that + is guarded by a mutex. This estimate is only updated at most every 5 seconds. + + We attempt to count running and runnable processes, but unlike linux we don't + count processes in uninterruptible sleep (blocked on I/O). + + The number of running processes is estimated as (NumberOfProcessors) * (% + Processor Time). The number of runnable processes is estimated as + ProcessorQueueLength. + + Note that PDH will only return data for '% Processor Time' afer the second + call to PdhCollectQueryData(), as it's computed over an interval, so the first + attempt to estimate load will fail and 0.0 will be returned. + + We also assume that '% Processor Time' averaged over the interval since the + last time getloadavg() was called is a good approximation of the instantaneous + '% Processor Time'. +*/ + +#include "winsup.h" +#include "shared_info.h" +#include "loadavg.h" + +#include <math.h> +#include <time.h> +#include <sys/strace.h> + +/* Prototype for PdhAddEnglishCounterA in pdh.h under _WIN32_WINNT >= 0x0600 is + missing WINAPI */ +#undef _WIN32_WINNT +#include <pdh.h> +extern "C" +PDH_FUNCTION PdhAddEnglishCounterA(PDH_HQUERY hQuery, LPCSTR szFullCounterPath, + DWORD_PTR dwUserData, PDH_HCOUNTER *phCounter); + +static PDH_HQUERY query; +static PDH_HCOUNTER counter1; +static PDH_HCOUNTER counter2; +static HANDLE mutex; + +static bool load_init (void) +{ + static NO_COPY bool tried = false; + static NO_COPY bool initialized = false; + + if (!tried) { + tried = true; + + if (!((PdhOpenQueryA (NULL, 0, &query) == ERROR_SUCCESS) && + (PdhAddEnglishCounterA (query, "\\Processor(_Total)\\% Processor Time", + 0, &counter1) == ERROR_SUCCESS) && + (PdhAddEnglishCounterA (query, "\\System\\Processor Queue Length", + 0, &counter2) == ERROR_SUCCESS))) { + debug_printf("loadavg PDH initialization failed\n"); + return false; + } + + mutex = CreateMutex(&sec_all_nih, FALSE, "cyg.loadavg.mutex"); + if (!mutex) { + debug_printf("opening loadavg mutexfailed\n"); + return false; + } + + initialized = true; + } + + return initialized; +} + +/* estimate the current load */ +static bool get_load (double *load) +{ + *load = 0.0; + + PDH_STATUS ret = PdhCollectQueryData (query); + if (ret != ERROR_SUCCESS) + return false; + + /* Estimate the number of running processes as (NumberOfProcessors) * (% + Processor Time) */ + PDH_FMT_COUNTERVALUE fmtvalue1; + ret = PdhGetFormattedCounterValue (counter1, PDH_FMT_DOUBLE, NULL, &fmtvalue1); + if (ret != ERROR_SUCCESS) + return false; + + SYSTEM_INFO sysinfo; + GetSystemInfo (&sysinfo); + + double running = fmtvalue1.doubleValue * sysinfo.dwNumberOfProcessors / 100; + + /* Estimate the number of runnable processes using ProcessorQueueLength */ + PDH_FMT_COUNTERVALUE fmtvalue2; + ret = PdhGetFormattedCounterValue (counter2, PDH_FMT_LONG, NULL, &fmtvalue2); + if (ret != ERROR_SUCCESS) + return false; + + LONG rql = fmtvalue2.longValue; + + *load = rql + running; + return true; +} + +/* + loadavginfo shared-memory object +*/ + +void loadavginfo::initialize () +{ + for (int i = 0; i < 3; i++) + loadavg[i] = 0.0; + + last_time = 0; +} + +void loadavginfo::calc_load (int index, int delta_time, int decay_time, double n) +{ + double df = 1.0 / exp ((double)delta_time/decay_time); + loadavg[index] = (loadavg[index] * df) + (n * (1.0 - df)); +} + +void loadavginfo::update_loadavg () +{ + double active_tasks; + + if (!get_load (&active_tasks)) + return; + + /* Don't recalculate the load average if less than 5 seconds has elapsed since + the last time it was calculated */ + time_t curr_time = time (NULL); + int delta_time = curr_time - last_time; + if (delta_time < 5) { + return; + } + + if (last_time == 0) { + /* Initialize the load average to the current load */ + for (int i = 0; i < 3; i++) { + loadavg[i] = active_tasks; + } + } else { + /* Compute the exponentially weighted moving average over ... */ + calc_load (0, delta_time, 60, active_tasks); /* ... 1 min */ + calc_load (1, delta_time, 300, active_tasks); /* ... 5 min */ + calc_load (2, delta_time, 900, active_tasks); /* ... 15 min */ + } + + last_time = curr_time; +} + +int loadavginfo::fetch (double _loadavg[], int nelem) +{ + if (!load_init ()) + return 0; + + WaitForSingleObject(mutex, INFINITE); + + update_loadavg (); + + memcpy (_loadavg, loadavg, nelem * sizeof(double)); + + ReleaseMutex(mutex); + + return nelem; +} + +/* getloadavg: BSD */ +extern "C" int +getloadavg (double loadavg[], int nelem) +{ + /* The maximum number of samples is 3 */ + if (nelem > 3) + nelem = 3; + + /* Return the samples and number of samples retrieved */ + return cygwin_shared->loadavg.fetch(loadavg, nelem); +} |