Index: /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/CheckController.c
===================================================================
--- /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/CheckController.c	(revision 42885)
+++ /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/CheckController.c	(revision 42886)
@@ -1,6 +1,19 @@
 # include "pantasks.h"
 
-void TimerMark (struct timeval *start);
-float TimerElapsed (struct timeval *start, int reset);
+static int NjobsTotalSum = 0;
+static int NpassTotalSum = 0;
+static float NtimeTotalSum = 0.0;
+
+void CheckControllerStats (int reset) {
+
+  gprint (GP_LOG, "CC: Npass: %d, Njobs: %d, rate: %f\n",
+	  NpassTotalSum, NjobsTotalSum, NjobsTotalSum / NtimeTotalSum);
+
+  if (reset) {
+    NjobsTotalSum = 0;
+    NpassTotalSum = 0;
+    NtimeTotalSum = 0.0;
+  }
+}
 
 int CheckController () {
@@ -11,12 +24,15 @@
   Job *job;
   IOBuffer buffer;
-  struct timeval start;
 
   /* get the list of completed jobs (exit / crash), update the job status */
   if (!CheckControllerStatus()) return (TRUE);
 
+  // count the number of times this function was called while controller was active
+  NpassTotalSum ++;
+  
   /*** check EXIT jobs ***/
   InitIOBuffer (&buffer, 0x100);
 
+  struct timeval start;
   TimerMark (&start);
   FlushIOBuffer (&buffer);
@@ -79,4 +95,7 @@
   if (VerboseMode()) gprint (GP_ERR, "clear %d exit jobs %f\n", i, TimerElapsed(&start, TRUE));
 
+  // track the number of jobs actually cleared (might not equal number in the list)
+  int NjobsExit = i;
+
   /*** check CRASH jobs ***/
   FlushIOBuffer (&buffer);
@@ -130,4 +149,8 @@
   if (VerboseMode()) gprint (GP_ERR, "clear %d crash jobs %f\n", i, TimerElapsed(&start, TRUE)); 
 
+  // add in the crash jobs actually cleared (i, not Njobs) and increment the time
+  NjobsTotalSum += i + NjobsExit;
+  NtimeTotalSum += TimerElapsed(&start, TRUE); 
+
   FlushIOBuffer (&buffer);
   FreeIOBuffer (&buffer);
Index: /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/CheckJobs.c
===================================================================
--- /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/CheckJobs.c	(revision 42885)
+++ /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/CheckJobs.c	(revision 42886)
@@ -1,4 +1,20 @@
 # include "pantasks.h"
 static int Ncheck = 0;
+
+static int NjobsTotalSum = 0;
+static int NpassTotalSum = 0;
+static float NtimeTotalSum = 0.0;
+
+void CheckJobsStats (int reset) {
+
+  gprint (GP_LOG, "CJ: Npass: %d, Njobs: %d, rate: %f\n",
+	  NpassTotalSum, NjobsTotalSum, NjobsTotalSum / NtimeTotalSum);
+
+  if (reset) {
+    NjobsTotalSum = 0;
+    NpassTotalSum = 0;
+    NtimeTotalSum = 0.0;
+  }
+}
 
 float CheckJobs () {
@@ -15,10 +31,19 @@
   Ncheck ++;
 
+  // count the number of times this function was called while controller was active
+  NpassTotalSum ++;
+  
+  struct timeval start;
+  TimerMark (&start);
+
   // actual maximum delay is controlled in job_threads.c
   next_timeout = 1.0;
+
+  int NjobsCheck = 0;
 
   JobTaskLock();
   /** test all jobs: ready to test?  finished? **/
   while ((job = NextJob ()) != NULL) {
+    NjobsCheck ++;
 
     task = job[0].task;
@@ -221,4 +246,8 @@
 
   JobTaskUnlock();
+
+  NjobsTotalSum += NjobsCheck;
+  NtimeTotalSum += TimerElapsed(&start, TRUE); 
+
   return (next_timeout);
 }
Index: /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/CheckTasks.c
===================================================================
--- /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/CheckTasks.c	(revision 42885)
+++ /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/CheckTasks.c	(revision 42886)
@@ -1,4 +1,20 @@
 # include "pantasks.h"
 static int Ncheck = 0; 
+
+static int NtaskTotalSum = 0;
+static int NpassTotalSum = 0;
+static float NtimeTotalSum = 0.0;
+
+void CheckTasksStats (int reset) {
+
+  gprint (GP_LOG, "CT: Npass: %d, Ntask: %d, rate: %f\n",
+	  NpassTotalSum, NtaskTotalSum, NtaskTotalSum / NtimeTotalSum);
+
+  if (reset) {
+    NtaskTotalSum = 0;
+    NpassTotalSum = 0;
+    NtimeTotalSum = 0.0;
+  }
+}
 
 float CheckTasks () {
@@ -11,10 +27,19 @@
   Ncheck ++;
 
+  // count the number of times this function was called while controller was active
+  NpassTotalSum ++;
+  
+  struct timeval start;
+  TimerMark (&start);
+
   // actual maximum delay is controlled in job_threads.c
   next_timeout = 1.0;
+
+  int NtaskCheck = 0;
 
   JobTaskLock();
   /** test all tasks: ready to test? ready to run? **/
   while ((task = NextTask ()) != NULL) {
+    NtaskCheck ++;
 
     /*** test for all reasons we should skip this task ***/
@@ -60,4 +85,5 @@
       JobTaskUnlock();
       CommandLock();
+      // XXX measure time to execute this loop?
       status = exec_loop (task[0].exec);
       CommandUnlock();
@@ -71,4 +97,6 @@
     /* check if there are errors with this task */
     if (!ValidateTask (task, TRUE)) { 
+      // XXX increment Nskipexec here?  If we skip because the task.exec did not generate a valid
+      // command + host, then we need to know...
       continue;
     }
@@ -77,4 +105,5 @@
     job = CreateJob (task);
     if (!job) {
+      // this failure should not happen : jobID is not valid, probably a programming error
       continue;
     }
@@ -84,4 +113,6 @@
     /* execute job */
     if (!SubmitJob (job)) {
+      // this can only fail if there is a communication error with the controller, or if pcontrol cannot
+      // start, or if the host is not defined (but that is required to define a job on the controller)
       DeleteJob (job);
       continue;
@@ -93,4 +124,8 @@
   }
   JobTaskUnlock();
+
+  NtaskTotalSum += NtaskCheck;
+  NtimeTotalSum += TimerElapsed(&start, TRUE); 
+
   return (next_timeout);
 }
Index: /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/ControllerOps.c
===================================================================
--- /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/ControllerOps.c	(revision 42885)
+++ /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/ControllerOps.c	(revision 42886)
@@ -481,4 +481,20 @@
 }
 
+static int   NreadTotalSum = 0;
+static int   NpassTotalSum = 0;
+static float NtimeTotalSum = 0.0;
+
+void CheckControllerOutputStats (int reset) {
+
+  gprint (GP_LOG, "CO: Npass: %d, Nread: %d, rate: %f\n",
+	  NpassTotalSum, NreadTotalSum, NreadTotalSum / NtimeTotalSum);
+
+  if (reset) {
+    NreadTotalSum = 0;
+    NpassTotalSum = 0;
+    NtimeTotalSum = 0.0;
+  }
+}
+
 int CheckControllerOutput () {
 
@@ -486,4 +502,10 @@
 
   if (!ControllerStatus) return (TRUE);
+
+  struct timeval start;
+  TimerMark (&start);
+
+  // count the number of times this function was called while controller was active
+  NpassTotalSum ++;
 
   /* read stdout buffer */
@@ -510,4 +532,8 @@
       break;
   }
+
+  NreadTotalSum += stdout_buffer.Nbuffer + stderr_buffer.Nbuffer;
+  NtimeTotalSum += TimerElapsed(&start, TRUE); 
+
   return (TRUE);
 }
Index: /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/JobOps.c
===================================================================
--- /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/JobOps.c	(revision 42885)
+++ /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/JobOps.c	(revision 42886)
@@ -101,4 +101,5 @@
   if (job[0].JobID < 0) {
     free (job);
+    fprintf (stderr, "invalid JobID, programming error?\n");
     return NULL;
   }
Index: /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/Makefile
===================================================================
--- /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/Makefile	(revision 42885)
+++ /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/Makefile	(revision 42886)
@@ -32,4 +32,5 @@
 $(SRC)/run.$(ARCH).o \
 $(SRC)/stop.$(ARCH).o \
+$(SRC)/ptstats.$(ARCH).o \
 $(SRC)/pantasks.$(ARCH).o \
 $(SRC)/thread_locks.$(ARCH).o \
@@ -43,4 +44,5 @@
 $(SRC)/server.$(ARCH).o \
 $(SRC)/status_server.$(ARCH).o \
+$(SRC)/ptstats.$(ARCH).o \
 $(SRC)/init_server.$(ARCH).o \
 $(SRC)/CheckPassword.$(ARCH).o \
Index: /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/init.c
===================================================================
--- /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/init.c	(revision 42885)
+++ /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/init.c	(revision 42886)
@@ -25,4 +25,5 @@
 int verbose         PROTO((int, char **));
 int version         PROTO((int, char **));
+int ptstats         PROTO((int, char **));
 
 static Command cmds[] = {  
@@ -41,4 +42,5 @@
   {1, "run",        run,           "run the scheduler"},
   {1, "flush",      flush_jobs,    "flush all jobs from the queue"},
+  {1, "ptstats",    ptstats,       "show pantasks througput stats"},
   {1, "showtask",   showtask,      "list a task"},
   {1, "status",     status_sys,    "get system or task status"},
Index: /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/init_server.c
===================================================================
--- /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/init_server.c	(revision 42885)
+++ /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/init_server.c	(revision 42886)
@@ -22,4 +22,6 @@
 int verbose         PROTO((int, char **));
 int version         PROTO((int, char **));
+int ptstats         PROTO((int, char **));
+
 int server          PROTO((int, char **));
 
@@ -40,4 +42,5 @@
   {1, "npending",   task_npending, "define maximum number of outstanding jobs for a task"},
   {1, "periods",    task_periods,  "define time scales for a task"},
+  {1, "ptstats",    ptstats,       "show pantasks througput stats"},
   {1, "flush",      flush_jobs,    "flush all jobs from the queue"},
   {1, "server",     server,        "server-specific commands"},
Index: /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/ptstats.c
===================================================================
--- /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/ptstats.c	(revision 42886)
+++ /tags/ipp-unions-20250528/Ohana/src/opihi/pantasks/ptstats.c	(revision 42886)
@@ -0,0 +1,30 @@
+# include "pantasks.h"
+
+int ptstats (int argc, char **argv) {
+
+  int N;
+
+  if (get_argument (argc, argv, "-h")) goto help;
+  if (get_argument (argc, argv, "-help")) goto help;
+  if (get_argument (argc, argv, "--help")) goto help;
+
+  int Reset = FALSE;
+  if ((N = get_argument (argc, argv, "-reset"))) {
+    remove_argument (N, &argc, argv);
+    Reset = TRUE;
+  }
+
+  if (argc != 1) goto help;
+
+  CheckControllerOutputStats(Reset);
+  CheckControllerStats(Reset);
+  CheckJobsStats(Reset);
+  CheckTasksStats(Reset);
+
+  return (TRUE);
+
+
+help:
+  gprint (GP_LOG, "USAGE: ptstats [-reset]\n");
+  return (FALSE);
+}
