xref: /illumos-gate/usr/src/uts/common/io/nvme/nvme_stat.c (revision 9164a50bf932130cbb5097a16f6986873ce0e6e5)
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 
93 	/* Errors reported by asynchronous events */
94 	STAT_INIT(diagfail_event);
95 	STAT_INIT(persistent_event);
96 	STAT_INIT(transient_event);
97 	STAT_INIT(fw_load_event);
98 	STAT_INIT(reliability_event);
99 	STAT_INIT(temperature_event);
100 	STAT_INIT(spare_event);
101 	STAT_INIT(vendor_event);
102 	STAT_INIT(notice_event);
103 	STAT_INIT(unknown_event);
104 
105 #undef STAT_INIT
106 
107 	kstat_install(nvme->n_device_kstat);
108 	return (true);
109 }
110 
111 static void
112 nvme_stat_admin_cleanup(nvme_t *nvme)
113 {
114 	if (nvme->n_admin_kstat != NULL) {
115 		kstat_delete(nvme->n_admin_kstat);
116 		nvme->n_admin_kstat = NULL;
117 		mutex_destroy(&nvme->n_admin_stat_mutex);
118 	}
119 }
120 
121 static boolean_t
122 nvme_stat_admin_init(nvme_t *nvme)
123 {
124 	kstat_t *ksp = kstat_create(NVME_MODULE_NAME,
125 	    ddi_get_instance(nvme->n_dip), "admin", "controller",
126 	    KSTAT_TYPE_NAMED,
127 	    sizeof (nvme_admin_stat_t) / sizeof (kstat_named_t),
128 	    KSTAT_FLAG_VIRTUAL);
129 	nvme_admin_stat_t *nas = &nvme->n_admin_stat;
130 
131 	if (ksp == NULL) {
132 		dev_err(nvme->n_dip, CE_WARN,
133 		    "!failed to create admin kstats");
134 		return (false);
135 	}
136 
137 	nvme->n_admin_kstat = ksp;
138 	ksp->ks_data = nas;
139 	mutex_init(&nvme->n_admin_stat_mutex, NULL, MUTEX_DRIVER, NULL);
140 
141 #define	STAT_INIT_ONE(stat, index, postfix) \
142 	kstat_named_init(&nas->nas_ ## stat[index], #stat postfix, \
143 	KSTAT_DATA_UINT64)
144 
145 #define	STAT_INIT(stat) \
146 	do { \
147 		STAT_INIT_ONE(stat, NAS_CNT, "_cnt"); \
148 		STAT_INIT_ONE(stat, NAS_AVG, "_avg"); \
149 		STAT_INIT_ONE(stat, NAS_MAX, "_max"); \
150 	} while (0)
151 
152 	STAT_INIT(getlogpage);
153 	STAT_INIT(identify);
154 	STAT_INIT(abort);
155 	STAT_INIT(fwactivate);
156 	STAT_INIT(fwimgload);
157 	STAT_INIT(nsformat);
158 	STAT_INIT(vendor);
159 	STAT_INIT(other);
160 
161 #undef STAT_INIT
162 #undef STAT_INIT_ONE
163 
164 	kstat_install(nvme->n_admin_kstat);
165 	return (true);
166 }
167 
168 void
169 nvme_stat_cleanup(nvme_t *nvme)
170 {
171 	nvme_stat_device_cleanup(nvme);
172 	nvme_stat_admin_cleanup(nvme);
173 }
174 
175 boolean_t
176 nvme_stat_init(nvme_t *nvme)
177 {
178 	if (!nvme_stat_device_init(nvme) || !nvme_stat_admin_init(nvme)) {
179 		nvme_stat_cleanup(nvme);
180 		return (B_FALSE);
181 	}
182 	return (B_TRUE);
183 }
184 
185 void
186 nvme_admin_stat_cmd(nvme_t *nvme, nvme_cmd_t *cmd)
187 {
188 	hrtime_t t;
189 	uint64_t cnt, avg;
190 	kstat_named_t *data, *cntd, *avgd, *maxd;
191 
192 	switch (cmd->nc_sqe.sqe_opc) {
193 	case NVME_OPC_DELETE_SQUEUE:
194 	case NVME_OPC_CREATE_SQUEUE:
195 	case NVME_OPC_DELETE_CQUEUE:
196 	case NVME_OPC_CREATE_CQUEUE:
197 		/* No statistics are kept for these opcodes */
198 		return;
199 	case NVME_OPC_GET_LOG_PAGE:
200 		data = nvme->n_admin_stat.nas_getlogpage;
201 		break;
202 	case NVME_OPC_IDENTIFY:
203 		data = nvme->n_admin_stat.nas_identify;
204 		break;
205 	case NVME_OPC_ABORT:
206 		data = nvme->n_admin_stat.nas_abort;
207 		break;
208 	case NVME_OPC_FW_ACTIVATE:
209 		data = nvme->n_admin_stat.nas_fwactivate;
210 		break;
211 	case NVME_OPC_FW_IMAGE_LOAD:
212 		data = nvme->n_admin_stat.nas_fwimgload;
213 		break;
214 	case NVME_OPC_NVM_FORMAT:
215 		data = nvme->n_admin_stat.nas_nsformat;
216 		break;
217 	case NVME_OPC_VENDOR_LOW ... NVME_OPC_VENDOR_HIGH:
218 		data = nvme->n_admin_stat.nas_vendor;
219 		break;
220 	default:
221 		data = nvme->n_admin_stat.nas_other;
222 		break;
223 	}
224 
225 	t = gethrtime() - cmd->nc_submit_ts;
226 
227 	cntd = &data[NAS_CNT];
228 	avgd = &data[NAS_AVG];
229 	maxd = &data[NAS_MAX];
230 
231 	mutex_enter(&nvme->n_admin_stat_mutex);
232 	cnt = cntd->value.ui64;
233 	avg = avgd->value.ui64;
234 
235 	/*
236 	 * Update the cumulative rolling average.
237 	 * Since `t` and `avg` are orders of magnitude greater than `cnt` it
238 	 * is sufficient to directly adjust the current average towards the
239 	 * new value.
240 	 */
241 	if (t > avg)
242 		avg += (t - avg) / (cnt + 1);
243 	else
244 		avg -= (avg - t) / (cnt + 1);
245 	cntd->value.ui64++;
246 	avgd->value.ui64 = avg;
247 	if (t > maxd->value.ui64)
248 		maxd->value.ui64 = t;
249 	mutex_exit(&nvme->n_admin_stat_mutex);
250 }
251