From f1a11a01dff9632c07ba9ec3c001bed1b0ef2ffd Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Tue, 21 Apr 2026 11:44:09 +0200 Subject: [PATCH] zpcimon: Add JSON output for SMART data details MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This uses the same format as: nvme smart-log --output-format=json /dev/nvmeX One complication being that several values in the SMART data use unsigned 128 bit integers. Both GCC and Clang support __uint128_t as a C extension but don't offer printf() support for it. Just like nvme-cli add a custom uint128_t to string function.. Reviewed-by: Jan Höppner Signed-off-by: Niklas Schnelle Signed-off-by: Jan Höppner --- zpcimon/nvmemon.c | 106 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/zpcimon/nvmemon.c b/zpcimon/nvmemon.c index 85a5d0ad..a516b4aa 100644 --- a/zpcimon/nvmemon.c +++ b/zpcimon/nvmemon.c @@ -23,14 +23,116 @@ #include "nvmemon.h" #include "zpcimon.h" +/** + * Reads a CPU native 128 bit unsigned integer from the binary buffer @data + * containing the value in the 128 bit little endian format used by the + * NVMe standard. + * + * @return the 128 bit value encoded in the data buffer. + */ +static __uint128_t nvme_le128_to_cpu(const __u8 data[16]) +{ + __uint128_t u; + int i; + + u = data[0]; + for (i = 1; i < (int)sizeof(u); i++) + u |= (__uint128_t)data[i] << (8 * i); + return u; +} + +#define MAX_UINT128_DECIMAL_LEN (41) /* length of 2^128-1 in decimal + 0 byte */ + +/** + * Write the decimal string representation of the passed unsigned 128 bit value @val into + * the buffer @buf for output in JSON. The representation does not include + * quotes. + */ +static void nvme_u128_to_json_val(__uint128_t val, char buf[MAX_UINT128_DECIMAL_LEN]) +{ + const char *digit_ascii = "0123456789"; + int pos = MAX_UINT128_DECIMAL_LEN - 1; + int mod_ten; + int len; + + /* Write digits right to left at the end of res */ + do { + mod_ten = val % 10; + val /= 10; + buf[pos--] = digit_ascii[mod_ten]; + } while (val); + /* Move digits to the front */ + len = MAX_UINT128_DECIMAL_LEN - 1 - pos; + memmove(buf, &buf[pos + 1], len); + buf[len] = '\0'; +} + +static void nvme_json_print_smart_log(struct zpcimon_ctx *ctx, struct nvme_smart_log *log) +{ + char u128_num_buf[MAX_UINT128_DECIMAL_LEN]; + unsigned int temperature; + + /* While some fields in struct nvme_smart_log are __leXX + * temperature is an array of two u8 of the little endian data + * in Kelvin. + */ + temperature = log->temperature[1] << 8 | log->temperature[0]; + + util_fmt_obj_start(FMT_DEFAULT, "smart-log"); + util_fmt_pair(FMT_DEFAULT, "critical_warning", "%d", log->critical_warning); + util_fmt_pair(FMT_DEFAULT, "temperature", "%d", temperature); + util_fmt_pair(FMT_DEFAULT, "avail_spare", "%d", log->avail_spare); + util_fmt_pair(FMT_DEFAULT, "spare_thresh", "%d", log->spare_thresh); + util_fmt_pair(FMT_DEFAULT, "percent_used", "%d", log->percent_used); + util_fmt_pair(FMT_DEFAULT, "endurance_grp_critical_warning_summary", "%d", + log->endu_grp_crit_warn_sumry); + nvme_u128_to_json_val(nvme_le128_to_cpu(log->data_units_read), u128_num_buf); + util_fmt_pair(FMT_DEFAULT, "data_units_read", "%s", u128_num_buf); + nvme_u128_to_json_val(nvme_le128_to_cpu(log->data_units_written), u128_num_buf); + util_fmt_pair(FMT_DEFAULT, "data_units_written", "%s", u128_num_buf); + nvme_u128_to_json_val(nvme_le128_to_cpu(log->host_reads), u128_num_buf); + util_fmt_pair(FMT_DEFAULT, "host_read_commands", "%s", u128_num_buf); + nvme_u128_to_json_val(nvme_le128_to_cpu(log->host_writes), u128_num_buf); + util_fmt_pair(FMT_DEFAULT, "host_write_commands", "%s", u128_num_buf); + nvme_u128_to_json_val(nvme_le128_to_cpu(log->ctrl_busy_time), u128_num_buf); + util_fmt_pair(FMT_DEFAULT, "controller_busy_time", "%s", u128_num_buf); + nvme_u128_to_json_val(nvme_le128_to_cpu(log->power_cycles), u128_num_buf); + util_fmt_pair(FMT_DEFAULT, "power_cycles", "%s", u128_num_buf); + /* 2^128 hours is 2.8*10^24 times the age of the universe ;) */ + nvme_u128_to_json_val(nvme_le128_to_cpu(log->power_on_hours), u128_num_buf); + util_fmt_pair(FMT_DEFAULT, "power_on_hours", "%s", u128_num_buf); + nvme_u128_to_json_val(nvme_le128_to_cpu(log->unsafe_shutdowns), u128_num_buf); + util_fmt_pair(FMT_DEFAULT, "unsafe_shutdowns", "%s", u128_num_buf); + nvme_u128_to_json_val(nvme_le128_to_cpu(log->media_errors), u128_num_buf); + util_fmt_pair(FMT_DEFAULT, "media_errors", "%s", u128_num_buf); + nvme_u128_to_json_val(nvme_le128_to_cpu(log->num_err_log_entries), u128_num_buf); + util_fmt_pair(FMT_DEFAULT, "num_err_log_entries", "%s", u128_num_buf); + util_fmt_pair(FMT_DEFAULT, "warning_temp_time", "%d", le32toh(log->warning_temp_time)); + util_fmt_pair(FMT_DEFAULT, "critical_comp_time", "%d", le32toh(log->critical_comp_time)); + util_fmt_pair(FMT_DEFAULT, "temperature_sensor_1", "%d", le16toh(log->temp_sensor[0])); + util_fmt_pair(FMT_DEFAULT, "temperature_sensor_2", "%d", le16toh(log->temp_sensor[1])); + util_fmt_pair(FMT_DEFAULT, "temperature_sensor_3", "%d", le16toh(log->temp_sensor[2])); + util_fmt_pair(FMT_DEFAULT, "thm_temp1_trans_count", "%d", + le32toh(log->thm_temp1_trans_count)); + util_fmt_pair(FMT_DEFAULT, "thm_temp2_trans_count", "%d", + le32toh(log->thm_temp2_trans_count)); + util_fmt_pair(FMT_DEFAULT, "thm_temp1_total_time", "%d", + le32toh(log->thm_temp1_total_time)); + util_fmt_pair(FMT_DEFAULT, "thm_temp2_total_time", "%d", + le32toh(log->thm_temp2_total_time)); + util_fmt_obj_end(); /* smart-log */ + + if (ctx->opts.smart_blob) + zpcimon_json_base64_pair("smart-log-raw", (uint8_t *)log, sizeof(*log)); +} + static void nvme_json_print(struct zpcimon_ctx *ctx, struct zpci_dev *zdev, const char *name, struct nvme_smart_log *log) { zpci_adapter_json_print_start(zdev); util_fmt_obj_start(FMT_DEFAULT, "nvmedev"); util_fmt_pair(FMT_QUOTE, "dev", name); - if (ctx->opts.smart_blob) - zpcimon_json_base64_pair("smart-log-raw", (uint8_t *)log, sizeof(*log)); + nvme_json_print_smart_log(ctx, log); util_fmt_obj_end(); zpci_adapter_json_print_end(); fflush(stdout);