Skip to content

Commit c99a3ed

Browse files
grondomergify[bot]
authored andcommitted
flux-overlay: Add -H, --highlight option to status subcommand
Problem: It would occasionally be useful to highlight the overlay subtrees which connect a given set of hosts or ranks, but the flux-overlay status subcommand does not currently include this feature. Add a `-H, --highlight=TARGETS` option to `flux overlay status` which will hightlight the name of ranks where the idset of ranks in the subtree intersects with the provided target ranks or hostnames.
1 parent 18dbae6 commit c99a3ed

File tree

1 file changed

+184
-7
lines changed

1 file changed

+184
-7
lines changed

src/cmd/builtin/overlay.c

Lines changed: 184 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ static double default_timeout = 0.5;
2929
static const char *ansi_default = "\033[39m";
3030
static const char *ansi_red = "\033[31m";
3131
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+
3235
//static const char *ansi_green = "\033[32m";
3336
static 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

98105
struct 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
}
503620
done:
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+
579753
static 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

Comments
 (0)