[PATCH] Cygwin: New tool loadavg to maintain load averages
Mark Geisert
mark@maxrnd.com
Wed Oct 9 05:20:17 GMT 2024
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.
Reported-by: Mark Liam Brown <brownmarkliam@gmail.com>
Addresses: https://cygwin.com/pipermail/cygwin/2024-August/256361.html
Signed-off-by: Mark Geisert <mark@maxrnd.com>
Fixes: N/A (new code)
---
winsup/utils/Makefile.am | 2 +
winsup/utils/loadavg.c | 366 +++++++++++++++++++++++++++++++++++++++
2 files changed, 368 insertions(+)
create mode 100644 winsup/utils/loadavg.c
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..5b1e2127f
--- /dev/null
+++ b/winsup/utils/loadavg.c
@@ -0,0 +1,363 @@
+/*
+ loadavg.c
+ Outputs to stdout an estimate of current cpu load
+
+ Written by Mark Geisert <mark@maxrnd.com>.
+ h/t to Jon Turney for Cygwin's loadavg.cc which served as 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;
+#define PIDFILE "/var/run/loadavg.pid"
+#define PDHMSGFILE "/usr/include/w32api/pdhmsg.h"
+
+/* the following assumes 15ms NT kernel counter update rate */
+#define COUNTS_PER_MSEC 0.066666666666666666
+#define COUNTS_PER_SEC 66.666666666666666666
+#define COUNTS_PER_MIN 4000
+#define COUNTS_PER_HOUR 240000
+
+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 ("\n");
+ printf ("Loadavg estimates and displays current load average by sampling over time.\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");
+}
+
+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;
+}
+
+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;
+ }
+ }
+
+ /* The Windows name for 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));
+ }
+
+ initialized = true;
+
+ /* prime the data pump, evidently */
+ (void) PdhCollectQueryData (query);
+ Sleep (15);
+ }
+
+ return initialized;
+}
+
+/* estimate the current load */
+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;
+ }
+
+ SYSTEM_INFO sysinfo;
+ GetSystemInfo (&sysinfo);
+ 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 using ProcessorQueueLength */
+ 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;
+
+ 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);
+ fprintf (stderr, "daemon already running as pid %s\n", buf);
+ close (fd);
+ return 1;
+ }
+ fchmod (fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ (void) daemon (0, 0);
+ 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 (5000);
+ }
+ 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; 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 = ~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 *= COUNTS_PER_HOUR;
+ else if (!strcmp (units, "m"))
+ number *= COUNTS_PER_MIN;
+ else if (!strcmp (units, "s"))
+ number *= COUNTS_PER_SEC;
+ else if (!strcmp (units, "ms"))
+ number *= COUNTS_PER_MSEC;
+ else
+ goto bail;
+ // fallthru
+ }
+
+ready:
+ count = (int) ceil (number);
+ double totload = 0.0;
+ while (samples < count) {
+ totload += get_load ();
+ Sleep (12); /* 12 seems to work better than canonical 15 here */
+ }
+ fprintf (stdout, "%3.2f\n", totload / samples);
+ }
+
+ return 0;
+}
--
2.45.1
More information about the Cygwin-patches
mailing list