feat(xce-test-cli-ux): add JSON output mode to the listing commands
Bring workerlist/joblist/reslist onto the shared flagspec_t library with an identically-spelled --json flag, wired to the task-2.2 JSON list formatter via a new pure field-mapping module (listrow_json.c/.h). Default table output is unchanged; JSON rows carry raw numeric values (flags, timestamps, memory sizes) rather than their humanized display strings, per Requirement 5.1's "same fields" contract. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019jaMf9QEwpugH2W9KAuc7f
This commit is contained in:
parent
a57c04716b
commit
0d67f5cbde
7 changed files with 996 additions and 16 deletions
|
|
@ -67,6 +67,12 @@
|
|||
* (xce-test-cli-ux task 2.5 remediation) */
|
||||
#include "join_worker_args.h" /* join_worker_parse_attrs (xce-test-cli-ux
|
||||
* task 2.6 remediation, bug-1557) */
|
||||
#include "listfmt_json.h" /* listfmt_json_begin/new_row/finish -- JSON list
|
||||
* output for workerlist/joblist/reslist
|
||||
* (xce-test-cli-ux task 2.2, wired in task 3.4) */
|
||||
#include "listrow_json.h" /* reslist_json_row/workerlist_json_row/
|
||||
* joblist_json_row -- per-command JSON field
|
||||
* mapping (xce-test-cli-ux task 3.4) */
|
||||
#include "rcfile.h" /* startup rc-file path resolution + loading (xce-test-rc-file task 2.2) */
|
||||
#include "resval_typeresolve.h" /* resval_resolve_type (resource-value-verification task 2.1) */
|
||||
#include "xce/cluster.h" /* xce_parse_peers, xce_cluster_config_t */
|
||||
|
|
@ -444,14 +450,16 @@ static const char USAGE_HELLO[] =
|
|||
"example: hello -c 3 -m \"ping\"\n";
|
||||
|
||||
static const char USAGE_RESLIST[] =
|
||||
"reslist\n"
|
||||
" (no arguments)\n"
|
||||
"reslist [--json]\n"
|
||||
" --json emit each resource as a JSON object (same fields as the\n"
|
||||
" table) instead of the human-readable table\n"
|
||||
" Prints all cluster-wide resources (name, flags, interval,\n"
|
||||
" description).\n";
|
||||
|
||||
static const char USAGE_WORKERLIST[] =
|
||||
"workerlist\n"
|
||||
" (no arguments)\n"
|
||||
"workerlist [--json]\n"
|
||||
" --json emit each worker as a JSON object (same fields as the\n"
|
||||
" table) instead of the human-readable table\n"
|
||||
" Prints all joined workers (host, status, host type/model, cpu\n"
|
||||
" factor, max cpu/mem/swap/tmp, ndisks).\n";
|
||||
|
||||
|
|
@ -482,10 +490,11 @@ static const char USAGE_UPGRADECONFIG[] =
|
|||
"example: upgradeconfig /var/xce/cluster.bin\n";
|
||||
|
||||
static const char USAGE_JOBLIST[] =
|
||||
"joblist\n"
|
||||
" (no arguments)\n"
|
||||
"joblist [--json]\n"
|
||||
" --json emit each job as a JSON object (same fields as the table)\n"
|
||||
" instead of the human-readable table\n"
|
||||
" Prints all jobs in the cluster (JobId, user, queue, state, times,\n"
|
||||
" ntasks, command) and captures the count into $jobcount.\n";
|
||||
" mintasks/maxtasks, command) and captures the count into $jobcount.\n";
|
||||
|
||||
static const char USAGE_EXPECT_JOBS[] =
|
||||
"expect-jobs <N>\n"
|
||||
|
|
@ -2002,12 +2011,53 @@ static int reslist_thunk(void* ctxp)
|
|||
return call->rc;
|
||||
}
|
||||
|
||||
/* xce-test-cli-ux task 3.4, Requirement 5.1/5.2, 6.2: reslist's first-ever
|
||||
* flag, --json (long-only -- no short form is required by the
|
||||
* requirements and none is added here). Same long_opt spelling ("json")
|
||||
* and concept ("json-output") as workerlist_flags/joblist_flags below --
|
||||
* Requirement 6.2 mandates identical naming for the same concept across
|
||||
* every command that has it. */
|
||||
enum { RESLIST_OPT_JSON = 0 };
|
||||
|
||||
static const flagspec_t reslist_flags[] = {
|
||||
{ 0, "json", "json-output", no_argument, 0, "human-readable table" },
|
||||
};
|
||||
#define RESLIST_NFLAGS (sizeof(reslist_flags) / sizeof(reslist_flags[0]))
|
||||
|
||||
int cmd_reslist(int argc, char* argv[])
|
||||
{
|
||||
int rc, i;
|
||||
int opt;
|
||||
int json_mode = 0;
|
||||
xce_resource_t** res;
|
||||
reslist_call_t* call = (reslist_call_t*)calloc(1, sizeof(*call));
|
||||
reslist_call_t* call;
|
||||
struct option long_opts[RESLIST_NFLAGS + 1];
|
||||
char optstring[8];
|
||||
|
||||
optind = 1;
|
||||
opterr = 0;
|
||||
|
||||
if (flagspec_build_opts(reslist_flags, RESLIST_NFLAGS, long_opts,
|
||||
optstring, sizeof(optstring))
|
||||
< 0) {
|
||||
fprintf(stderr,
|
||||
"xce_test: reslist: internal error building flag table\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
while ((opt = getopt_long(argc, argv, optstring, long_opts, NULL))
|
||||
!= -1) {
|
||||
switch (opt) {
|
||||
case FLAGSPEC_LONG_ONLY_BASE + RESLIST_OPT_JSON:
|
||||
json_mode = 1;
|
||||
break;
|
||||
case '?':
|
||||
return flagspec_reject("reslist", reslist_flags, RESLIST_NFLAGS,
|
||||
argv[optind - 1], "reslist [--json]");
|
||||
}
|
||||
}
|
||||
|
||||
call = (reslist_call_t*)calloc(1, sizeof(*call));
|
||||
if (call == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
|
@ -2032,6 +2082,32 @@ int cmd_reslist(int argc, char* argv[])
|
|||
return rc;
|
||||
}
|
||||
|
||||
if (json_mode) {
|
||||
/* xce-test-cli-ux task 3.4, Requirement 5.1/5.3: emit the SAME
|
||||
* fields the table below prints (Name, Flags, Interval,
|
||||
* Description), unabbreviated, as one JSON object per resource.
|
||||
* reslist_json_row() (listrow_json.h) owns the field mapping;
|
||||
* listfmt_json.h (task 2.2) owns the array lifecycle/printing. */
|
||||
cJSON* array = listfmt_json_begin();
|
||||
|
||||
if (array == NULL) {
|
||||
free_Resource_list(rc, res);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (i = 0; i < rc; i++) {
|
||||
cJSON* row = listfmt_json_new_row(array);
|
||||
|
||||
reslist_json_row(row, res[i]->name, res[i]->flags,
|
||||
res[i]->interval, res[i]->desc);
|
||||
}
|
||||
|
||||
listfmt_json_finish(array);
|
||||
free_Resource_list(rc, res);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Note the extra literal space before the Description specifier: unlike
|
||||
* cmd_workerlist's all-left-justified text columns (which self-separate
|
||||
* via their own trailing padding), Interval stays right-justified here,
|
||||
|
|
@ -2082,12 +2158,52 @@ static int workerlist_thunk(void* ctxp)
|
|||
return call->rc;
|
||||
}
|
||||
|
||||
/* xce-test-cli-ux task 3.4, Requirement 5.1/5.2, 6.2: workerlist's
|
||||
* first-ever flag, --json -- IDENTICAL long_opt/concept spelling to
|
||||
* reslist_flags/joblist_flags (Requirement 6.2). */
|
||||
enum { WORKERLIST_OPT_JSON = 0 };
|
||||
|
||||
static const flagspec_t workerlist_flags[] = {
|
||||
{ 0, "json", "json-output", no_argument, 0, "human-readable table" },
|
||||
};
|
||||
#define WORKERLIST_NFLAGS \
|
||||
(sizeof(workerlist_flags) / sizeof(workerlist_flags[0]))
|
||||
|
||||
int cmd_workerlist(int argc, char* argv[])
|
||||
{
|
||||
int rc, i;
|
||||
int opt;
|
||||
int json_mode = 0;
|
||||
xce_worker_t** workers;
|
||||
workerlist_call_t* call = (workerlist_call_t*)calloc(1, sizeof(*call));
|
||||
workerlist_call_t* call;
|
||||
struct option long_opts[WORKERLIST_NFLAGS + 1];
|
||||
char optstring[8];
|
||||
|
||||
optind = 1;
|
||||
opterr = 0;
|
||||
|
||||
if (flagspec_build_opts(workerlist_flags, WORKERLIST_NFLAGS, long_opts,
|
||||
optstring, sizeof(optstring))
|
||||
< 0) {
|
||||
fprintf(stderr,
|
||||
"xce_test: workerlist: internal error building flag table\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
while ((opt = getopt_long(argc, argv, optstring, long_opts, NULL))
|
||||
!= -1) {
|
||||
switch (opt) {
|
||||
case FLAGSPEC_LONG_ONLY_BASE + WORKERLIST_OPT_JSON:
|
||||
json_mode = 1;
|
||||
break;
|
||||
case '?':
|
||||
return flagspec_reject("workerlist", workerlist_flags,
|
||||
WORKERLIST_NFLAGS, argv[optind - 1],
|
||||
"workerlist [--json]");
|
||||
}
|
||||
}
|
||||
|
||||
call = (workerlist_call_t*)calloc(1, sizeof(*call));
|
||||
if (call == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
|
@ -2111,6 +2227,40 @@ int cmd_workerlist(int argc, char* argv[])
|
|||
return rc;
|
||||
}
|
||||
|
||||
if (json_mode) {
|
||||
/* xce-test-cli-ux task 3.4, Requirement 5.1/5.3: emit the SAME
|
||||
* fields the table below prints, unabbreviated, as one JSON
|
||||
* object per worker -- MaxMem/MaxSwap/MaxTmp as their RAW byte
|
||||
* values (not the table's humanized "64.0 MB"-style strings), see
|
||||
* workerlist_json_row()'s own doc comment (listrow_json.h). */
|
||||
cJSON* array = listfmt_json_begin();
|
||||
|
||||
if (array == NULL) {
|
||||
free_Worker_list(rc, workers);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (i = 0; i < rc; i++) {
|
||||
cJSON* row = listfmt_json_new_row(array);
|
||||
|
||||
workerlist_json_row(row, workers[i]->name,
|
||||
worker_status_str(workers[i]->status),
|
||||
workers[i]->attrs->hosttype,
|
||||
workers[i]->attrs->hostmodel,
|
||||
workers[i]->attrs->cpufactor,
|
||||
workers[i]->attrs->maxcpus,
|
||||
workers[i]->attrs->maxmem,
|
||||
workers[i]->attrs->maxswap,
|
||||
workers[i]->attrs->maxtmp,
|
||||
workers[i]->attrs->ndisks);
|
||||
}
|
||||
|
||||
listfmt_json_finish(array);
|
||||
free_Worker_list(rc, workers);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("%-20s%-8s%-25s%-25s%12s%12s%12s%12s%12s%12s\n", "HostName",
|
||||
"Status", "HostType", "HostModel", "CPUfactor", "MaxCPU", "MaxMem",
|
||||
"MaxSwap", "MaxTmp", "NDisks");
|
||||
|
|
@ -2162,6 +2312,13 @@ int cmd_workerlist(int argc, char* argv[])
|
|||
* up. When `print_table` is set, the human-readable table is printed (the
|
||||
* `cmd_joblist` path). The job array is always freed internally.
|
||||
*
|
||||
* `json_mode` (xce-test-cli-ux task 3.4, Requirement 5.1/5.2): when set,
|
||||
* emits the SAME fields `print_table` would have printed as one JSON
|
||||
* object per job instead (joblist_json_row(), listrow_json.h) --
|
||||
* mutually exclusive with `print_table` in practice: `cmd_joblist` (the
|
||||
* only caller that ever sets either) passes exactly one of the two as
|
||||
* true, never both; `expect-jobs`/`expect-job` always pass 0 for both.
|
||||
*
|
||||
* On error this returns the negative rc WITHOUT setting any auto-variable, so a
|
||||
* failed joblist never advertises a fake `$jobcount` (Req 3.6). */
|
||||
typedef struct {
|
||||
|
|
@ -2180,10 +2337,11 @@ static int joblist_thunk(void* ctxp)
|
|||
}
|
||||
|
||||
static int joblist_query(const char* find_jobid, int* contains, int print_table,
|
||||
int quiet)
|
||||
int quiet, int json_mode)
|
||||
{
|
||||
int rc, i;
|
||||
xce_job_t** jobs;
|
||||
cJSON* json_array = NULL;
|
||||
joblist_call_t* call = (joblist_call_t*)calloc(1, sizeof(*call));
|
||||
|
||||
if (contains != NULL) {
|
||||
|
|
@ -2224,6 +2382,16 @@ static int joblist_query(const char* find_jobid, int* contains, int print_table,
|
|||
printf("%-26s%-10s%-10s%-8s%-24s%-24s%-24s%12s%12s%30s\n", "JobId",
|
||||
"User", "Queue", "State", "SubmitTime", "StartTime",
|
||||
"FinishTime", "MinTasks", "MaxTasks", "Command");
|
||||
} else if (json_mode) {
|
||||
json_array = listfmt_json_begin();
|
||||
|
||||
if (json_array == NULL) {
|
||||
for (i = 0; i < rc; i++) {
|
||||
free_Job_ptr(&(jobs[i]));
|
||||
}
|
||||
free(jobs);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < rc; i++) {
|
||||
|
|
@ -2252,9 +2420,27 @@ static int joblist_query(const char* find_jobid, int* contains, int print_table,
|
|||
sizeof(finish_buf)),
|
||||
jobs[i]->submit->mintasks, jobs[i]->submit->maxtasks,
|
||||
jobs[i]->submit->start_info->cmdline);
|
||||
} else if (json_mode) {
|
||||
/* xce-test-cli-ux task 3.4, Requirement 5.1/5.3: the SAME
|
||||
* fields the table above prints, unabbreviated, as one JSON
|
||||
* object per job -- joblist_json_row() (listrow_json.h) owns
|
||||
* the field mapping. */
|
||||
cJSON* row = listfmt_json_new_row(json_array);
|
||||
|
||||
joblist_json_row(row, buf, jobs[i]->submit->user,
|
||||
jobs[i]->submit->queue,
|
||||
job_state_str(jobs[i]->state),
|
||||
jobs[i]->queue_time, jobs[i]->start_time,
|
||||
jobs[i]->finish_time, jobs[i]->submit->mintasks,
|
||||
jobs[i]->submit->maxtasks,
|
||||
jobs[i]->submit->start_info->cmdline);
|
||||
}
|
||||
}
|
||||
|
||||
if (json_mode) {
|
||||
listfmt_json_finish(json_array);
|
||||
}
|
||||
|
||||
for (i = 0; i < rc; i++) {
|
||||
free_Job_ptr(&(jobs[i]));
|
||||
}
|
||||
|
|
@ -2264,11 +2450,49 @@ static int joblist_query(const char* find_jobid, int* contains, int print_table,
|
|||
return rc;
|
||||
}
|
||||
|
||||
/* xce-test-cli-ux task 3.4, Requirement 5.1/5.2, 6.2: joblist's first-ever
|
||||
* flag, --json -- IDENTICAL long_opt/concept spelling to reslist_flags/
|
||||
* workerlist_flags above (Requirement 6.2). */
|
||||
enum { JOBLIST_OPT_JSON = 0 };
|
||||
|
||||
static const flagspec_t joblist_flags[] = {
|
||||
{ 0, "json", "json-output", no_argument, 0, "human-readable table" },
|
||||
};
|
||||
#define JOBLIST_NFLAGS (sizeof(joblist_flags) / sizeof(joblist_flags[0]))
|
||||
|
||||
int cmd_joblist(int argc, char* argv[])
|
||||
{
|
||||
int count;
|
||||
int count;
|
||||
int opt;
|
||||
int json_mode = 0;
|
||||
struct option long_opts[JOBLIST_NFLAGS + 1];
|
||||
char optstring[8];
|
||||
|
||||
count = joblist_query(NULL, NULL, 1, /*quiet=*/0);
|
||||
optind = 1;
|
||||
opterr = 0;
|
||||
|
||||
if (flagspec_build_opts(joblist_flags, JOBLIST_NFLAGS, long_opts,
|
||||
optstring, sizeof(optstring))
|
||||
< 0) {
|
||||
fprintf(stderr,
|
||||
"xce_test: joblist: internal error building flag table\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
while ((opt = getopt_long(argc, argv, optstring, long_opts, NULL))
|
||||
!= -1) {
|
||||
switch (opt) {
|
||||
case FLAGSPEC_LONG_ONLY_BASE + JOBLIST_OPT_JSON:
|
||||
json_mode = 1;
|
||||
break;
|
||||
case '?':
|
||||
return flagspec_reject("joblist", joblist_flags, JOBLIST_NFLAGS,
|
||||
argv[optind - 1], "joblist [--json]");
|
||||
}
|
||||
}
|
||||
|
||||
count = joblist_query(NULL, NULL, /*print_table=*/!json_mode,
|
||||
/*quiet=*/0, json_mode);
|
||||
|
||||
if (count < 0) {
|
||||
return count;
|
||||
|
|
@ -2319,7 +2543,7 @@ int cmd_expect_jobs(int argc, char* argv[])
|
|||
/* Print-free count via the shared helper. A negative rc means the query
|
||||
* itself failed (e.g. no reachable master) — that is an assertion failure,
|
||||
* not "0 jobs". */
|
||||
count = joblist_query(NULL, NULL, 0, /*quiet=*/0);
|
||||
count = joblist_query(NULL, NULL, 0, /*quiet=*/0, /*json_mode=*/0);
|
||||
|
||||
if (count < 0) {
|
||||
printf("expect-jobs: could not determine job count (joblist query "
|
||||
|
|
@ -2367,7 +2591,7 @@ int cmd_expect_job(int argc, char* argv[])
|
|||
/* Print-free membership query via the shared helper. A negative rc means the
|
||||
* query itself failed (e.g. no reachable master) — that is an assertion
|
||||
* failure, distinct from "job absent". */
|
||||
rc = joblist_query(argv[1], &contains, 0, /*quiet=*/0);
|
||||
rc = joblist_query(argv[1], &contains, 0, /*quiet=*/0, /*json_mode=*/0);
|
||||
|
||||
if (rc < 0) {
|
||||
printf("expect-job: could not determine membership of '%s' (joblist "
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue