| /* 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); |
| } |