summaryrefslogtreecommitdiffstats
path: root/plugins/memblaze/memblaze-nvme.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/memblaze/memblaze-nvme.c')
-rw-r--r--plugins/memblaze/memblaze-nvme.c645
1 files changed, 644 insertions, 1 deletions
diff --git a/plugins/memblaze/memblaze-nvme.c b/plugins/memblaze/memblaze-nvme.c
index c0a70ee..b215125 100644
--- a/plugins/memblaze/memblaze-nvme.c
+++ b/plugins/memblaze/memblaze-nvme.c
@@ -646,7 +646,7 @@ static int glp_high_latency_show_bar(FILE *fdi, int print)
}
/*
- * High latency log page definiton
+ * High latency log page definition
* Total 32 bytes
*/
struct log_page_high_latency {
@@ -1197,3 +1197,646 @@ static int mb_set_lat_stats(int argc, char **argv,
dev_close(dev);
return err;
}
+
+// Global definitions
+
+static inline int K2C(int k) // KELVINS_2_CELSIUS
+{
+ return (k - 273);
+};
+
+// Global ID definitions
+
+enum {
+ // feature ids
+ FID_LATENCY_FEATURE = 0xd0,
+
+ // log ids
+ LID_SMART_LOG_ADD = 0xca,
+ LID_LATENCY_STATISTICS = 0xd0,
+ LID_HIGH_LATENCY_LOG = 0xd1,
+ LID_PERFORMANCE_STATISTICS = 0xd2,
+};
+
+// smart-log-add
+
+struct smart_log_add_item {
+ uint32_t index;
+ char *attr;
+};
+
+struct __packed wear_level {
+ __le16 min;
+ __le16 max;
+ __le16 avg;
+};
+
+struct __packed smart_log_add_item_12 {
+ uint8_t id;
+ uint8_t rsvd[2];
+ uint8_t norm;
+ uint8_t rsvd1;
+ union {
+ struct wear_level wear_level; // 0xad
+ struct temp_since_born { // 0xe7
+ __le16 max;
+ __le16 min;
+ __le16 curr;
+ } temp_since_born;
+ struct power_consumption { // 0xe8
+ __le16 max;
+ __le16 min;
+ __le16 curr;
+ } power_consumption;
+ struct temp_since_power_on { // 0xaf
+ __le16 max;
+ __le16 min;
+ __le16 curr;
+ } temp_since_power_on;
+ uint8_t raw[6];
+ };
+ uint8_t rsvd2;
+};
+
+struct __packed smart_log_add_item_10 {
+ uint8_t id;
+ uint8_t norm;
+ union {
+ struct wear_level wear_level; // 0xad
+ uint8_t raw[6];
+ };
+ uint8_t rsvd[2];
+};
+
+struct smart_log_add {
+ union {
+ union {
+ struct smart_log_add_v0 {
+ struct smart_log_add_item_12 program_fail_count;
+ struct smart_log_add_item_12 erase_fail_count;
+ struct smart_log_add_item_12 wear_leveling_count;
+ struct smart_log_add_item_12 end_to_end_error_count;
+ struct smart_log_add_item_12 crc_error_count;
+ struct smart_log_add_item_12 timed_workload_media_wear;
+ struct smart_log_add_item_12 timed_workload_host_reads;
+ struct smart_log_add_item_12 timed_workload_timer;
+ struct smart_log_add_item_12 thermal_throttle_status;
+ struct smart_log_add_item_12 retry_buffer_overflow_counter;
+ struct smart_log_add_item_12 pll_lock_loss_count;
+ struct smart_log_add_item_12 nand_bytes_written;
+ struct smart_log_add_item_12 host_bytes_written;
+ struct smart_log_add_item_12 system_area_life_remaining;
+ struct smart_log_add_item_12 nand_bytes_read;
+ struct smart_log_add_item_12 temperature;
+ struct smart_log_add_item_12 power_consumption;
+ struct smart_log_add_item_12 power_on_temperature;
+ struct smart_log_add_item_12 power_loss_protection;
+ struct smart_log_add_item_12 read_fail_count;
+ struct smart_log_add_item_12 thermal_throttle_time;
+ struct smart_log_add_item_12 flash_error_media_count;
+ } v0;
+
+ struct smart_log_add_item_12 v0_raw[22];
+ };
+
+ union {
+ struct smart_log_add_v2 {
+ struct smart_log_add_item_12 program_fail_count;
+ struct smart_log_add_item_12 erase_fail_count;
+ struct smart_log_add_item_12 wear_leveling_count;
+ struct smart_log_add_item_12 end_to_end_error_count;
+ struct smart_log_add_item_12 crc_error_count;
+ struct smart_log_add_item_12 timed_workload_media_wear;
+ struct smart_log_add_item_12 timed_workload_host_reads;
+ struct smart_log_add_item_12 timed_workload_timer;
+ struct smart_log_add_item_12 thermal_throttle_status;
+ struct smart_log_add_item_12 lifetime_write_amplification;
+ struct smart_log_add_item_12 pll_lock_loss_count;
+ struct smart_log_add_item_12 nand_bytes_written;
+ struct smart_log_add_item_12 host_bytes_written;
+ struct smart_log_add_item_12 system_area_life_remaining;
+ struct smart_log_add_item_12 firmware_update_count;
+ struct smart_log_add_item_12 dram_cecc_count;
+ struct smart_log_add_item_12 dram_uecc_count;
+ struct smart_log_add_item_12 xor_pass_count;
+ struct smart_log_add_item_12 xor_fail_count;
+ struct smart_log_add_item_12 xor_invoked_count;
+ struct smart_log_add_item_12 inflight_read_io_cmd;
+ struct smart_log_add_item_12 flash_error_media_count;
+ struct smart_log_add_item_12 nand_bytes_read;
+ struct smart_log_add_item_12 temp_since_born;
+ struct smart_log_add_item_12 power_consumption;
+ struct smart_log_add_item_12 temp_since_bootup;
+ struct smart_log_add_item_12 thermal_throttle_time;
+ } v2;
+
+ struct smart_log_add_item_12 v2_raw[27];
+ };
+
+ union {
+ struct smart_log_add_v3 {
+ struct smart_log_add_item_10 program_fail_count;
+ struct smart_log_add_item_10 erase_fail_count;
+ struct smart_log_add_item_10 wear_leveling_count;
+ struct smart_log_add_item_10 ext_e2e_err_count;
+ struct smart_log_add_item_10 crc_err_count;
+ struct smart_log_add_item_10 nand_bytes_written;
+ struct smart_log_add_item_10 host_bytes_written;
+ struct smart_log_add_item_10 reallocated_sector_count;
+ struct smart_log_add_item_10 uncorrectable_sector_count;
+ struct smart_log_add_item_10 nand_uecc_detection;
+ struct smart_log_add_item_10 nand_xor_correction;
+ struct smart_log_add_item_10 gc_count;
+ struct smart_log_add_item_10 dram_uecc_detection_count;
+ struct smart_log_add_item_10 sram_uecc_detection_count;
+ struct smart_log_add_item_10 internal_raid_recovery_fail_count;
+ struct smart_log_add_item_10 inflight_cmds;
+ struct smart_log_add_item_10 internal_e2e_err_count;
+ struct smart_log_add_item_10 die_fail_count;
+ struct smart_log_add_item_10 wear_leveling_execution_count;
+ struct smart_log_add_item_10 read_disturb_count;
+ struct smart_log_add_item_10 data_retention_count;
+ struct smart_log_add_item_10 capacitor_health;
+ } v3;
+
+ struct smart_log_add_item_10 v3_raw[24];
+ };
+
+ uint8_t raw[512];
+ };
+};
+
+static void smart_log_add_v0_print(struct smart_log_add_item_12 *item, int item_count)
+{
+ static const struct smart_log_add_item items[0xff] = {
+ [0xab] = {0, "program_fail_count" },
+ [0xac] = {1, "erase_fail_count" },
+ [0xad] = {2, "wear_leveling_count" },
+ [0xb8] = {3, "end_to_end_error_count" },
+ [0xc7] = {4, "crc_error_count" },
+ [0xe2] = {5, "timed_workload_media_wear" },
+ [0xe3] = {6, "timed_workload_host_reads" },
+ [0xe4] = {7, "timed_workload_timer" },
+ [0xea] = {8, "thermal_throttle_status" },
+ [0xf0] = {9, "retry_buffer_overflow_counter"},
+ [0xf3] = {10, "pll_lock_loss_count" },
+ [0xf4] = {11, "nand_bytes_written" },
+ [0xf5] = {12, "host_bytes_written" },
+ [0xf6] = {13, "system_area_life_remaining" },
+ [0xfa] = {14, "nand_bytes_read" },
+ [0xe7] = {15, "temperature" },
+ [0xe8] = {16, "power_consumption" },
+ [0xaf] = {17, "power_on_temperature" },
+ [0xec] = {18, "power_loss_protection" },
+ [0xf2] = {19, "read_fail_count" },
+ [0xeb] = {20, "thermal_throttle_time" },
+ [0xed] = {21, "flash_error_media_count" },
+ };
+
+ for (int i = 0; i < item_count; i++, item++) {
+ if (item->id == 0)
+ continue;
+
+ printf("%#-12" PRIx8 "%-36s%-12d", item->id, items[item->id].attr, item->norm);
+ switch (item->id) {
+ case 0xad:
+ printf("min: %d, max: %d, avg: %d\n",
+ le16_to_cpu(item->wear_level.min),
+ le16_to_cpu(item->wear_level.max),
+ le16_to_cpu(item->wear_level.avg));
+ break;
+ case 0xe7:
+ printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n",
+ K2C(le16_to_cpu(item->temp_since_born.max)),
+ le16_to_cpu(item->temp_since_born.max),
+ K2C(le16_to_cpu(item->temp_since_born.min)),
+ le16_to_cpu(item->temp_since_born.min),
+ K2C(le16_to_cpu(item->temp_since_born.curr)),
+ le16_to_cpu(item->temp_since_born.curr));
+ break;
+ case 0xe8:
+ printf("max: %d, min: %d, curr: %d\n",
+ le16_to_cpu(item->power_consumption.max),
+ le16_to_cpu(item->power_consumption.min),
+ le16_to_cpu(item->power_consumption.curr));
+ break;
+ case 0xaf:
+ printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n",
+ K2C(le16_to_cpu(item->temp_since_power_on.max)),
+ le16_to_cpu(item->temp_since_power_on.max),
+ K2C(le16_to_cpu(item->temp_since_power_on.min)),
+ le16_to_cpu(item->temp_since_power_on.min),
+ K2C(le16_to_cpu(item->temp_since_power_on.curr)),
+ le16_to_cpu(item->temp_since_power_on.curr));
+ break;
+ default:
+ printf("%" PRIu64 "\n", int48_to_long(item->raw));
+ break;
+ }
+ }
+}
+
+static void smart_log_add_v2_print(struct smart_log_add_item_12 *item, int item_count)
+{
+ static const struct smart_log_add_item items[0xff] = {
+ [0xab] = {0, "program_fail_count" },
+ [0xac] = {1, "erase_fail_count" },
+ [0xad] = {2, "wear_leveling_count" },
+ [0xb8] = {3, "end_to_end_error_count" },
+ [0xc7] = {4, "crc_error_count" },
+ [0xe2] = {5, "timed_workload_media_wear" },
+ [0xe3] = {6, "timed_workload_host_reads" },
+ [0xe4] = {7, "timed_workload_timer" },
+ [0xea] = {8, "thermal_throttle_status" },
+ [0xf0] = {9, "lifetime_write_amplification"},
+ [0xf3] = {10, "pll_lock_loss_count" },
+ [0xf4] = {11, "nand_bytes_written" },
+ [0xf5] = {12, "host_bytes_written" },
+ [0xf6] = {13, "system_area_life_remaining" },
+ [0xf9] = {14, "firmware_update_count" },
+ [0xfa] = {15, "dram_cecc_count" },
+ [0xfb] = {16, "dram_uecc_count" },
+ [0xfc] = {17, "xor_pass_count" },
+ [0xfd] = {18, "xor_fail_count" },
+ [0xfe] = {19, "xor_invoked_count" },
+ [0xe5] = {20, "inflight_read_io_cmd" },
+ [0xe6] = {21, "flash_error_media_count" },
+ [0xf8] = {22, "nand_bytes_read" },
+ [0xe7] = {23, "temp_since_born" },
+ [0xe8] = {24, "power_consumption" },
+ [0xaf] = {25, "temp_since_bootup" },
+ [0xeb] = {26, "thermal_throttle_time" },
+ };
+
+ for (int i = 0; i < item_count; i++, item++) {
+ if (item->id == 0)
+ continue;
+
+ printf("%#-12" PRIx8 "%-36s%-12d", item->id, items[item->id].attr, item->norm);
+ switch (item->id) {
+ case 0xad:
+ printf("min: %d, max: %d, avg: %d\n",
+ le16_to_cpu(item->wear_level.min),
+ le16_to_cpu(item->wear_level.max),
+ le16_to_cpu(item->wear_level.avg));
+ break;
+ case 0xe7:
+ printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n",
+ K2C(le16_to_cpu(item->temp_since_born.max)),
+ le16_to_cpu(item->temp_since_born.max),
+ K2C(le16_to_cpu(item->temp_since_born.min)),
+ le16_to_cpu(item->temp_since_born.min),
+ K2C(le16_to_cpu(item->temp_since_born.curr)),
+ le16_to_cpu(item->temp_since_born.curr));
+ break;
+ case 0xe8:
+ printf("max: %d, min: %d, curr: %d\n",
+ le16_to_cpu(item->power_consumption.max),
+ le16_to_cpu(item->power_consumption.min),
+ le16_to_cpu(item->power_consumption.curr));
+ break;
+ case 0xaf:
+ printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n",
+ K2C(le16_to_cpu(item->temp_since_power_on.max)),
+ le16_to_cpu(item->temp_since_power_on.max),
+ K2C(le16_to_cpu(item->temp_since_power_on.min)),
+ le16_to_cpu(item->temp_since_power_on.min),
+ K2C(le16_to_cpu(item->temp_since_power_on.curr)),
+ le16_to_cpu(item->temp_since_power_on.curr));
+ break;
+ default:
+ printf("%" PRIu64 "\n", int48_to_long(item->raw));
+ break;
+ }
+ }
+}
+
+static void smart_log_add_v3_print(struct smart_log_add_item_10 *item, int item_count)
+{
+ static const struct smart_log_add_item items[0xff] = {
+ [0xab] = {0, "program_fail_count" },
+ [0xac] = {1, "erase_fail_count" },
+ [0xad] = {2, "wear_leveling_count" },
+ [0xb8] = {3, "ext_e2e_err_count" },
+ [0xc7] = {4, "crc_err_count" },
+ [0xf4] = {5, "nand_bytes_written" },
+ [0xf5] = {6, "host_bytes_written" },
+ [0xd0] = {7, "reallocated_sector_count" },
+ [0xd1] = {8, "uncorrectable_sector_count" },
+ [0xd2] = {9, "nand_uecc_detection" },
+ [0xd3] = {10, "nand_xor_correction" },
+ [0xd4] = {12, "gc_count" }, // 11 is reserved
+ [0xd5] = {13, "dram_uecc_detection_count" },
+ [0xd6] = {14, "sram_uecc_detection_count" },
+ [0xd7] = {15, "internal_raid_recovery_fail_count"},
+ [0xd8] = {16, "inflight_cmds" },
+ [0xd9] = {17, "internal_e2e_err_count" },
+ [0xda] = {19, "die_fail_count" }, // 18 is reserved
+ [0xdb] = {20, "wear_leveling_execution_count" },
+ [0xdc] = {21, "read_disturb_count" },
+ [0xdd] = {22, "data_retention_count" },
+ [0xde] = {23, "capacitor_health" },
+ };
+
+ for (int i = 0; i < item_count; i++, item++) {
+ if (item->id == 0)
+ continue;
+
+ printf("%#-12" PRIx8 "%-36s%-12d", item->id, items[item->id].attr, item->norm);
+ switch (item->id) {
+ case 0xad:
+ printf("min: %d, max: %d, avg: %d\n",
+ le16_to_cpu(item->wear_level.min),
+ le16_to_cpu(item->wear_level.max),
+ le16_to_cpu(item->wear_level.avg));
+ break;
+ default:
+ printf("%" PRIu64 "\n", int48_to_long(item->raw));
+ break;
+ }
+ }
+}
+
+static void smart_log_add_print(struct smart_log_add *log, const char *devname)
+{
+ uint8_t version = log->raw[511];
+
+ printf("Version: %u\n", version);
+ printf("\n");
+ printf("Additional Smart Log for NVMe device: %s\n", devname);
+ printf("\n");
+
+ printf("%-12s%-36s%-12s%s\n", "Id", "Key", "Normalized", "Raw");
+
+ switch (version) {
+ case 0:
+ return smart_log_add_v0_print(&log->v0_raw[0],
+ sizeof(struct smart_log_add_v0) / sizeof(struct smart_log_add_item_12));
+ case 2:
+ return smart_log_add_v2_print(&log->v2_raw[0],
+ sizeof(struct smart_log_add_v2) / sizeof(struct smart_log_add_item_12));
+ case 3:
+ return smart_log_add_v3_print(&log->v3_raw[0],
+ sizeof(struct smart_log_add_v3) / sizeof(struct smart_log_add_item_10));
+
+ case 1:
+ fprintf(stderr, "Version %d: N/A\n", version);
+ break;
+ default:
+ fprintf(stderr, "Version %d: Not supported yet\n", version);
+ break;
+ }
+}
+
+static int mb_get_smart_log_add(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ int err = 0;
+
+ // Get the configuration
+
+ struct config {
+ bool raw_binary;
+ };
+
+ struct config cfg = {0};
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, "dump the whole log buffer in binary format"),
+ OPT_END()};
+
+ // Open device
+
+ struct nvme_dev *dev = NULL;
+
+ err = parse_and_open(&dev, argc, argv, cmd->help, opts);
+ if (err)
+ return err;
+
+ // Get log
+
+ struct smart_log_add log = {0};
+
+ err = nvme_get_log_simple(dev_fd(dev), LID_SMART_LOG_ADD, sizeof(struct smart_log_add), &log);
+ if (!err) {
+ if (!cfg.raw_binary)
+ smart_log_add_print(&log, dev->name);
+ else
+ d_raw((unsigned char *)&log, sizeof(struct smart_log_add));
+ } else if (err > 0) {
+ nvme_show_status(err);
+ } else {
+ nvme_show_error("%s: %s", cmd->name, nvme_strerror(errno));
+ }
+
+ // Close device
+
+ dev_close(dev);
+ return err;
+}
+
+// performance-monitor
+
+struct latency_stats_bucket {
+ char *start_threshold;
+ char *end_threshold;
+};
+
+struct __packed latency_stats {
+ union {
+ struct latency_stats_v2_0 {
+ uint32_t minor_version;
+ uint32_t major_version;
+ uint32_t bucket_read_data[32];
+ uint32_t rsvd[32];
+ uint32_t bucket_write_data[32];
+ uint32_t rsvd1[32];
+ uint32_t bucket_trim_data[32];
+ uint32_t rsvd2[32];
+ uint8_t rsvd3[248];
+ } v2_0;
+ uint8_t raw[1024];
+ };
+};
+
+struct __packed high_latency_log {
+ union {
+ struct high_latency_log_v1 {
+ uint32_t version;
+ struct high_latency_log_entry {
+ uint64_t timestamp; // ms
+ uint32_t latency;
+ uint32_t qid;
+ uint32_t opcode : 8;
+ uint32_t fuse : 2;
+ uint32_t psdt : 2;
+ uint32_t cid : 16;
+ uint32_t rsvd : 4;
+ uint32_t nsid;
+ uint64_t slba;
+ uint32_t nlb : 16;
+ uint32_t dtype : 8;
+ uint32_t pinfo : 4;
+ uint32_t fua : 1;
+ uint32_t lr : 1;
+ uint32_t rsvd1 : 2;
+ uint8_t rsvd2[28];
+ } entries[1024];
+ } v1;
+ uint8_t raw[4 + 1024 * 64];
+ };
+};
+
+struct __packed performance_stats {
+ union {
+ struct performance_stats_v1 {
+ uint8_t version;
+ uint8_t rsvd[3];
+ struct performance_stats_timestamp {
+ uint8_t timestamp[6];
+ struct performance_stats_entry {
+ uint16_t read_iops; // K IOPS
+ uint16_t read_bandwidth; // MiB
+ uint32_t read_latency; // us
+ uint32_t read_latency_max; // us
+ uint16_t write_iops; // K IOPS
+ uint16_t write_bandwidth; // MiB
+ uint32_t write_latency; // us
+ uint32_t write_latency_max; // us
+ } entries[3600];
+ } timestamps[24];
+ } v1;
+ uint8_t raw[4 + 24 * (6 + 3600 * 24)];
+ };
+};
+
+static int mb_set_latency_feature(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err = 0;
+
+ // Get the configuration
+
+ struct config {
+ uint32_t perf_monitor;
+ uint32_t cmd_mask;
+ uint32_t read_threshold;
+ uint32_t write_threshold;
+ uint32_t de_allocate_trim_threshold;
+ };
+
+ struct config cfg = {0};
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("sel-perf-log", 's', &cfg.perf_monitor,
+ "Select features to turn on, default: Disable\n"
+ " bit 0: latency statistics\n"
+ " bit 1: high latency log\n"
+ " bit 2: Performance stat"),
+ OPT_UINT("set-commands-mask", 'm', &cfg.cmd_mask,
+ "Set Enable, default: Disable\n"
+ " bit 0: Read commands\n"
+ " bit 1: high Write commands\n"
+ " bit 2: De-allocate/TRIM (this bit is not worked for Performance stat.)"),
+ OPT_UINT("set-read-threshold", 'r', &cfg.read_threshold,
+ "set read high latency log threshold, it's a 0-based value and unit is 10ms"),
+ OPT_UINT("set-write-threshold", 'w', &cfg.write_threshold,
+ "set write high latency log threshold, it's a 0-based value and unit is 10ms"),
+ OPT_UINT("set-trim-threshold", 't', &cfg.de_allocate_trim_threshold,
+ "set trim high latency log threshold, it's a 0-based value and unit is 10ms"),
+ OPT_END()};
+
+ // Open device
+
+ struct nvme_dev *dev = NULL;
+
+ err = parse_and_open(&dev, argc, argv, cmd->help, opts);
+ if (err)
+ return err;
+
+
+ // Set feature
+
+ uint32_t result = 0;
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = FID_LATENCY_FEATURE,
+ .nsid = 0,
+ .cdw11 = 0 | cfg.perf_monitor,
+ .cdw12 = 0 | cfg.cmd_mask,
+ .cdw13 = 0 |
+ (cfg.read_threshold & 0xff) |
+ ((cfg.write_threshold & 0xff) << 8) |
+ ((cfg.de_allocate_trim_threshold & 0xff) << 16),
+ .cdw15 = 0,
+ .save = 0,
+ .uuidx = 0,
+ .data = NULL,
+ .data_len = 0,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_set_features(&args);
+ if (!err)
+ printf("%s have done successfully. result = %#" PRIx32 ".\n", cmd->name, result);
+ else if (err > 0)
+ nvme_show_status(err);
+ else
+ nvme_show_error("%s: %s", cmd->name, nvme_strerror(errno));
+
+ // Close device
+
+ dev_close(dev);
+ return err;
+}
+
+static int mb_get_latency_feature(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err = 0;
+
+ // Get the configuration
+
+ OPT_ARGS(opts) = {
+ OPT_END()};
+
+ // Open device
+
+ struct nvme_dev *dev = NULL;
+
+ err = parse_and_open(&dev, argc, argv, cmd->help, opts);
+ if (err)
+ return err;
+
+ // Get feature
+
+ uint32_t result = 0;
+
+ err = nvme_get_features_simple(dev_fd(dev), FID_LATENCY_FEATURE, 0, &result);
+ if (!err) {
+ printf("%s have done successfully. result = %#" PRIx32 ".\n", cmd->name, result);
+
+ printf("latency statistics enable status = %d\n", (result & (0x01 << 0)) >> 0);
+ printf("high latency enable status = %d\n", (result & (0x01 << 1)) >> 1);
+ printf("performance stat enable status = %d\n", (result & (0x01 << 2)) >> 2);
+
+ printf("Monitor Read command = %d\n", (result & (0x01 << 4)) >> 4);
+ printf("Monitor Write command = %d\n", (result & (0x01 << 5)) >> 5);
+ printf("Monitor Trim command = %d\n", (result & (0x01 << 6)) >> 6);
+
+ printf("Threshold for Read = %dms\n", (((result & (0xff << 8)) >> 8) + 1) * 10);
+ printf("Threshold for Write = %dms\n", (((result & (0xff << 16)) >> 16) + 1) * 10);
+ printf("Threshold for Trim = %dms\n", (((result & (0xff << 24)) >> 24) + 1) * 10);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ } else {
+ nvme_show_error("%s: %s", cmd->name, nvme_strerror(errno));
+ }
+
+ // Close device
+
+ dev_close(dev);
+ return err;
+}