mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
cpumf/lshwc: Add support for structured output formats
Introduce a new command-line option --format FORMAT to control the output format of lshwc. Supported formats include CSV, JSON, JSON-SEQ, and key-value PAIRS. The output is now generated using the shared util_fmt infrastructure, enabling consistent and machine-readable output. This change also includes: - Label generation for counters - Integration of util_fmt for structured formatting - Updated man page with documentation for the new option - Updated man page with JSON output structure documentation - Add option (-q) to force quoting all elements. Reference: https://www.ietf.org/rfc/rfc4180.txt Signed-off-by: Jan Polensky <japo@linux.ibm.com> Reviewed-by: Thomas Richter <tmricht@linux.ibm.com> Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
This commit is contained in:
committed by
Steffen Eiden
parent
120ad30f1e
commit
b69b750ef5
229
cpumf/lshwc.c
229
cpumf/lshwc.c
@@ -39,6 +39,7 @@
|
||||
#include "lib/util_scandir.h"
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "lib/util_fmt.h"
|
||||
#include "lib/libcpumf.h"
|
||||
|
||||
#include "lshwc.h"
|
||||
@@ -58,16 +59,52 @@ static char *ctrformat = "%ld";
|
||||
static bool shortname;
|
||||
static bool hideundef;
|
||||
static bool delta, firstread;
|
||||
static int output_format = FMT_CSV;
|
||||
static bool quote_all;
|
||||
|
||||
static unsigned int max_possible_cpus; /* No of possible CPUs */
|
||||
static struct ctrname { /* List of defined counters */
|
||||
char *name; /* Counter name */
|
||||
char *label; /* Output name */
|
||||
bool hitcnt; /* Counter number read from ioctl() */
|
||||
unsigned long total; /* Total counter value */
|
||||
unsigned long *ccv; /* Per CPU counter value */
|
||||
unsigned long *ccvprv; /* Per CPU counter value (previous read) */
|
||||
} ctrname[MAXCTRS];
|
||||
|
||||
struct time_formats {
|
||||
char epoch[32];
|
||||
char date_time[32];
|
||||
char date[16];
|
||||
char time[16];
|
||||
};
|
||||
|
||||
static void mk_labels(void)
|
||||
{
|
||||
char label[64];
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
if (shortname) {
|
||||
if (ctrname[i].name)
|
||||
snprintf(label, sizeof(label), "%s", ctrname[i].name);
|
||||
else
|
||||
snprintf(label, sizeof(label), "U%ld", i);
|
||||
} else {
|
||||
if (output_format == FMT_CSV)
|
||||
snprintf(label, sizeof(label), "%s(%ld)",
|
||||
ctrname[i].name ?: "Counter", i);
|
||||
else if (ctrname[i].name)
|
||||
snprintf(label, sizeof(label), "%s", ctrname[i].name);
|
||||
else
|
||||
label[0] = 0;
|
||||
}
|
||||
if (output_format != FMT_CSV)
|
||||
util_str_tolower(label);
|
||||
ctrname[i].label = util_strdup(label);
|
||||
}
|
||||
}
|
||||
|
||||
static char *mk_name(int ctr, char *name)
|
||||
{
|
||||
char ctrset[8];
|
||||
@@ -130,6 +167,7 @@ static void free_counternames(void)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
free(ctrname[i].name);
|
||||
free(ctrname[i].label);
|
||||
free(ctrname[i].ccv);
|
||||
free(ctrname[i].ccvprv);
|
||||
}
|
||||
@@ -336,90 +374,111 @@ static bool check_setpossible(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void show_header(void)
|
||||
static void safe_strtime(char *dest, size_t size, const char *fmt, const struct tm *tm)
|
||||
{
|
||||
static bool header;
|
||||
bool comma = false;
|
||||
|
||||
if (header)
|
||||
return; /* Printed already */
|
||||
printf("Date,Time,CPU,"); /* Print counter name and number */
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (hideundef && !ctrname[i].name)
|
||||
continue;
|
||||
if (comma)
|
||||
putchar(',');
|
||||
if (shortname) {
|
||||
if (ctrname[i].name)
|
||||
printf("%s", ctrname[i].name);
|
||||
else
|
||||
printf("U%ld", i);
|
||||
} else {
|
||||
printf("%s(%ld)", ctrname[i].name ?: "Counter", i);
|
||||
}
|
||||
comma = true;
|
||||
}
|
||||
putchar('\n');
|
||||
header = true;
|
||||
if (!strftime(dest, size, fmt, tm))
|
||||
dest[0] = 0;
|
||||
}
|
||||
|
||||
static void line(char *header)
|
||||
static void generate_timestamp(struct time_formats *date)
|
||||
{
|
||||
bool comma;
|
||||
time_t now = time(NULL);
|
||||
struct tm *now_tm = localtime(&now);
|
||||
|
||||
show_header();
|
||||
if (allcpu) {
|
||||
for (unsigned int h = 0; h < max_possible_cpus; ++h) {
|
||||
char txt[16];
|
||||
safe_strtime(date->date_time, sizeof(date->date_time), "%F %T%z", now_tm);
|
||||
safe_strtime(date->date, sizeof(date->date), "%F", now_tm);
|
||||
safe_strtime(date->time, sizeof(date->time), "%T", now_tm);
|
||||
safe_strtime(date->epoch, sizeof(date->epoch), "%s", now_tm);
|
||||
}
|
||||
|
||||
if (!check[h].cpu_hit)
|
||||
continue;
|
||||
comma = false;
|
||||
snprintf(txt, sizeof(txt), "CPU%d,", h);
|
||||
printf("%s%s", header, txt);
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (hideundef && !ctrname[i].name)
|
||||
continue;
|
||||
if (comma)
|
||||
putchar(',');
|
||||
printf(ctrformat, ctrname[i].ccv[h]);
|
||||
comma = true;
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
static void output_times(struct time_formats date)
|
||||
{
|
||||
if (output_format == FMT_CSV) {
|
||||
util_fmt_pair(FMT_PERSIST, "Date", "%s", date.date);
|
||||
util_fmt_pair(FMT_PERSIST, "Time", "%s", date.time);
|
||||
} else {
|
||||
util_fmt_pair(FMT_PERSIST | FMT_QUOTE, "date_time", "%s", date.date_time);
|
||||
util_fmt_pair(FMT_PERSIST, "time_epoch", "%s", date.epoch);
|
||||
}
|
||||
}
|
||||
|
||||
/* Print total count of all CPUs */
|
||||
printf("%s%s,", header, delta && !firstread ? "Delta" : "Total");
|
||||
comma = false;
|
||||
static void prepare_counter(size_t id, unsigned long value)
|
||||
{
|
||||
if (output_format == FMT_CSV) {
|
||||
util_fmt_pair(FMT_PERSIST, ctrname[id].label, ctrformat, value);
|
||||
} else {
|
||||
util_fmt_obj_start(FMT_ROW, NULL);
|
||||
if (strlen(ctrname[id].label))
|
||||
util_fmt_pair(FMT_PERSIST | FMT_QUOTE, "name", ctrname[id].label);
|
||||
util_fmt_pair(FMT_PERSIST, "id", ctrformat, id);
|
||||
util_fmt_pair(FMT_PERSIST, "value", ctrformat, value);
|
||||
util_fmt_obj_end();
|
||||
}
|
||||
}
|
||||
|
||||
static void output_per_cpu(struct time_formats date)
|
||||
{
|
||||
for (unsigned int h = 0; h < max_possible_cpus; ++h) {
|
||||
if (!check[h].cpu_hit)
|
||||
continue;
|
||||
|
||||
char txt[16];
|
||||
|
||||
snprintf(txt, sizeof(txt), "CPU%d", h);
|
||||
util_fmt_obj_start(FMT_ROW, "cpu_%d", h);
|
||||
output_times(date);
|
||||
if (output_format == FMT_CSV) {
|
||||
util_fmt_pair(FMT_PERSIST, "CPU", "CPU%d", h);
|
||||
} else {
|
||||
util_fmt_pair(FMT_PERSIST, "cpu", "%d", h);
|
||||
util_fmt_obj_start(FMT_LIST, "counters");
|
||||
}
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (hideundef && !ctrname[i].name)
|
||||
continue;
|
||||
prepare_counter(i, ctrname[i].ccv[h]);
|
||||
}
|
||||
if (output_format != FMT_CSV)
|
||||
util_fmt_obj_end();
|
||||
util_fmt_obj_end();
|
||||
}
|
||||
}
|
||||
|
||||
static void output_total(struct time_formats date)
|
||||
{
|
||||
util_fmt_obj_start(FMT_ROW, "total");
|
||||
output_times(date);
|
||||
if (output_format == FMT_CSV) {
|
||||
util_fmt_pair(FMT_PERSIST, "CPU", "%s", delta && !firstread ? "Delta" : "Total");
|
||||
} else {
|
||||
util_fmt_pair(FMT_PERSIST | FMT_QUOTE, "cpu", "%s",
|
||||
delta && !firstread ? "delta" : "total");
|
||||
util_fmt_obj_start(FMT_LIST, "counters");
|
||||
}
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (hideundef && !ctrname[i].name)
|
||||
continue;
|
||||
if (comma)
|
||||
putchar(',');
|
||||
printf(ctrformat, ctrname[i].total);
|
||||
comma = true;
|
||||
prepare_counter(i, ctrname[i].total);
|
||||
ctrname[i].total = 0;
|
||||
ctrname[i].hitcnt = false;
|
||||
}
|
||||
putchar('\n');
|
||||
if (output_format != FMT_CSV)
|
||||
util_fmt_obj_end();
|
||||
util_fmt_obj_end();
|
||||
}
|
||||
|
||||
static void show(void)
|
||||
static void show_format(void)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
struct tm *now_tm;
|
||||
char now_text[32];
|
||||
struct time_formats now;
|
||||
|
||||
now_tm = localtime(&now);
|
||||
strftime(now_text, sizeof(now_text), "%F,%T,", now_tm);
|
||||
line(now_text);
|
||||
generate_timestamp(&now);
|
||||
if (allcpu)
|
||||
output_per_cpu(now);
|
||||
output_total(now);
|
||||
}
|
||||
|
||||
/* Return Counter set size numbers (in counters) */
|
||||
@@ -551,7 +610,7 @@ static int test_read(struct s390_hwctr_read *read)
|
||||
}
|
||||
}
|
||||
}
|
||||
show();
|
||||
show_format();
|
||||
firstread = false;
|
||||
return 0;
|
||||
}
|
||||
@@ -615,6 +674,7 @@ static void do_sleep(void)
|
||||
static int do_it(char *s)
|
||||
{
|
||||
struct s390_hwctr_start start;
|
||||
unsigned int flags = FMT_WARN;
|
||||
int ioctlfd;
|
||||
int rc;
|
||||
|
||||
@@ -634,6 +694,24 @@ static int do_it(char *s)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (output_format == FMT_CSV)
|
||||
flags |= FMT_NOMETA;
|
||||
if (output_format == FMT_JSON || output_format == FMT_JSONSEQ)
|
||||
flags |= FMT_HANDLEINT;
|
||||
if (quote_all)
|
||||
flags |= FMT_QUOTEALL;
|
||||
|
||||
mk_labels();
|
||||
util_fmt_init(stdout, output_format, flags, 1);
|
||||
util_fmt_obj_start(FMT_DEFAULT, "lshwc");
|
||||
if (output_format == FMT_JSON || output_format == FMT_JSONSEQ) {
|
||||
util_fmt_obj_start(FMT_ROW, "cpumcf info");
|
||||
util_fmt_pair(FMT_PERSIST, "counter first", "%d", cfvn);
|
||||
util_fmt_pair(FMT_PERSIST, "counter second", "%d", csvn);
|
||||
util_fmt_pair(FMT_PERSIST, "authorization", "%d", authorization);
|
||||
util_fmt_obj_end();
|
||||
}
|
||||
util_fmt_obj_start(FMT_LIST, "measurements");
|
||||
for (unsigned long i = 0; !rc && i < loop_count; ++i) {
|
||||
rc = do_read(ioctlfd);
|
||||
if (rc) {
|
||||
@@ -643,6 +721,9 @@ static int do_it(char *s)
|
||||
if (read_interval && i + 1 < loop_count)
|
||||
do_sleep();
|
||||
}
|
||||
util_fmt_obj_end();
|
||||
util_fmt_obj_end();
|
||||
util_fmt_exit();
|
||||
rc = do_stop(ioctlfd);
|
||||
close(ioctlfd);
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
@@ -689,6 +770,15 @@ static struct util_opt opt_vec[] = {
|
||||
.argument = "NUMBER",
|
||||
.desc = "run time in s (seconds) m (minutes) h (hours) and d (days)"
|
||||
},
|
||||
{
|
||||
.option = { "quote-all", no_argument, NULL, 'q' },
|
||||
.desc = "Apply quoting to all output elements"
|
||||
},
|
||||
{
|
||||
.option = { "format", required_argument, NULL, 'f' },
|
||||
.argument = "FORMAT",
|
||||
.desc = "List counters in specified FORMAT (" FMT_TYPE_NAMES ")"
|
||||
},
|
||||
UTIL_OPT_HELP,
|
||||
UTIL_OPT_VERSION,
|
||||
UTIL_OPT_END
|
||||
@@ -718,6 +808,7 @@ static void have_support(void)
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
enum util_fmt_t fmt;
|
||||
unsigned long no;
|
||||
char *slash;
|
||||
int ch;
|
||||
@@ -793,6 +884,14 @@ int main(int argc, char **argv)
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'q':
|
||||
quote_all = true;
|
||||
break;
|
||||
case 'f':
|
||||
if (!util_fmt_name_to_type(optarg, &fmt))
|
||||
errx(EXIT_FAILURE, "Supported formats:" FMT_TYPE_NAMES);
|
||||
output_format = fmt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,15 @@ lshwc \- extract CPU Measurement Facilities counter sets
|
||||
.
|
||||
.SH SYNOPSIS
|
||||
\*c
|
||||
.RB [ \-a ][ \-d ][ \-H ][ \-s ][ \-x ][ \-X ]
|
||||
.RB [ \-a ][ \-d ][ \-H ][ \-s ][ \-x ][ \-X ][ \-q ]
|
||||
.RB [ \-l
|
||||
.IR count ]
|
||||
.RB [ \-i
|
||||
.IR interval ]
|
||||
.RB [ \-t
|
||||
.IR time ]
|
||||
.RB [ \-f
|
||||
.IR format ]
|
||||
\fR[\fIcpulist\fR][:\fIsets\fR]\fP
|
||||
.br
|
||||
\*c
|
||||
@@ -128,6 +130,49 @@ Displays counter values as hexadecimal values.
|
||||
Displays counter values as hexadecimal values with a leading 0x prefix.
|
||||
.
|
||||
.TP
|
||||
.BR \-q
|
||||
Apply quoting to every output element, regardless of content or format.
|
||||
.
|
||||
.TP
|
||||
.BR \-f ", " \-\-format \fI\ format\fP
|
||||
Retrieve output in one of the following formats:
|
||||
JSON, CSV, JSON-SEQ or PAIRS.
|
||||
If no format is specified, the output defaults to CSV.
|
||||
.
|
||||
.SS JSON Output Structure
|
||||
The JSON output contains two top-level objects:
|
||||
.B "meta"
|
||||
and
|
||||
.B "lshwc"
|
||||
|
||||
.TP
|
||||
.B meta
|
||||
General metadata such as API version, host, and timestamp.
|
||||
|
||||
.TP
|
||||
.B lshwc
|
||||
Measurement data and configuration.
|
||||
|
||||
.RS
|
||||
.IP \fBcpumcf info\fP
|
||||
CPU Measurement Counter Facility information.
|
||||
.IP \fBmeasurements\fP
|
||||
An array of objects, each representing a CPU or "total".
|
||||
Each object includes:
|
||||
.RS
|
||||
.IP \fBcpu\fP
|
||||
CPU identifier, such as 0, 1 or "delta" or "total".
|
||||
.IP \fBdate_time\fP
|
||||
Timestamp in ISO 8601 format.
|
||||
.IP \fBtime_epoch\fP
|
||||
The number of seconds since the epoch: 1970-01-01 00:00:00 UTC.
|
||||
.IP \fBcounters\fP
|
||||
An array of counter objects, each containing \fBname\fP (if available), \fBid\fP,
|
||||
and \fBvalue\fP.
|
||||
.RE
|
||||
.RE
|
||||
.
|
||||
.TP
|
||||
\fR[\fIcpulist\fR][:\fIsets\fR]\fP
|
||||
A comma-separated list of CPUs.
|
||||
Each CPU can optionally be followed by characters that specify the counter set.
|
||||
@@ -210,6 +255,101 @@ Date,Time,CPU,B0,B1,B2,B3,B4,B5
|
||||
2025-03-26,10:35:04,Delta,83763472,71730813,609260,13643680,366992,12672405
|
||||
.ft
|
||||
.fi
|
||||
.sp 1
|
||||
This example shows the counter values of the problem state counter set with
|
||||
CPU 3 selected.
|
||||
.nf
|
||||
.ft CW
|
||||
.sp 1
|
||||
# lshwc -l2 -a 3:P -H --format json
|
||||
{
|
||||
"meta": {
|
||||
"api_level": 1,
|
||||
"version": "2.37.0-build-20250616",
|
||||
"host": "b46lp08.lnxne.boe",
|
||||
"time_epoch": 1750094646,
|
||||
"time": "2025-06-16 19:24:06+0200"
|
||||
},
|
||||
"lshwc": {
|
||||
"cpumcf info": {
|
||||
"counter first": 3,
|
||||
"counter second": 8,
|
||||
"authorization": 47
|
||||
},
|
||||
"measurements": [
|
||||
{
|
||||
"date_time": "2025-06-16 19:24:06+0200",
|
||||
"time_epoch": 1750094646,
|
||||
"cpu": 3,
|
||||
"counters": [
|
||||
{
|
||||
"name": "problem_state_cpu_cycles",
|
||||
"id": 32,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "problem_state_instructions",
|
||||
"id": 33,
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"date_time": "2025-06-16 19:24:06+0200",
|
||||
"time_epoch": 1750094646,
|
||||
"cpu": "total",
|
||||
"counters": [
|
||||
{
|
||||
"name": "problem_state_cpu_cycles",
|
||||
"id": 32,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "problem_state_instructions",
|
||||
"id": 33,
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"date_time": "2025-06-16 19:25:06+0200",
|
||||
"time_epoch": 1750094706,
|
||||
"cpu": 3,
|
||||
"counters": [
|
||||
{
|
||||
"name": "problem_state_cpu_cycles",
|
||||
"id": 32,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "problem_state_instructions",
|
||||
"id": 33,
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"date_time": "2025-06-16 19:25:06+0200",
|
||||
"time_epoch": 1750094706,
|
||||
"cpu": "total",
|
||||
"counters": [
|
||||
{
|
||||
"name": "problem_state_cpu_cycles",
|
||||
"id": 32,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "problem_state_instructions",
|
||||
"id": 33,
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
.ft
|
||||
.fi
|
||||
.SH "SEE ALSO"
|
||||
.BR lscpumf (8)
|
||||
.BR chcpumf (8)
|
||||
|
||||
Reference in New Issue
Block a user