summaryrefslogtreecommitdiffstats
path: root/examples/telemetry-listen.c
blob: 746992b5d7f60b389c666e397a33907cacdca3a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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;
}