1111#if HAVE_CONFIG_H
1212# include <config.h>
1313#endif
14+ #include <unistd.h>
1415#include <jansson.h>
1516#include <flux/core.h>
1617
@@ -28,6 +29,9 @@ static double default_timeout = 0.5;
2829static const char * ansi_default = "\033[39m" ;
2930static const char * ansi_red = "\033[31m" ;
3031static const char * ansi_yellow = "\033[33m" ;
32+ static const char * ansi_blue = "\033[01;34m" ;
33+ static const char * ansi_reset = "\033[0m" ;
34+
3135//static const char *ansi_green = "\033[32m";
3236static const char * ansi_dark_gray = "\033[90m" ;
3337
@@ -57,8 +61,12 @@ static struct optparse_option status_opts[] = {
5761 .usage = "Do not fill in presumed state of nodes that are"
5862 " inaccessible behind offline/lost overlay parents" ,
5963 },
60- { .name = "no-color" , .has_arg = 0 ,
61- .usage = "Do not use color to highlight offline/lost nodes" ,
64+ { .name = "color" , .key = 'L' , .has_arg = 2 , .arginfo = "WHEN" ,
65+ .usage = "Colorize output when supported; WHEN can be 'always' "
66+ "(default if omitted), 'never', or 'auto' (default)."
67+ },
68+ { .name = "highlight" , .key = 'H' , .has_arg = 1 , .arginfo = "TARGET" ,
69+ .usage = "Highlight one or more TARGETs and their ancestors."
6270 },
6371 { .name = "wait" , .key = 'w' , .has_arg = 1 , .arginfo = "STATE" ,
6472 .usage = "Wait until subtree enters STATE before reporting"
@@ -77,10 +85,12 @@ static struct optparse_option disconnect_opts[] = {
7785struct status {
7886 flux_t * h ;
7987 int verbose ;
88+ int color ;
8089 double timeout ;
8190 optparse_t * opt ;
8291 struct timespec start ;
8392 const char * wait ;
93+ struct idset * highlight ;
8494 zlistx_t * stack ;
8595};
8696
@@ -94,6 +104,7 @@ enum connector {
94104
95105struct status_node {
96106 int rank ;
107+ struct idset * subtree_ranks ;
97108 const char * status ;
98109 double duration ;
99110 bool ghost ;
@@ -173,7 +184,7 @@ static const char *status_colorize (struct status *ctx,
173184{
174185 static char buf [128 ];
175186
176- if (! optparse_hasopt ( ctx -> opt , "no- color" ) ) {
187+ if (ctx -> color ) {
177188 if (streq (status , "lost" ) && !ghost ) {
178189 snprintf (buf , sizeof (buf ), "%s%s%s" ,
179190 ansi_red , status , ansi_default );
@@ -239,15 +250,29 @@ static const char *status_indent (struct status *ctx, int n)
239250
240251/* Return string containing hostname and rank.
241252 */
242- static const char * status_getname (struct status * ctx , int rank )
253+ static const char * status_getname (struct status * ctx ,
254+ struct status_node * node )
243255{
244256 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+ }
245268
246269 snprintf (buf ,
247270 sizeof (buf ),
248- "%d %s" ,
249- rank ,
250- 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 );
251276 return buf ;
252277}
253278
@@ -275,7 +300,7 @@ static void status_print (struct status *ctx,
275300 printf ("%s%s%s: %s%s%s\n" ,
276301 status_indent (ctx , level ),
277302 connector_string (connector ),
278- status_getname (ctx , node -> rank ),
303+ status_getname (ctx , node ),
279304 status_colorize (ctx , node -> status , node -> ghost ),
280305 status_duration (ctx , node -> duration ),
281306 parent ? status_rpctime (ctx ) : "" );
@@ -323,6 +348,86 @@ static json_t *topo_lookup (struct status *ctx,
323348 return topo ;
324349}
325350
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+
326431/* Walk a "ghost" subtree from the fixed topology. Each node is assumed to
327432 * have the same 'status' as the offline/lost parent at the subtree root.
328433 * This augments healthwalk() to fill in nodes that would otherwise be missing
@@ -346,6 +451,7 @@ static void status_ghostwalk (struct status *ctx,
346451 .duration = -1. , // invalid - don't print
347452 .ghost = true,
348453 .connector = connector ,
454+ .subtree_ranks = NULL ,
349455 };
350456
351457 if (json_unpack (topo , "{s:o}" , "children" , & children ) < 0 )
@@ -362,6 +468,9 @@ static void status_ghostwalk (struct status *ctx,
362468 status_prefix_push (ctx , PIPE );
363469 node .connector = TEE ;
364470 }
471+ if (!(node .subtree_ranks = topology_subtree_ranks (entry )))
472+ log_err_exit ("Unable to get subtree ranks for rank %d" ,
473+ node .rank );
365474 if (fun (ctx , & node , false, level + 1 ))
366475 status_ghostwalk (ctx ,
367476 entry ,
@@ -370,6 +479,7 @@ static void status_ghostwalk (struct status *ctx,
370479 node .connector ,
371480 fun );
372481 status_prefix_pop (ctx );
482+ idset_destroy (node .subtree_ranks );
373483 }
374484}
375485
@@ -422,13 +532,18 @@ static int status_healthwalk (struct status *ctx,
422532 enum connector connector ,
423533 map_f fun )
424534{
425- struct status_node node = { .ghost = false, .connector = connector };
535+ struct status_node node = {
536+ .ghost = false,
537+ .connector = connector ,
538+ .rank = rank
539+ };
426540 flux_future_t * f ;
427541 json_t * children ;
428542 const char * errstr ;
429543 int rc = 0 ;
430544
431545 monotime (& ctx -> start );
546+ node .subtree_ranks = NULL ;
432547
433548 if (!(f = health_rpc (ctx , rank , ctx -> wait , ctx -> timeout ))
434549 || flux_rpc_get_unpack (f ,
@@ -449,12 +564,13 @@ static int status_healthwalk (struct status *ctx,
449564 printf ("%s%s%s: %s%s\n" ,
450565 status_indent (ctx , level ),
451566 connector_string (connector ),
452- status_getname (ctx , rank ),
567+ status_getname (ctx , & node ),
453568 errstr ,
454569 status_rpctime (ctx ));
455570 rc = -1 ;
456571 goto done ;
457572 }
573+ node .subtree_ranks = subtree_ranks (ctx -> h , node .rank );
458574 if (fun (ctx , & node , true, level )) {
459575 if (children ) {
460576 size_t index ;
@@ -471,6 +587,9 @@ static int status_healthwalk (struct status *ctx,
471587 "status" , & child .status ,
472588 "duration" , & child .duration ) < 0 )
473589 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 );
474593 if (index == total - 1 ) {
475594 status_prefix_push (ctx , BLANK );
476595 connector = child .connector = ELBOW ;
@@ -494,10 +613,12 @@ static int status_healthwalk (struct status *ctx,
494613 }
495614 }
496615 status_prefix_pop (ctx );
616+ idset_destroy (child .subtree_ranks );
497617 }
498618 }
499619 }
500620done :
621+ idset_destroy (node .subtree_ranks );
501622 flux_future_destroy (f );
502623 return rc ;
503624}
@@ -555,6 +676,80 @@ static bool validate_wait (const char *wait)
555676 return true;
556677}
557678
679+ static int status_use_color (optparse_t * p )
680+ {
681+ const char * when ;
682+ int color ;
683+
684+ if (!(when = optparse_get_str (p , "color" , "auto" )))
685+ when = "always" ;
686+ if (streq (when , "always" ))
687+ color = 1 ;
688+ else if (streq (when , "never" ))
689+ color = 0 ;
690+ else if (streq (when , "auto" ))
691+ color = isatty (STDOUT_FILENO ) ? 1 : 0 ;
692+ else
693+ log_msg_exit ("Invalid argument to --color: '%s'" , when );
694+ return color ;
695+ }
696+
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+
558753static int subcmd_status (optparse_t * p , int ac , char * av [])
559754{
560755 int rank = optparse_get_int (p , "rank" , 0 );
@@ -563,6 +758,7 @@ static int subcmd_status (optparse_t *p, int ac, char *av[])
563758
564759 ctx .h = builtin_get_flux_handle (p );
565760 ctx .verbose = optparse_get_int (p , "verbose" , 0 );
761+ ctx .color = status_use_color (p );
566762 ctx .timeout = optparse_get_duration (p , "timeout" , default_timeout );
567763 if (ctx .timeout == 0 )
568764 ctx .timeout = -1.0 ; // disabled
@@ -572,6 +768,8 @@ static int subcmd_status (optparse_t *p, int ac, char *av[])
572768 log_msg_exit ("invalid --wait state" );
573769 if (!(ctx .stack = zlistx_new ()))
574770 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" );
575773
576774 if (optparse_hasopt (p , "summary" ))
577775 fun = show_top ;
@@ -583,6 +781,7 @@ static int subcmd_status (optparse_t *p, int ac, char *av[])
583781 status_healthwalk (& ctx , rank , 0 , NIL , fun );
584782
585783 zlistx_destroy (& ctx .stack );
784+ idset_destroy (ctx .highlight );
586785 return 0 ;
587786}
588787
0 commit comments