xref: /illumos-gate/usr/src/uts/common/io/nvme/nvme_stat.c (revision e5d0cebc3bbd01b8ae62cebd964dde7bb8157b02)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2024 Oxide Computer Company
14  */
15 
16 #include "nvme_reg.h"
17 #include "nvme_var.h"
18 
19 static void
20 nvme_stat_device_cleanup(nvme_t *nvme)
21 {
22 	if (nvme->n_device_kstat != NULL) {
23 		kstat_delete(nvme->n_device_kstat);
24 		nvme->n_device_kstat = NULL;
25 	}
26 }
27 
28 static boolean_t
29 nvme_stat_device_init(nvme_t *nvme)
30 {
31 	kstat_t *ksp = kstat_create(NVME_MODULE_NAME,
32 	    ddi_get_instance(nvme->n_dip), "device", "controller",
33 	    KSTAT_TYPE_NAMED,
34 	    sizeof (nvme_device_stat_t) / sizeof (kstat_named_t),
35 	    KSTAT_FLAG_VIRTUAL);
36 	nvme_device_stat_t *nds = &nvme->n_device_stat;
37 
38 	if (ksp == NULL) {
39 		dev_err(nvme->n_dip, CE_WARN,
40 		    "!failed to create device kstats");
41 		return (false);
42 	}
43 
44 	nvme->n_device_kstat = ksp;
45 	ksp->ks_data = nds;
46 
47 #define	STAT_INIT(stat) \
48 	kstat_named_init(&nds->nds_ ## stat, #stat, KSTAT_DATA_UINT64)
49 
50 	/* Errors detected by driver */
51 	STAT_INIT(dma_bind_err);
52 	STAT_INIT(abort_timeout);
53 	STAT_INIT(abort_failed);
54 	STAT_INIT(abort_successful);
55 	STAT_INIT(abort_unsuccessful);
56 	STAT_INIT(cmd_timeout);
57 	STAT_INIT(wrong_logpage);
58 	STAT_INIT(unknown_logpage);
59 	STAT_INIT(too_many_cookies);
60 	STAT_INIT(unknown_cid);
61 
62 	/* Errors detected by hardware */
63 	STAT_INIT(inv_cmd_err);
64 	STAT_INIT(inv_field_err);
65 	STAT_INIT(inv_nsfmt_err);
66 	STAT_INIT(data_xfr_err);
67 	STAT_INIT(internal_err);
68 	STAT_INIT(abort_rq_err);
69 	STAT_INIT(abort_pwrloss_err);
70 	STAT_INIT(abort_sq_del);
71 	STAT_INIT(nvm_cap_exc);
72 	STAT_INIT(nvm_ns_notrdy);
73 	STAT_INIT(nvm_ns_formatting);
74 	STAT_INIT(inv_cq_err);
75 	STAT_INIT(inv_qid_err);
76 	STAT_INIT(max_qsz_exc);
77 	STAT_INIT(inv_int_vect);
78 	STAT_INIT(inv_log_page);
79 	STAT_INIT(inv_format);
80 	STAT_INIT(inv_q_del);
81 	STAT_INIT(cnfl_attr);
82 	STAT_INIT(inv_prot);
83 	STAT_INIT(readonly);
84 	STAT_INIT(inv_fwslot);
85 	STAT_INIT(inv_fwimg);
86 	STAT_INIT(fwact_creset);
87 	STAT_INIT(fwact_nssr);
88 	STAT_INIT(fwact_reset);
89 	STAT_INIT(fwact_mtfa);
90 	STAT_INIT(fwact_prohibited);
91 	STAT_INIT(fw_overlap);
92 	STAT_INIT(inv_cmdseq_err);
93 
94 	/* Errors reported by asynchronous events */
95 	STAT_INIT(diagfail_event);
96 	STAT_INIT(persistent_event);
97 	STAT_INIT(transient_event);
98 	STAT_INIT(fw_load_event);
99 	STAT_INIT(reliability_event);
100 	STAT_INIT(temperature_event);
101 	STAT_INIT(spare_event);
102 	STAT_INIT(vendor_event);
103 	STAT_INIT(notice_event);
104 	STAT_INIT(unknown_event);
105 
106 #undef STAT_INIT
107 
108 	kstat_install(nvme->n_device_kstat);
109 	return (true);
110 }
111 
112 static void
113 nvme_stat_admin_cleanup(nvme_t *nvme)
114 {
115 	if (nvme->n_admin_kstat != NULL) {
116 		kstat_delete(nvme->n_admin_kstat);
117 		nvme->n_admin_kstat = NULL;
118 		mutex_destroy(&nvme->n_admin_stat_mutex);
119 	}
120 }
121 
122 static boolean_t
123 nvme_stat_admin_init(nvme_t *nvme)
124 {
125 	kstat_t *ksp = kstat_create(NVME_MODULE_NAME,
126 	    ddi_get_instance(nvme->n_dip), "admin", "controller",
127 	    KSTAT_TYPE_NAMED,
128 	    sizeof (nvme_admin_stat_t) / sizeof (kstat_named_t),
129 	    KSTAT_FLAG_VIRTUAL);
130 	nvme_admin_stat_t *nas = &nvme->n_admin_stat;
131 
132 	if (ksp == NULL) {
133 		dev_err(nvme->n_dip, CE_WARN,
134 		    "!failed to create admin kstats");
135 		return (false);
136 	}
137 
138 	nvme->n_admin_kstat = ksp;
139 	ksp->ks_data = nas;
140 	mutex_init(&nvme->n_admin_stat_mutex, NULL, MUTEX_DRIVER, NULL);
141 
142 #define	STAT_INIT_ONE(stat, index, postfix) \
143 	kstat_named_init(&nas->nas_ ## stat[index], #stat postfix, \
144 	KSTAT_DATA_UINT64)
145 
146 #define	STAT_INIT(stat) \
147 	do { \
148 		STAT_INIT_ONE(stat, NAS_CNT, "_cnt"); \
149 		STAT_INIT_ONE(stat, NAS_AVG, "_avg"); \
150 		STAT_INIT_ONE(stat, NAS_MAX, "_max"); \
151 	} while (0)
152 
153 	STAT_INIT(getlogpage);
154 	STAT_INIT(identify);
155 	STAT_INIT(abort);
156 	STAT_INIT(fwactivate);
157 	STAT_INIT(fwimgload);
158 	STAT_INIT(nsformat);
159 	STAT_INIT(vendor);
160 	STAT_INIT(other);
161 
162 #undef STAT_INIT
163 #undef STAT_INIT_ONE
164 
165 	kstat_install(nvme->n_admin_kstat);
166 	return (true);
167 }
168 
169 void
170 nvme_stat_cleanup(nvme_t *nvme)
171 {
172 	nvme_stat_device_cleanup(nvme);
173 	nvme_stat_admin_cleanup(nvme);
174 }
175 
176 boolean_t
177 nvme_stat_init(nvme_t *nvme)
178 {
179 	if (!nvme_stat_device_init(nvme) || !nvme_stat_admin_init(nvme)) {
180 		nvme_stat_cleanup(nvme);
181 		return (B_FALSE);
182 	}
183 	return (B_TRUE);
184 }
185 
186 void
187 nvme_admin_stat_cmd(nvme_t *nvme, nvme_cmd_t *cmd)
188 {
189 	hrtime_t t;
190 	uint64_t cnt, avg;
191 	kstat_named_t *data, *cntd, *avgd, *maxd;
192 
193 	switch (cmd->nc_sqe.sqe_opc) {
194 	case NVME_OPC_DELETE_SQUEUE:
195 	case NVME_OPC_CREATE_SQUEUE:
196 	case NVME_OPC_DELETE_CQUEUE:
197 	case NVME_OPC_CREATE_CQUEUE:
198 		/* No statistics are kept for these opcodes */
199 		return;
200 	case NVME_OPC_GET_LOG_PAGE:
201 		data = nvme->n_admin_stat.nas_getlogpage;
202 		break;
203 	case NVME_OPC_IDENTIFY:
204 		data = nvme->n_admin_stat.nas_identify;
205 		break;
206 	case NVME_OPC_ABORT:
207 		data = nvme->n_admin_stat.nas_abort;
208 		break;
209 	case NVME_OPC_FW_ACTIVATE:
210 		data = nvme->n_admin_stat.nas_fwactivate;
211 		break;
212 	case NVME_OPC_FW_IMAGE_LOAD:
213 		data = nvme->n_admin_stat.nas_fwimgload;
214 		break;
215 	case NVME_OPC_NVM_FORMAT:
216 		data = nvme->n_admin_stat.nas_nsformat;
217 		break;
218 	case NVME_OPC_VENDOR_LOW ... NVME_OPC_VENDOR_HIGH:
219 		data = nvme->n_admin_stat.nas_vendor;
220 		break;
221 	default:
222 		data = nvme->n_admin_stat.nas_other;
223 		break;
224 	}
225 
226 	t = gethrtime() - cmd->nc_submit_ts;
227 
228 	cntd = &data[NAS_CNT];
229 	avgd = &data[NAS_AVG];
230 	maxd = &data[NAS_MAX];
231 
232 	mutex_enter(&nvme->n_admin_stat_mutex);
233 	cnt = cntd->value.ui64;
234 	avg = avgd->value.ui64;
235 
236 	/*
237 	 * Update the cumulative rolling average.
238 	 * Since `t` and `avg` are orders of magnitude greater than `cnt` it
239 	 * is sufficient to directly adjust the current average towards the
240 	 * new value.
241 	 */
242 	if (t > avg)
243 		avg += (t - avg) / (cnt + 1);
244 	else
245 		avg -= (avg - t) / (cnt + 1);
246 	cntd->value.ui64++;
247 	avgd->value.ui64 = avg;
248 	if (t > maxd->value.ui64)
249 		maxd->value.ui64 = t;
250 	mutex_exit(&nvme->n_admin_stat_mutex);
251 }
252