@@ -15,6 +15,23 @@ component accessors="true" singleton {
1515 */
1616 property name = " name" ;
1717
18+ /**
19+ * The created date time
20+ * This is set when the executor is created
21+ */
22+ property name = " created" type = " date" ;
23+
24+ /**
25+ * The last activity date time
26+ * This happens when a task is submitted
27+ */
28+ property name = " lastActivity" type = " date" ;
29+
30+ /**
31+ * Task submission count
32+ */
33+ property name = " taskSubmissionCount" type = " numeric" default = 0 ;
34+
1835 /**
1936 * The native Java executor class modeled in this executor
2037 */
@@ -59,6 +76,15 @@ component accessors="true" singleton {
5976 }
6077 };
6178
79+ variables .healthThresholds = {
80+ " poolUtilization" : { " degraded" : 75 , " critical" : 95 },
81+ " threadUtilization" : { " degraded" : 75 , " critical" : 95 },
82+ " queueUtilization" : { " degraded" : 70 , " critical" : 95 },
83+ " taskCompletionRate" : { " degraded" : 50 , " critical" : 25 },
84+ " inactivityMinutes" : 30 ,
85+ " minimumTasksForCompletion" : 10
86+ };
87+
6288 /**
6389 * Constructor
6490 *
@@ -80,6 +106,9 @@ component accessors="true" singleton {
80106 variables .debug = arguments .debug ;
81107 variables .loadAppContext = arguments .loadAppContext ;
82108 variables .shutdownTimeout = arguments .shutdownTimeout ;
109+ variables .created = now ();
110+ variables .lastActivity = variables .created ;
111+ variables .taskSubmissionCount = 0 ;
83112
84113 return this ;
85114 }
@@ -109,6 +138,10 @@ component accessors="true" singleton {
109138 [ " java.util.concurrent.Callable" ]
110139 );
111140
141+ // Update the last activity
142+ variables .lastActivity = now ();
143+ variables .taskSubmissionCount ++ ;
144+
112145 // Send for execution
113146 return new coldbox .system .async .tasks .FutureTask ( variables .native .submit ( jCallable ) );
114147 }
@@ -157,6 +190,17 @@ component accessors="true" singleton {
157190 return variables .native .isShutdown ();
158191 }
159192
193+ /**
194+ * Check if executor is healthy (simple boolean check)
195+ * Uses current stats to determine health
196+ *
197+ * @return boolean True if status is "healthy" or "idle"
198+ */
199+ boolean function isHealthy (){
200+ var stats = getStats ();
201+ return listFindNoCase ( " healthy,idle" , stats .healthStatus ) > 0 ;
202+ }
203+
160204 /**
161205 * Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or
162206 * the current thread is interrupted, whichever happens first.
@@ -312,20 +356,260 @@ component accessors="true" singleton {
312356 * @return struct of data about the executor and the schedule
313357 */
314358 struct function getStats (){
315- return {
359+ var uptimeSeconds = dateDiff ( " s" , variables .created , now () );
360+ var stats = {
361+ " created" : dateTimeFormat ( variables .created , " iso" ),
362+ " features" : variables .features [ variables .native .getClass ().getSimpleName () ],
363+ " lastActivity" : dateTimeFormat ( variables .lastActivity , " iso" ),
364+ " lastActivityMinutesAgo" : dateDiff ( " n" , variables .lastActivity , now () ),
365+ " lastActivitySecondsAgo" : dateDiff ( " s" , variables .lastActivity , now () ),
316366 " name" : getName (),
317- " poolSize" : getPoolSize (),
318- " maximumPoolSize" : getMaximumPoolSize (),
319- " largestPoolSize" : getLargestPoolSize (),
320- " corePoolSize" : getCorePoolSize (),
321- " completedTaskCount" : getCompletedTaskCount (),
322- " taskCount" : getTaskCount (),
323- " activeCount" : getActiveCount (),
367+ " thresholds" : variables .healthThresholds ,
368+ " type" : variables .native .getClass ().getName (),
369+ " uptimeDays" : dateDiff ( " d" , variables .created , now () ),
370+ " uptimeSeconds" : uptimeSeconds ,
371+ // Pool Stats
372+ " corePoolSize" : 0 ,
373+ " largestPoolSize" : 0 ,
374+ " maximumPoolSize" : 0 ,
375+ " poolSize" : 0 ,
376+ " poolUtilization" : 0 ,
377+ // Task Stats
378+ " activeCount" : 0 ,
379+ " allowsCoreThreadTimeOut" : false ,
380+ " averageTasksPerMinute" : uptimeSeconds > 0 ? ( variables .taskSubmissionCount / uptimeSeconds ) * 60 : 0 ,
381+ " averageTasksPerSecond" : uptimeSeconds > 0 ? variables .taskSubmissionCount / uptimeSeconds : 0 ,
382+ " completedTaskCount" : 0 ,
383+ " keepAliveTimeoutInSeconds" : 0 ,
384+ " taskCompletionRate" : 0 ,
385+ " taskCount" : 0 ,
386+ " taskSubmissionCount" : variables .taskSubmissionCount ,
387+ " threadsUtilization" : 0 ,
388+ // States
389+ " isShutdown" : isShutdown (),
324390 " isTerminated" : isTerminated (),
325391 " isTerminating" : isTerminating (),
326- " isShutdown" : isShutdown (),
327- " type" : variables .native .getClass ().getName (),
328- " queue" : getQueue ().toString ()
392+ // Queue Stats
393+ " queueCapacity" : 0 ,
394+ " queueIsEmpty" : 0 ,
395+ " queueIsFull" : 0 ,
396+ " queueRemainingCapacity" : 0 ,
397+ " queueSize" : 0 ,
398+ " queueType" : 0 ,
399+ " queueUtilization" : 0
400+ };
401+
402+ // Pool Stats
403+ if ( hasFeature ( " pool" ) ){
404+ stats [ " corePoolSize" ] = getCorePoolSize ();
405+ stats [ " largestPoolSize" ] = getLargestPoolSize ();
406+ stats [ " maximumPoolSize" ] = getMaximumPoolSize ();
407+ stats [ " poolSize" ] = getPoolSize ();
408+ stats [ " poolUtilization" ] = getMaximumPoolSize () > 0 ? ( getPoolSize () / getMaximumPoolSize () ) * 100 : 0 ;
409+ }
410+
411+ // Task Stats
412+ if ( hasFeature ( " taskMethods" ) ){
413+ stats [ " activeCount" ] = getActiveCount ();
414+ stats [ " allowsCoreThreadTimeOut" ] = variables .native .allowsCoreThreadTimeOut ();
415+ stats [ " completedTaskCount" ] = getCompletedTaskCount ();
416+ stats [ " keepAliveTimeoutInSeconds" ] = variables .native .getKeepAliveTime ( this .$timeUnit .get ( " seconds" ) );
417+ stats [ " taskCompletionRate" ] = getTaskCount () > 0 ? ( getCompletedTaskCount () / getTaskCount () ) * 100 : 0 ;
418+ stats [ " taskCount" ] = getTaskCount ();
419+ stats [ " threadsUtilization" ] = getPoolSize () > 0 ? ( getActiveCount () / getPoolSize () ) * 100 : 0 ;
420+ }
421+
422+ // Queue Stats
423+ if ( hasFeature ( " queue" ) ){
424+ stats [ " queueCapacity" ] = getQueue ().size () + getQueue ().remainingCapacity ();
425+ stats [ " queueIsEmpty" ] = getQueue ().isEmpty ();
426+ // Check unbounded queues
427+ stats [ " queueIsFull" ] = stats .maximumPoolSize >= 2147483647
428+ ? false
429+ : ( getQueue ().remainingCapacity () == 0 );
430+ stats [ " queueRemainingCapacity" ] = getQueue ().remainingCapacity ();
431+ stats [ " queueSize" ] = getQueue ().size ();
432+ stats [ " queueType" ] = getQueue ().getClass ().getSimpleName ();
433+ stats [ " queueUtilization" ] = stats .queueCapacity > 0 ? ( stats .queueSize / stats .queueCapacity ) * 100 : 0 ;
434+ }
435+
436+ // Add health status to stats (this must come last after all stats are calculated)
437+ stats [ " healthStatus" ] = getHealthStatus ( stats );
438+ stats [ " healthReport" ] = getHealthReport ( stats );
439+
440+ return stats ;
441+ }
442+
443+ /**
444+ * This functions needs to use standard heuristics to determine a health status of the executor.
445+ * It should return a value of "healthy", "degraded", "critical", "draining".
446+ *
447+ * @return string The health status of the executor
448+ */
449+ private function getHealthStatus ( required struct stats ){
450+ var thresholds = variables .healthThresholds ;
451+ var criticalIssues = [];
452+ var degradedIssues = [];
453+
454+ // Shutdown/termination states (highest priority)
455+ if ( arguments .stats .isTerminated ) return " terminated" ;
456+ if ( arguments .stats .isShutdown ) return " shutdown" ;
457+ if ( arguments .stats .isTerminating ) return " draining" ;
458+
459+ // Critical health issues
460+ if ( arguments .stats .queueIsFull ) {
461+ criticalIssues .append ( " queue_full" );
462+ }
463+
464+ if ( arguments .stats .poolUtilization > thresholds .poolUtilization .critical ) {
465+ criticalIssues .append ( " pool_exhausted" );
466+ }
467+
468+ if ( arguments .stats .threadsUtilization > thresholds .threadUtilization .critical ) {
469+ criticalIssues .append ( " threads_exhausted" );
470+ }
471+
472+ // Queue utilization critical
473+ if ( arguments .stats .queueUtilization > thresholds .queueUtilization .critical ) {
474+ criticalIssues .append ( " queue_near_full" );
475+ }
476+
477+ // Task completion rate critical
478+ if ( arguments .stats .taskCount >= thresholds .minimumTasksForCompletion &&
479+ arguments .stats .taskCompletionRate < thresholds .taskCompletionRate .critical ) {
480+ criticalIssues .append ( " task_completion_critical" );
481+ }
482+
483+ // Degraded health issues
484+ if ( arguments .stats .poolUtilization > thresholds .poolUtilization .degraded ) {
485+ degradedIssues .append ( " high_pool_usage" );
486+ }
487+
488+ if ( arguments .stats .threadsUtilization > thresholds .threadUtilization .degraded ) {
489+ degradedIssues .append ( " high_thread_usage" );
490+ }
491+
492+ // Queue utilization degraded
493+ if ( arguments .stats .queueUtilization > thresholds .queueUtilization .degraded ) {
494+ degradedIssues .append ( " queue_backing_up" );
495+ }
496+
497+ // Task completion rate degraded
498+ if ( arguments .stats .taskCount >= thresholds .minimumTasksForCompletion &&
499+ arguments .stats .taskCompletionRate < thresholds .taskCompletionRate .degraded ) {
500+ degradedIssues .append ( " task_completion_degraded" );
501+ }
502+
503+ // Check for idle state (no activity, no work)
504+ if ( arguments .stats .lastActivityMinutesAgo > thresholds .inactivityMinutes &&
505+ arguments .stats .activeCount == 0 &&
506+ arguments .stats .queueSize == 0 &&
507+ ! arguments .stats .isShutdown &&
508+ ! arguments .stats .isTerminating ) {
509+ return " idle" ;
510+ }
511+
512+ // Return status based on issues found
513+ if ( criticalIssues .len () > 0 ) return " critical" ;
514+ if ( degradedIssues .len () > 0 ) return " degraded" ;
515+
516+ return " healthy" ;
517+ }
518+
519+ /**
520+ * Provides a comprehensive health report with detailed analysis, issues, and recommendations
521+ * Uses the provided stats to generate the report
522+ *
523+ * @stats struct The stats structure to analyze for health report
524+ *
525+ * @return struct Detailed health report
526+ */
527+ struct function getHealthReport ( required struct stats ){
528+ var status = arguments .stats .healthStatus ;
529+ var thresholds = variables .healthThresholds ;
530+ var issues = [];
531+ var recommendations = [];
532+ var alerts = [];
533+
534+ // Analyze pool utilization
535+ if ( arguments .stats .poolUtilization > thresholds .poolUtilization .critical ) {
536+ issues .append (" Critical pool utilization: #numberFormat (arguments .stats .poolUtilization , ' 0.0' ) #%" );
537+ recommendations .append (" Immediately increase maximum pool size or reduce workload" );
538+ alerts .append ({ " level" : " critical" , " metric" : " poolUtilization" , " value" : arguments .stats .poolUtilization });
539+ } else if ( arguments .stats .poolUtilization > thresholds .poolUtilization .degraded ) {
540+ issues .append (" High pool utilization: #numberFormat (arguments .stats .poolUtilization , ' 0.0' ) #%" );
541+ recommendations .append (" Consider increasing maximum pool size" );
542+ alerts .append ({ " level" : " warning" , " metric" : " poolUtilization" , " value" : arguments .stats .poolUtilization });
543+ }
544+
545+ // Analyze thread utilization
546+ if ( arguments .stats .threadsUtilization > thresholds .threadUtilization .critical ) {
547+ issues .append (" Critical thread utilization: #numberFormat (arguments .stats .threadsUtilization , ' 0.0' ) #%" );
548+ recommendations .append (" All threads busy - consider increasing pool size or optimizing tasks" );
549+ alerts .append ({ " level" : " critical" , " metric" : " threadsUtilization" , " value" : arguments .stats .threadsUtilization });
550+ } else if ( arguments .stats .threadsUtilization > thresholds .threadUtilization .degraded ) {
551+ issues .append (" High thread utilization: #numberFormat (arguments .stats .threadsUtilization , ' 0.0' ) #%" );
552+ recommendations .append (" Monitor thread usage patterns and consider capacity planning" );
553+ alerts .append ({ " level" : " warning" , " metric" : " threadsUtilization" , " value" : arguments .stats .threadsUtilization });
554+ }
555+
556+ // Analyze queue health
557+ if ( arguments .stats .queueIsFull ) {
558+ issues .append (" Queue is full: #arguments .stats .queueSize #/#arguments .stats .queueCapacity # capacity" );
559+ recommendations .append (" Queue rejecting tasks - increase capacity or improve processing speed" );
560+ alerts .append ({ " level" : " critical" , " metric" : " queueFull" , " value" : true });
561+ } else if ( arguments .stats .queueUtilization > thresholds .queueUtilization .critical ) {
562+ issues .append (" Queue near capacity: #numberFormat (arguments .stats .queueUtilization , ' 0.0' ) #% (#arguments .stats .queueSize #/#arguments .stats .queueCapacity #)" );
563+ recommendations .append (" Queue filling up - monitor for processing bottlenecks" );
564+ alerts .append ({ " level" : " critical" , " metric" : " queueUtilization" , " value" : arguments .stats .queueUtilization });
565+ } else if ( arguments .stats .queueUtilization > thresholds .queueUtilization .degraded ) {
566+ issues .append (" Queue utilization elevated: #numberFormat (arguments .stats .queueUtilization , ' 0.0' ) #% (#arguments .stats .queueSize #/#arguments .stats .queueCapacity #)" );
567+ recommendations .append (" Monitor queue growth trends" );
568+ alerts .append ({ " level" : " warning" , " metric" : " queueUtilization" , " value" : arguments .stats .queueUtilization });
569+ }
570+
571+ // Analyze task completion rates
572+ if ( arguments .stats .taskCount >= thresholds .minimumTasksForCompletion ) {
573+ if ( arguments .stats .taskCompletionRate < thresholds .taskCompletionRate .critical ) {
574+ issues .append (" Very low task completion rate: #numberFormat (arguments .stats .taskCompletionRate , ' 0.0' ) #%" );
575+ recommendations .append (" Investigate task failures or performance issues" );
576+ alerts .append ({ " level" : " critical" , " metric" : " taskCompletionRate" , " value" : arguments .stats .taskCompletionRate });
577+ } else if ( arguments .stats .taskCompletionRate < thresholds .taskCompletionRate .degraded ) {
578+ issues .append (" Low task completion rate: #numberFormat (arguments .stats .taskCompletionRate , ' 0.0' ) #%" );
579+ recommendations .append (" Monitor task success patterns" );
580+ alerts .append ({ " level" : " warning" , " metric" : " taskCompletionRate" , " value" : arguments .stats .taskCompletionRate });
581+ }
582+ }
583+
584+ // Analyze activity patterns
585+ if ( status == " idle" ) {
586+ issues .append (" Executor idle for #arguments .stats .lastActivityMinutesAgo # minutes" );
587+ recommendations .append (" Verify if inactivity is expected or indicates a problem" );
588+ }
589+
590+ // Performance insights
591+ var insights = [];
592+ if ( arguments .stats .averageTasksPerSecond > 0 ) {
593+ insights .append (" Processing rate: #numberFormat (arguments .stats .averageTasksPerSecond , ' 0.00' ) # tasks/second" );
594+ }
595+ if ( arguments .stats .uptimeDays > 0 ) {
596+ insights .append (" Uptime: #arguments .stats .uptimeDays # days" );
597+ }
598+
599+ // Resource efficiency analysis
600+ if ( arguments .stats .poolSize > 0 && arguments .stats .averageTasksPerSecond > 0 ) {
601+ var tasksPerThread = arguments .stats .averageTasksPerSecond / arguments .stats .poolSize ;
602+ insights .append (" Efficiency: #numberFormat (tasksPerThread , ' 0.00' ) # tasks/second per thread" );
603+ }
604+
605+ return {
606+ " status" : status ,
607+ " summary" : issues .len () == 0 ? " Executor operating normally" : " #issues .len () # issue#issues .len () == 1 ? ' ' : ' s' # detected" ,
608+ " issues" : issues ,
609+ " recommendations" : recommendations ,
610+ " alerts" : alerts ,
611+ " insights" : insights ,
612+ " lastChecked" : dateTimeFormat (now (), " iso" )
329613 };
330614 }
331615
0 commit comments