@@ -29,6 +29,9 @@ static double default_timeout = 0.5;
2929static const char * ansi_default = "\033[39m" ;
3030static const char * ansi_red = "\033[31m" ;
3131static const char * ansi_yellow = "\033[33m" ;
32+ static const char * ansi_blue = "\033[01;34m" ;
33+ static const char * ansi_reset = "\033[0m" ;
34+
3235//static const char *ansi_green = "\033[32m";
3336static const char * ansi_dark_gray = "\033[90m" ;
3437
@@ -62,6 +65,9 @@ static struct optparse_option status_opts[] = {
6265 .usage = "Colorize output when supported; WHEN can be 'always' "
6366 "(default if omitted), 'never', or 'auto' (default)."
6467 },
68+ { .name = "highlight" , .key = 'H' , .has_arg = 1 , .arginfo = "TARGET" ,
69+ .usage = "Highlight one or more TARGETs and their ancestors."
70+ },
6571 { .name = "wait" , .key = 'w' , .has_arg = 1 , .arginfo = "STATE" ,
6672 .usage = "Wait until subtree enters STATE before reporting"
6773 " (full, partial, offline, degraded, lost)" ,
@@ -84,6 +90,7 @@ struct status {
8490 optparse_t * opt ;
8591 struct timespec start ;
8692 const char * wait ;
93+ struct idset * highlight ;
8794 zlistx_t * stack ;
8895};
8996
@@ -97,6 +104,7 @@ enum connector {
97104
98105struct status_node {
99106 int rank ;
107+ struct idset * subtree_ranks ;
100108 const char * status ;
101109 double duration ;
102110 bool ghost ;
@@ -242,15 +250,29 @@ static const char *status_indent (struct status *ctx, int n)
242250
243251/* Return string containing hostname and rank.
244252 */
245- static const char * status_getname (struct status * ctx , int rank )
253+ static const char * status_getname (struct status * ctx ,
254+ struct status_node * node )
246255{
247256 static char buf [128 ];
257+ const char * highlight_start = "" ;
258+ const char * highlight_end = "" ;
259+
260+ /* Highlight name if colorized output is enabled and this rank's
261+ * subtree (when known) intersects requested highlighted ranks:
262+ */
263+ if (node -> subtree_ranks
264+ && idset_has_intersection (ctx -> highlight , node -> subtree_ranks )) {
265+ highlight_start = ctx -> color ? ansi_blue : "<<" ;
266+ highlight_end = ctx -> color ? ansi_reset : ">>" ;
267+ }
248268
249269 snprintf (buf ,
250270 sizeof (buf ),
251- "%d %s" ,
252- rank ,
253- flux_get_hostbyrank (ctx -> h , rank ));
271+ "%s%d %s%s" ,
272+ highlight_start ,
273+ node -> rank ,
274+ flux_get_hostbyrank (ctx -> h , node -> rank ),
275+ highlight_end );
254276 return buf ;
255277}
256278
@@ -278,7 +300,7 @@ static void status_print (struct status *ctx,
278300 printf ("%s%s%s: %s%s%s\n" ,
279301 status_indent (ctx , level ),
280302 connector_string (connector ),
281- status_getname (ctx , node -> rank ),
303+ status_getname (ctx , node ),
282304 status_colorize (ctx , node -> status , node -> ghost ),
283305 status_duration (ctx , node -> duration ),
284306 parent ? status_rpctime (ctx ) : "" );
@@ -326,6 +348,86 @@ static json_t *topo_lookup (struct status *ctx,
326348 return topo ;
327349}
328350
351+ /* Recursive function to walk 'topology', adding all subtree ranks to 'ids'.
352+ * Returns 0 on success, -1 on failure (errno is not set).
353+ *
354+ * Note: Lifted directly from src/broker/groups.c
355+ */
356+ static int add_subtree_ids (struct idset * ids , json_t * topology )
357+ {
358+ int rank ;
359+ json_t * a ;
360+ size_t index ;
361+ json_t * entry ;
362+
363+ if (json_unpack (topology , "{s:i s:o}" , "rank" , & rank , "children" , & a ) < 0
364+ || idset_set (ids , rank ) < 0 )
365+ return -1 ;
366+ json_array_foreach (a , index , entry ) {
367+ if (add_subtree_ids (ids , entry ) < 0 )
368+ return -1 ;
369+ }
370+ return 0 ;
371+ }
372+
373+ /* Return an idset of ranks included in subtree 'topology'
374+ * (including root rank).
375+ */
376+ static struct idset * topology_subtree_ranks (json_t * topology )
377+ {
378+ struct idset * ids ;
379+
380+ if (!topology )
381+ return NULL ;
382+
383+ if (!(ids = idset_create (0 , IDSET_FLAG_AUTOGROW ))
384+ || add_subtree_ids (ids , topology ))
385+ goto error ;
386+ return ids ;
387+ error :
388+ idset_destroy (ids );
389+ return NULL ;
390+ }
391+
392+ /* Return the subtree topology rooted at 'subtree_rank'.
393+ */
394+ static json_t * get_subtree_topology (json_t * topo , int subtree_rank )
395+ {
396+ int rank ;
397+ json_t * a ;
398+ json_t * result ;
399+ size_t index ;
400+ json_t * entry ;
401+
402+ if (json_unpack (topo , "{s:i s:o}" , "rank" , & rank , "children" , & a ) < 0 )
403+ return NULL ;
404+ if (rank == subtree_rank )
405+ return topo ;
406+ json_array_foreach (a , index , entry ) {
407+ if (json_unpack (entry , "{s:i}" , "rank" , & rank ) < 0 )
408+ return NULL ;
409+ if (rank == subtree_rank )
410+ return entry ;
411+ else if ((result = get_subtree_topology (entry , subtree_rank )))
412+ return result ;
413+ }
414+ return NULL ;
415+ }
416+
417+ /* Return an idset of all ranks in the topology subtree rooted at 'rank'.
418+ */
419+ static struct idset * subtree_ranks (flux_t * h , int rank )
420+ {
421+ json_t * topo ;
422+ json_t * topology = get_topology (h );
423+
424+ if (!(topo = get_subtree_topology (topology , rank ))) {
425+ log_err ("get_subtree_topology" );
426+ return NULL ;
427+ }
428+ return topology_subtree_ranks (topo );
429+ }
430+
329431/* Walk a "ghost" subtree from the fixed topology. Each node is assumed to
330432 * have the same 'status' as the offline/lost parent at the subtree root.
331433 * This augments healthwalk() to fill in nodes that would otherwise be missing
@@ -349,6 +451,7 @@ static void status_ghostwalk (struct status *ctx,
349451 .duration = -1. , // invalid - don't print
350452 .ghost = true,
351453 .connector = connector ,
454+ .subtree_ranks = NULL ,
352455 };
353456
354457 if (json_unpack (topo , "{s:o}" , "children" , & children ) < 0 )
@@ -365,6 +468,9 @@ static void status_ghostwalk (struct status *ctx,
365468 status_prefix_push (ctx , PIPE );
366469 node .connector = TEE ;
367470 }
471+ if (!(node .subtree_ranks = topology_subtree_ranks (entry )))
472+ log_err_exit ("Unable to get subtree ranks for rank %d" ,
473+ node .rank );
368474 if (fun (ctx , & node , false, level + 1 ))
369475 status_ghostwalk (ctx ,
370476 entry ,
@@ -373,6 +479,7 @@ static void status_ghostwalk (struct status *ctx,
373479 node .connector ,
374480 fun );
375481 status_prefix_pop (ctx );
482+ idset_destroy (node .subtree_ranks );
376483 }
377484}
378485
@@ -425,13 +532,18 @@ static int status_healthwalk (struct status *ctx,
425532 enum connector connector ,
426533 map_f fun )
427534{
428- struct status_node node = { .ghost = false, .connector = connector };
535+ struct status_node node = {
536+ .ghost = false,
537+ .connector = connector ,
538+ .rank = rank
539+ };
429540 flux_future_t * f ;
430541 json_t * children ;
431542 const char * errstr ;
432543 int rc = 0 ;
433544
434545 monotime (& ctx -> start );
546+ node .subtree_ranks = NULL ;
435547
436548 if (!(f = health_rpc (ctx , rank , ctx -> wait , ctx -> timeout ))
437549 || flux_rpc_get_unpack (f ,
@@ -452,12 +564,13 @@ static int status_healthwalk (struct status *ctx,
452564 printf ("%s%s%s: %s%s\n" ,
453565 status_indent (ctx , level ),
454566 connector_string (connector ),
455- status_getname (ctx , rank ),
567+ status_getname (ctx , & node ),
456568 errstr ,
457569 status_rpctime (ctx ));
458570 rc = -1 ;
459571 goto done ;
460572 }
573+ node .subtree_ranks = subtree_ranks (ctx -> h , node .rank );
461574 if (fun (ctx , & node , true, level )) {
462575 if (children ) {
463576 size_t index ;
@@ -474,6 +587,9 @@ static int status_healthwalk (struct status *ctx,
474587 "status" , & child .status ,
475588 "duration" , & child .duration ) < 0 )
476589 log_msg_exit ("error parsing child array entry" );
590+ if (!(child .subtree_ranks = subtree_ranks (ctx -> h , child .rank )))
591+ log_err_exit ("Unable to get subtree idset for rank %d" ,
592+ child .rank );
477593 if (index == total - 1 ) {
478594 status_prefix_push (ctx , BLANK );
479595 connector = child .connector = ELBOW ;
@@ -497,10 +613,12 @@ static int status_healthwalk (struct status *ctx,
497613 }
498614 }
499615 status_prefix_pop (ctx );
616+ idset_destroy (child .subtree_ranks );
500617 }
501618 }
502619 }
503620done :
621+ idset_destroy (node .subtree_ranks );
504622 flux_future_destroy (f );
505623 return rc ;
506624}
@@ -576,6 +694,62 @@ static int status_use_color (optparse_t *p)
576694 return color ;
577695}
578696
697+ static struct idset * highlight_ranks (struct status * ctx , optparse_t * p )
698+ {
699+ const char * arg ;
700+ struct idset * ids ;
701+ struct idset * diff = NULL ;
702+ struct idset * allranks = NULL ;
703+ uint32_t size ;
704+
705+ if (flux_get_size (ctx -> h , & size ) < 0 )
706+ log_err_exit ("flux_get_size" );
707+
708+ if (!(allranks = idset_create (0 , IDSET_FLAG_AUTOGROW ))
709+ || idset_range_set (allranks , 0 , size - 1 ) < 0
710+ || !(ids = idset_create (0 , IDSET_FLAG_AUTOGROW )))
711+ log_err_exit ("Failed to create highlight idset" );
712+
713+ optparse_getopt_iterator_reset (p , "highlight" );
714+ while ((arg = optparse_getopt_next (p , "highlight" ))) {
715+ flux_error_t error ;
716+ struct idset * idset ;
717+ char * result ;
718+
719+ /* First, attempt to decode as idset. If that fails, assume
720+ * a hostlist was provided and lookup using the hostmap.
721+ */
722+ if (!(idset = idset_decode (arg ))) {
723+ if (!(result = flux_hostmap_lookup (ctx -> h , arg , & error )))
724+ log_msg_exit ("Error decoding %s: %s" , arg , error .text );
725+ if (!(idset = idset_decode (result )))
726+ log_err_exit ("Unable to decode %s" , arg );
727+ free (result );
728+ }
729+
730+ /* Accumulate ids in result idset
731+ */
732+ if (idset_add (ids , idset ) < 0 )
733+ log_err_exit ("Failed to append %s to highlight idset" , arg );
734+ idset_destroy (idset );
735+ }
736+
737+ /* Fail with error if any ranks in returned idset will fall outside
738+ * the range 0-size.
739+ */
740+ if (!(diff = idset_difference (ids , allranks )))
741+ log_err_exit ("Failed to determine validity of highlight idset" );
742+ if (idset_count (diff ) > 0 )
743+ log_msg_exit ("--highlight: rank%s %s not in set %s" ,
744+ idset_count (diff ) > 1 ? "s" : "" ,
745+ idset_encode (diff , IDSET_FLAG_RANGE ),
746+ idset_encode (allranks , IDSET_FLAG_RANGE ));
747+
748+ idset_destroy (diff );
749+ idset_destroy (allranks );
750+ return ids ;
751+ }
752+
579753static int subcmd_status (optparse_t * p , int ac , char * av [])
580754{
581755 int rank = optparse_get_int (p , "rank" , 0 );
@@ -594,6 +768,8 @@ static int subcmd_status (optparse_t *p, int ac, char *av[])
594768 log_msg_exit ("invalid --wait state" );
595769 if (!(ctx .stack = zlistx_new ()))
596770 log_msg_exit ("failed to create status zlistx" );
771+ if (!(ctx .highlight = highlight_ranks (& ctx , p )))
772+ log_msg_exit ("failed to create highlight idset" );
597773
598774 if (optparse_hasopt (p , "summary" ))
599775 fun = show_top ;
@@ -605,6 +781,7 @@ static int subcmd_status (optparse_t *p, int ac, char *av[])
605781 status_healthwalk (& ctx , rank , 0 , NIL , fun );
606782
607783 zlistx_destroy (& ctx .stack );
784+ idset_destroy (ctx .highlight );
608785 return 0 ;
609786}
610787
0 commit comments