v2: autoload PerfDataHelper functions Keep loadavg in shared memory Guard loadavg access by a mutex Initialize loadavg to the current load v3: Shared memory version bump isn't needed if we are only extending it Remove unused autoload Mark inititalized flags as NO_COPY for correct behaviour in fork child Signed-off-by: Jon Turney <jon.turney@dronecode.org.uk>
		
			
				
	
	
		
			193 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* 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);
 | 
						|
}
 |