summaryrefslogtreecommitdiffstats
path: root/examples/telemetry-listen.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/telemetry-listen.c')
-rw-r--r--examples/telemetry-listen.c167
1 files changed, 167 insertions, 0 deletions
diff --git a/examples/telemetry-listen.c b/examples/telemetry-listen.c
new file mode 100644
index 0000000..746992b
--- /dev/null
+++ b/examples/telemetry-listen.c
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2020 Western Digital Corporation or its affiliates.
+ *
+ * Authors: Keith Busch <keith.busch@wdc.com>
+ */
+
+/**
+ * Open all nvme controller's uevent and listen for changes. If NVME_AEN event
+ * is observed with controller telemetry data, read the log and save it to a
+ * file in /var/log/ with the device's unique name and epoch timestamp.
+ */
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <libnvme.h>
+
+#include <ccan/endian/endian.h>
+
+struct events {
+ nvme_ctrl_t c;
+ int uevent_fd;
+};
+
+static int open_uevent(nvme_ctrl_t c)
+{
+ char buf[0x1000];
+ if (snprintf(buf, sizeof(buf), "%s/uevent", nvme_ctrl_get_sysfs_dir(c)) < 0)
+ return -1;
+ return open(buf, O_RDONLY);
+}
+
+static void save_telemetry(nvme_ctrl_t c)
+{
+ char buf[0x1000];
+ size_t log_size;
+ int ret, fd;
+ struct nvme_telemetry_log *log;
+ time_t s;
+
+ /* Clear the log (rae == false) at the end to see new telemetry events later */
+ ret = nvme_get_ctrl_telemetry(nvme_ctrl_get_fd(c), false, &log, NVME_TELEMETRY_DA_3, &log_size);
+ if (ret)
+ return;
+
+ s = time(NULL);
+ ret = snprintf(buf, sizeof(buf), "/var/log/%s-telemetry-%ld",
+ nvme_ctrl_get_subsysnqn(c), s);
+ if (ret < 0) {
+ free(log);
+ return;
+ }
+ log_size = (le16_to_cpu(log->dalb3) + 1) * NVME_LOG_TELEM_BLOCK_SIZE;
+
+ fd = open(buf, O_CREAT|O_WRONLY, S_IRUSR|S_IRGRP);
+ if (fd < 0) {
+ free(log);
+ return;
+ }
+
+ ret = write(fd, log, log_size);
+ if (ret < 0)
+ printf("failed to write telemetry log\n");
+ else
+ printf("telemetry log save as %s, wrote:%d size:%ld\n", buf,
+ ret, log_size);
+ close(fd);
+ free(log);
+}
+
+static void check_telemetry(nvme_ctrl_t c, int ufd)
+{
+ char buf[0x1000] = { 0 };
+ char *p, *ptr;
+
+ if (read(ufd, buf, sizeof(buf)) < 0)
+ return;
+
+ ptr = buf;
+ while ((p = strsep(&ptr, "\n")) != NULL) {
+ __u32 aen, type, info, lid;
+
+ if (sscanf(p, "NVME_AEN=0x%08x", &aen) != 1)
+ continue;
+
+ type = aen & 0x07;
+ info = (aen >> 8) & 0xff;
+ lid = (aen >> 16) & 0xff;
+
+ printf("%s: aen type:%x info:%x lid:%d\n",
+ nvme_ctrl_get_name(c), type, info, lid);
+ if (type == NVME_AER_NOTICE &&
+ info == NVME_AER_NOTICE_TELEMETRY)
+ save_telemetry(c);
+ }
+}
+
+static void wait_events(fd_set *fds, struct events *e, int nr)
+{
+ int ret, i;
+
+ for (i = 0; i < nr; i++)
+ check_telemetry(e[i].c, e[i].uevent_fd);
+
+ while (1) {
+ ret = select(nr, fds, NULL, NULL, NULL);
+ if (ret < 0)
+ return;
+
+ for (i = 0; i < nr; i++) {
+ if (!FD_ISSET(e[i].uevent_fd, fds))
+ continue;
+ check_telemetry(e[i].c, e[i].uevent_fd);
+ }
+ }
+}
+
+int main()
+{
+ struct events *e;
+ fd_set fds;
+ int i = 0;
+
+ nvme_subsystem_t s;
+ nvme_ctrl_t c;
+ nvme_host_t h;
+ nvme_root_t r;
+
+ r = nvme_scan(NULL);
+ if (!r)
+ return EXIT_FAILURE;
+
+ nvme_for_each_host(r, h)
+ nvme_for_each_subsystem(h, s)
+ nvme_subsystem_for_each_ctrl(s, c)
+ i++;
+
+ e = calloc(i, sizeof(e));
+ FD_ZERO(&fds);
+ i = 0;
+
+ nvme_for_each_host(r, h) {
+ nvme_for_each_subsystem(h, s) {
+ nvme_subsystem_for_each_ctrl(s, c) {
+ int fd = open_uevent(c);
+
+ if (fd < 0)
+ continue;
+ FD_SET(fd, &fds);
+ e[i].uevent_fd = fd;
+ e[i].c = c;
+ i++;
+ }
+ }
+ }
+
+ wait_events(&fds, e, i);
+ nvme_free_tree(r);
+ free(e);
+
+ return EXIT_SUCCESS;
+}