Skip to content

Commit 9269a9c

Browse files
authored
Merge pull request #4322 from grondo/overlay-highlight
flux-overlay: replace --no-color with --color=WHEN option, add -H, --highlight=TARGETS
2 parents 79d0ad7 + e4f18f8 commit 9269a9c

File tree

3 files changed

+249
-16
lines changed

3 files changed

+249
-16
lines changed

src/cmd/builtin/overlay.c

Lines changed: 209 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
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;
2829
static const char *ansi_default = "\033[39m";
2930
static const char *ansi_red = "\033[31m";
3031
static 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";
3236
static 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[] = {
7785
struct 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

95105
struct 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
}
500620
done:
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+
558753
static 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

Comments
 (0)