Hi Mark, Not being a bugfix, this patch should go into the main branch and only update release/3.6.0.
Thanks, Corinna On Nov 12 22:21, Mark Geisert wrote: > This program provides an up-to-the-moment load average measurement. The > user can take 1 sample, or obtain the average of N samples by number or > time duration. There is a daemon mode to have the global load average > stats updated such that 'uptime' and other tools provide a more reasonable > load average calculation over time. > > A release note is provided for Cygwin 3.5.5. > Documentation for doc/utils.xml is pending. > > Reported-by: Mark Liam Brown <brownmarkl...@gmail.com> > Addresses: https://cygwin.com/pipermail/cygwin/2024-August/256361.html > Signed-off-by: Mark Geisert <m...@maxrnd.com> > Fixes: N/A (new code) > > --- > winsup/cygwin/release/3.5.5 | 8 + > winsup/utils/Makefile.am | 2 + > winsup/utils/loadavg.c | 380 ++++++++++++++++++++++++++++++++++++ > 3 files changed, 390 insertions(+) > create mode 100644 winsup/utils/loadavg.c > > diff --git a/winsup/cygwin/release/3.5.5 b/winsup/cygwin/release/3.5.5 > index a83ea7d8a..7db82631d 100644 > --- a/winsup/cygwin/release/3.5.5 > +++ b/winsup/cygwin/release/3.5.5 > @@ -1,3 +1,11 @@ > +What's New: > +----------- > + > +- New tool loadavg that provides up-to-the-moment load average measurement. > + It also has a daemon mode to continuously update the global load average. > + Addresses: https://cygwin.com/pipermail/cygwin/2024-August/256361.html > + > + > Fixes: > ------ > > diff --git a/winsup/utils/Makefile.am b/winsup/utils/Makefile.am > index 57a4f377c..3cb2f6bac 100644 > --- a/winsup/utils/Makefile.am > +++ b/winsup/utils/Makefile.am > @@ -25,6 +25,7 @@ bin_PROGRAMS = \ > gmondump \ > kill \ > ldd \ > + loadavg \ > locale \ > lsattr \ > minidumper \ > @@ -82,6 +83,7 @@ dumper_CXXFLAGS = -I$(top_srcdir)/../include $(AM_CXXFLAGS) > dumper_LDADD = $(LDADD) -lpsapi -lntdll -lbfd @BFD_LIBS@ > dumper_LDFLAGS = > ldd_LDADD = $(LDADD) -lpsapi -lntdll > +loadavg_LDADD = $(LDADD) -lpdh > mount_CXXFLAGS = -DFSTAB_ONLY $(AM_CXXFLAGS) > minidumper_LDADD = $(LDADD) -ldbghelp > pldd_LDADD = $(LDADD) -lpsapi > diff --git a/winsup/utils/loadavg.c b/winsup/utils/loadavg.c > new file mode 100644 > index 000000000..0bc3ffe1d > --- /dev/null > +++ b/winsup/utils/loadavg.c > @@ -0,0 +1,380 @@ > +/* > + loadavg.c > + Outputs to stdout an estimate of current cpu load > + > + Written by Mark Geisert <m...@maxrnd.com> > + h/t to Jon Turney for Cygwin's loadavg.cc which served as a model > + > + 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. > +*/ > + > +#define _GNU_SOURCE > +#include <fcntl.h> > +#include <math.h> > +#include <signal.h> > +#include <stdbool.h> > +#include <stdio.h> > +#include <unistd.h> > +#include <sys/cygwin.h> > +#include <sys/stat.h> > + > +#include <pdh.h> > + > +int once = 0; > +int samples = 0; > +int verbose = 0; > +SYSTEM_INFO sysinfo; > +#define PIDFILE "/var/run/loadavg.pid" > +#define PDHMSGFILE "/usr/include/w32api/pdhmsg.h" > + > +/* the following assumes standard 64Hz NT kernel counter update rate */ > +#define UPDATES_PER_MSEC 0.064 > +#define UPDATES_PER_SEC 64 > +#define UPDATES_PER_MIN 3840 > +#define UPDATES_PER_HOUR 230400 > + > +PDH_HQUERY query; > +PDH_HCOUNTER counter1; > +PDH_HCOUNTER counter2; > +#define c1name L"\\Processor(_Total)\\% Processor Time" > +#define c1namex L"\\Processor Information(_Total)\\% Processor Time" > +#define c2name L"\\System\\Processor Queue Length" > + > +void > +usage (void) > +{ > + printf ("Loadavg estimates current system load average by sampling certain > Windows\n"); > + printf ("kernel counters over time. The counters are updated 64 times per > second.\n"); > + printf ("\n"); > + printf ("Usage: loadavg take one sample\n"); > + printf ("\n"); > + printf (" loadavg [ -v ] <count> take <count> samples\n"); > + printf (" loadavg [ -v ] <number>h|m|s|ms take samples for > <number> duration\n"); > + printf (" (specifying -v displays every sample taken)\n"); > + printf ("\n"); > + printf (" loadavg -h this help message\n"); > + printf (" loadavg -d daemon (background) > mode\n"); > + printf (" (ensures 1/5/15-minute load averages computed for > 'uptime')\n"); > + printf (" loadavg -k terminates the > daemon\n"); > +} > + > +/* Convert PDH error status to PDH error name, if possible */ > +char * > +pdh_status (PDH_STATUS err) > +{ > + static FILE *f = NULL; > + static char buf[132]; > + static char hexcode[32]; > + char line[132]; > + char prefix[80]; > + char middle[80]; > + static char suffix[80]; > + > + snprintf (hexcode, sizeof hexcode, "0x%X", err); > + if (strstr (suffix, hexcode)) > + goto done; /* same as last time called */ > + > + if (f) > + (void) fseek (f, 0, SEEK_SET); > + else > + f = fopen (PDHMSGFILE, "r"); > + if (!f) > + goto bail; > + > + while (!feof (f)) { > + (void) fgets (line, (sizeof line) - 1, f); > + if (strncmp (line, "#define PDH_", 12)) > + continue; > + if (!strstr (line, hexcode)) > + continue; > + int num = sscanf (line, "%s %s %s", prefix, middle, suffix); > + if (num != 3) > + continue; > + snprintf (buf, sizeof buf, "returns %s", middle); > + goto done; > + } > + > +bail: > + snprintf (buf, sizeof buf, "returns %s", hexcode); > + > +done: > + return buf; > +} > + > +/* Initialize state for reading counters once or many times */ > +bool > +load_init (void) > +{ > + static bool tried = false; > + static bool initialized = false; > + > + if (!tried) > + { > + tried = true; > + > + PDH_STATUS ret = PdhOpenQueryW (NULL, 0, &query); > + if (ret != ERROR_SUCCESS) { > + fprintf (stderr, "PdhOpenQueryW %s\n", pdh_status (ret)); > + return false; > + } > + > + /* The Windows name for counter1 can vary.. look for both alternatives > */ > + ret = PdhAddEnglishCounterW (query, c1name, 0, &counter1); > + if (ret != ERROR_SUCCESS) { > + fprintf (stderr, "PdhAddEnglishCounterW#1 %s\n", pdh_status (ret)); > + ret = PdhAddEnglishCounterW (query, c1namex, 0, &counter1); > + if (ret != ERROR_SUCCESS) { > + fprintf (stderr, "PdhAddEnglishCounterW#1 %s\n", pdh_status (ret)); > + return false; > + } > + } > + > + /* What we call counter2 might be missing.. carry on anyway */ > + ret = PdhAddEnglishCounterW (query, c2name, 0, &counter2); > + if (ret != ERROR_SUCCESS) { > + fprintf (stderr, "PdhAddEnglishCounterW#2 %s\n", pdh_status (ret)); > + } > + > + GetSystemInfo (&sysinfo); /* get system number of processors, etc. */ > + initialized = true; > + > + /* obtain+discard first sample now; avoids PDH_INVALID_DATA in > get_load */ > + (void) PdhCollectQueryData (query); /* i.o.w. take the error here */ > + > + /* Delay a short time so PdhCQD in caller will have data to collect */ > + Sleep (16/*ms*/); /* let other procs run; one|more yield()s not enough > */ > + } > + > + return initialized; > +} > + > +/* estimate the current load based on certain counter values */ > +double > +get_load (void) > +{ > + PDH_STATUS ret = PdhCollectQueryData (query); > + if (ret != ERROR_SUCCESS) { > + fprintf (stderr, "PdhCollectQueryData %s\n", pdh_status (ret)); > + return 0.0; > + } > + > + /* 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) { > + fprintf (stderr, "PdhGetFormattedCounterValue#1 %s\n", pdh_status (ret)); > + return 0.0; > + } > + > + double ncpus = (double) sysinfo.dwNumberOfProcessors; > + if (verbose) { > + fprintf (stdout, "%llu ", GetTickCount64 ()); > + > + fprintf (stdout, "ncpus: %d, %%ptime: %3.2f, ", (int) ncpus, > + fmtvalue1.doubleValue); > + } > + double running = fmtvalue1.doubleValue * ncpus / 100.0; > + > + /* Estimate the number of runnable threads as > + (ProcessorQueueLength) / (NumberOfProcessors) */ > + double rql = 0.0; > + PDH_FMT_COUNTERVALUE fmtvalue2; > + ret = PdhGetFormattedCounterValue (counter2, PDH_FMT_LONG, NULL, > &fmtvalue2); > + if (ret == ERROR_SUCCESS) { > + rql = (double) fmtvalue2.longValue; > + rql /= ncpus; /* make the measure a per-cpu queue length */ > + } else { > + ++once; /* counter is missing; just print an error message once (below) > */ > + } > + > + double load = running + rql; > + if (verbose ) { > + fprintf (stdout, "running: %3.2f, effrql: %3.2f, load: %3.2f\n", > + running, rql, load); > + } > + if (once == 1) > + fprintf (stderr, "PdhGetFormattedCounterValue#2 %s\n", pdh_status (ret)); > + > + ++samples; > + return load; > +} > + > +int > +start_daemon (void) > +{ > + char buf[132]; > + int fd = -1; > + pid_t pid = 0; > + > +top: > + fd = open (PIDFILE, O_RDWR | O_CREAT | O_EXCL); > + if (fd == -1) { > + fd = open (PIDFILE, O_RDONLY); > + if (fd == -1) { > + fprintf (stderr, "unable to open pid file\n"); > + return 1; > + } > + > + memset (buf, 0, sizeof buf); > + read (fd, buf, sizeof buf); > + close (fd); > + > + pid = atoi (buf); > + /* does pid still exist? */ > + if (kill (pid, 0) == 0) { > + fprintf (stderr, "daemon already running as pid %d\n", pid); > + return 1; > + } > + unlink (PIDFILE); > + goto top; > + } > + fchmod (fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); > + > + (void) daemon (0, 0); > + > + /* this code runs in the daemon; the parent process has exited via > daemon() */ > + snprintf (buf, sizeof buf, "%d", getpid ()); > + write (fd, buf, strlen (buf)); > + /* don't close(fd).. keep it open to protect against another daemon */ > + > + double loadavg[3]; > + while (1) { > + (void) getloadavg (loadavg, 3); > + sleep (5/*seconds*/); > + } > + return 0; > +} > + > +int > +stop_daemon (void) > +{ > + char buf[132]; > + int fd = -1; > + ssize_t len = 0; > + pid_t pid = 0; > + char *winpath = NULL; > + > + fd = open (PIDFILE, O_RDONLY); > + if (fd == -1) { > + fprintf (stderr, "unable to open pid file\n"); > + return 1; > + } > + memset (buf, 0, sizeof buf); > + read (fd, buf, sizeof buf); > + close (fd); > + > + pid = atoi (buf); > + /* does pid still exist? */ > + if (kill (pid, 0) == -1) { > + fprintf (stderr, "daemon pid %d already gone\n", pid); > + unlink (PIDFILE); > + return 1; > + } > + > + /* using kill() to terminate the daemon fails (why?); work around that */ > + winpath = getenv ("SYSTEMROOT"); > + len = cygwin_conv_path (CCP_WIN_A_TO_POSIX | CCP_PROC_CYGDRIVE, > + winpath, buf, sizeof buf); > + len = strlen (buf); > + snprintf (&buf[len], (sizeof buf) - len, > + "/System32/taskkill /f /pid `cat /proc/%d/winpid`", pid); > + fprintf (stdout, "Windows says: "); > + fflush (stdout); > + system (buf); > + > + /* if pid is gone, delete pid file */ > + if (kill (pid, 0) == -1) > + unlink (PIDFILE); > + return 0; > +} > + > +int > +main (int argc, char **argv) > +{ > + (void) setvbuf (stdout, NULL, _IOLBF, 120); > + if (!load_init ()) > + return 1; > + > + /* If program launched with no args, print load once and exit; else loop */ > + if (argc == 1) > + fprintf (stdout, "%3.2f\n", get_load ()); > + else { > + int arg = 1; > + > + while (arg < argc && argv[arg][0] == '-') { > + switch (argv[arg][1]) { > + case '\000': > + goto bail; > + > + case 'd': > + if (arg != 1 || (arg != argc - 1)) > + goto bail; > + return start_daemon (); > + > + case 'k': > + if (arg != 1 || (arg != argc - 1)) > + goto bail; > + return stop_daemon (); > + > + case 'h': > + usage (); > + return 0; > + > + case 'v': > + ++verbose; > + break; > + } > + ++arg; > + } > + if (arg != argc - 1) { > +bail: > + usage (); > + return 1; > + } > + > + /* deal with last arg whether it's a number or a duration */ > + int count; > + double number = 0.0; > + char *ptr = argv[arg]; > + char units[80]; > + > + units[0] = '\000'; > + int num = sscanf (ptr, "%lg%s", &number, units); > + switch (num) { > + case 1: /* arg looks like just a number */ > + if (number > 0.0 && !strlen (units)) > + goto ready; > + // fallthru > + case 0: /* arg looks like garbage of some kind */ > + goto bail; > + > + case 2: /* arg looks like it could be a duration */ > + if (!strcmp (units, "h")) > + number *= UPDATES_PER_HOUR; > + else if (!strcmp (units, "m")) > + number *= UPDATES_PER_MIN; > + else if (!strcmp (units, "s")) > + number *= UPDATES_PER_SEC; > + else if (!strcmp (units, "ms")) > + number *= UPDATES_PER_MSEC; > + else > + goto bail; > + // fallthru > + } > + > +ready: > + count = (int) ceil (number); > + double totload = 0.0; > + while (samples < count) { > + totload += get_load (); > + Sleep (11/*ms*/); /* tested best at capturing a sample every 15|16ms */ > + } > + fprintf (stdout, "%3.2f\n", totload / samples); > + } > + > + return 0; > +} > -- > 2.45.1