xref: /netbsd/usr.bin/vmstat/drvstats.c (revision d51a8cc9)
1 /*	$NetBSD: drvstats.c,v 1.14 2021/11/27 22:16:42 rillig Exp $	*/
2 
3 /*
4  * Copyright (c) 1996 John M. Vinopal
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *      This product includes software developed for the NetBSD Project
18  *      by John M. Vinopal.
19  * 4. The name of the author may not be used to endorse or promote products
20  *    derived from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/param.h>
36 #include <sys/sched.h>
37 #include <sys/sysctl.h>
38 #include <sys/time.h>
39 #include <sys/iostat.h>
40 
41 #include <err.h>
42 #include <fcntl.h>
43 #include <limits.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
48 #include "drvstats.h"
49 
50 /* Structures to hold the statistics. */
51 struct _drive	cur, last;
52 
53 extern int	hz;
54 
55 /* sysctl hw.drivestats buffer. */
56 static struct io_sysctl	*drives = NULL;
57 
58 /* Backward compatibility references. */
59 size_t		ndrive = 0;
60 int		*drv_select;
61 char		**dr_name;
62 
63 /* Missing from <sys/time.h> */
64 #define	timerset(tvp, uvp) do {						\
65 	((uvp)->tv_sec = (tvp)->tv_sec);				\
66 	((uvp)->tv_usec = (tvp)->tv_usec);				\
67 } while (0)
68 
69 /*
70  * Take the delta between the present values and the last recorded
71  * values, storing the present values in the 'last' structure, and
72  * the delta values in the 'cur' structure.
73  */
74 void
drvswap(void)75 drvswap(void)
76 {
77 	u_int64_t tmp;
78 	size_t	i;
79 
80 #define	SWAP(fld) do {							\
81 	tmp = cur.fld;							\
82 	cur.fld -= last.fld;						\
83 	last.fld = tmp;							\
84 } while (0)
85 
86 #define DELTA(x) do {							\
87 		timerclear(&tmp_timer);					\
88 		timerset(&(cur.x), &tmp_timer);				\
89 		timersub(&tmp_timer, &(last.x), &(cur.x));		\
90 		timerclear(&(last.x));					\
91 		timerset(&tmp_timer, &(last.x));			\
92 } while (0)
93 
94 	for (i = 0; i < ndrive; i++) {
95 		struct timeval	tmp_timer;
96 
97 		if (!cur.select[i])
98 			continue;
99 
100 		/*
101 		 * When a drive is replaced with one of the same
102 		 * name, the previous statistics are invalid. Try
103 		 * to detect this by validating counters and timestamp
104 		 */
105 		if ((cur.rxfer[i] == 0 && cur.wxfer[i] == 0)
106 		    || cur.rxfer[i] - last.rxfer[i] > INT64_MAX
107 		    || cur.wxfer[i] - last.wxfer[i] > INT64_MAX
108 		    || cur.seek[i] - last.seek[i] > INT64_MAX
109 		    || (cur.timestamp[i].tv_sec == 0 &&
110 		        cur.timestamp[i].tv_usec == 0)) {
111 
112 			last.rxfer[i] = cur.rxfer[i];
113 			last.wxfer[i] = cur.wxfer[i];
114 			last.seek[i] = cur.seek[i];
115 			last.rbytes[i] = cur.rbytes[i];
116 			last.wbytes[i] = cur.wbytes[i];
117 
118 			timerclear(&last.wait[i]);
119 			timerclear(&last.time[i]);
120 			timerclear(&last.waitsum[i]);
121 			timerclear(&last.busysum[i]);
122 			timerclear(&last.timestamp[i]);
123 		}
124 
125 		/* Delta Values. */
126 		SWAP(rxfer[i]);
127 		SWAP(wxfer[i]);
128 		SWAP(seek[i]);
129 		SWAP(rbytes[i]);
130 		SWAP(wbytes[i]);
131 
132 		DELTA(wait[i]);
133 		DELTA(time[i]);
134 		DELTA(waitsum[i]);
135 		DELTA(busysum[i]);
136 		DELTA(timestamp[i]);
137 	}
138 }
139 
140 void
tkswap(void)141 tkswap(void)
142 {
143 	u_int64_t tmp;
144 
145 	SWAP(tk_nin);
146 	SWAP(tk_nout);
147 }
148 
149 void
cpuswap(void)150 cpuswap(void)
151 {
152 	double etime;
153 	u_int64_t tmp;
154 	int	i, state;
155 
156 	for (i = 0; i < CPUSTATES; i++)
157 		SWAP(cp_time[i]);
158 
159 	etime = 0;
160 	for (state = 0; state < CPUSTATES; ++state) {
161 		etime += cur.cp_time[state];
162 	}
163 	if (etime == 0)
164 		etime = 1;
165 	etime /= hz;
166 	etime /= cur.cp_ncpu;
167 
168 	cur.cp_etime = etime;
169 }
170 #undef DELTA
171 #undef SWAP
172 
173 /*
174  * Read the drive statistics for each drive in the drive list.
175  * Also collect statistics for tty i/o and CPU ticks.
176  */
177 void
drvreadstats(void)178 drvreadstats(void)
179 {
180 	size_t		size, i, j, count;
181 	int		mib[3];
182 
183 	mib[0] = CTL_HW;
184 	mib[1] = HW_IOSTATS;
185 	mib[2] = sizeof(struct io_sysctl);
186 
187 	size = ndrive * sizeof(struct io_sysctl);
188 	if (sysctl(mib, 3, drives, &size, NULL, 0) < 0)
189 		err(1, "sysctl hw.iostats failed");
190 	/* recalculate array length */
191 	count = size / sizeof(struct io_sysctl);
192 
193 #define COPYF(x,k,l) cur.x[k] = drives[l].x
194 #define COPYT(x,k,l) do {						\
195 		cur.x[k].tv_sec = drives[l].x##_sec;			\
196 		cur.x[k].tv_usec = drives[l].x##_usec;			\
197 } while (0)
198 
199 	for (i = 0, j = 0; i < ndrive && j < count; i++) {
200 
201 		/*
202 		 * skip removed entries
203 		 *
204 		 * we cannot detect entries replaced with
205 		 * devices of the same name (e.g. unplug/replug).
206 		 */
207 		if (strcmp(cur.name[i], drives[j].name)) {
208 			cur.select[i] = 0;
209 			continue;
210 		}
211 
212 		COPYF(rxfer, i, j);
213 		COPYF(wxfer, i, j);
214 		COPYF(seek, i, j);
215 		COPYF(rbytes, i, j);
216 		COPYF(wbytes, i, j);
217 
218 		COPYT(wait, i, j);
219 		COPYT(time, i, j);
220 		COPYT(waitsum, i, j);
221 		COPYT(busysum, i, j);
222 		COPYT(timestamp, i, j);
223 
224 		++j;
225 	}
226 
227 	/* shrink table to new size */
228 	ndrive = j;
229 
230 	mib[0] = CTL_KERN;
231 	mib[1] = KERN_TKSTAT;
232 	mib[2] = KERN_TKSTAT_NIN;
233 	size = sizeof(cur.tk_nin);
234 	if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0)
235 		cur.tk_nin = 0;
236 
237 	mib[2] = KERN_TKSTAT_NOUT;
238 	size = sizeof(cur.tk_nout);
239 	if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0)
240 		cur.tk_nout = 0;
241 
242 	size = sizeof(cur.cp_time);
243 	(void)memset(cur.cp_time, 0, size);
244 	mib[0] = CTL_KERN;
245 	mib[1] = KERN_CP_TIME;
246 	if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0)
247 		(void)memset(cur.cp_time, 0, sizeof(cur.cp_time));
248 }
249 #undef COPYT
250 #undef COPYF
251 
252 /*
253  * Read collect statistics for tty i/o.
254  */
255 
256 void
tkreadstats(void)257 tkreadstats(void)
258 {
259 	size_t		size;
260 	int		mib[3];
261 
262 	mib[0] = CTL_KERN;
263 	mib[1] = KERN_TKSTAT;
264 	mib[2] = KERN_TKSTAT_NIN;
265 	size = sizeof(cur.tk_nin);
266 	if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0)
267 		cur.tk_nin = 0;
268 
269 	mib[2] = KERN_TKSTAT_NOUT;
270 	size = sizeof(cur.tk_nout);
271 	if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0)
272 		cur.tk_nout = 0;
273 }
274 
275 /*
276  * Read collect statistics for CPU ticks.
277  */
278 
279 void
cpureadstats(void)280 cpureadstats(void)
281 {
282 	size_t		size;
283 	int		mib[2];
284 
285 	size = sizeof(cur.cp_time);
286 	(void)memset(cur.cp_time, 0, size);
287 	mib[0] = CTL_KERN;
288 	mib[1] = KERN_CP_TIME;
289 	if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0)
290 		(void)memset(cur.cp_time, 0, sizeof(cur.cp_time));
291 }
292 
293 /*
294  * Perform all of the initialization and memory allocation needed to
295  * track drive statistics.
296  */
297 int
drvinit(int selected)298 drvinit(int selected)
299 {
300 	struct clockinfo clockinfo;
301 	size_t		size, i;
302 	static int	once = 0;
303 	int		mib[3];
304 
305 	if (once)
306 		return (1);
307 
308 	mib[0] = CTL_HW;
309 	mib[1] = HW_NCPU;
310 	size = sizeof(cur.cp_ncpu);
311 	if (sysctl(mib, 2, &cur.cp_ncpu, &size, NULL, 0) == -1)
312 		err(1, "sysctl hw.ncpu failed");
313 
314 	mib[0] = CTL_KERN;
315 	mib[1] = KERN_CLOCKRATE;
316 	size = sizeof(clockinfo);
317 	if (sysctl(mib, 2, &clockinfo, &size, NULL, 0) == -1)
318 		err(1, "sysctl kern.clockrate failed");
319 	hz = clockinfo.stathz;
320 	if (!hz)
321 		hz = clockinfo.hz;
322 
323 	mib[0] = CTL_HW;
324 	mib[1] = HW_IOSTATS;
325 	mib[2] = sizeof(struct io_sysctl);
326 	if (sysctl(mib, 3, NULL, &size, NULL, 0) == -1)
327 		err(1, "sysctl hw.drivestats failed");
328 	ndrive = size / sizeof(struct io_sysctl);
329 
330 	if (size == 0) {
331 		warnx("No drives attached.");
332 	} else {
333 		drives = (struct io_sysctl *)malloc(size);
334 		if (drives == NULL)
335 			errx(1, "Memory allocation failure.");
336 	}
337 
338 	/* Allocate space for the statistics. */
339 	cur.time = calloc(ndrive, sizeof(struct timeval));
340 	cur.wait = calloc(ndrive, sizeof(struct timeval));
341 	cur.waitsum = calloc(ndrive, sizeof(struct timeval));
342 	cur.busysum = calloc(ndrive, sizeof(struct timeval));
343 	cur.timestamp = calloc(ndrive, sizeof(struct timeval));
344 	cur.rxfer = calloc(ndrive, sizeof(u_int64_t));
345 	cur.wxfer = calloc(ndrive, sizeof(u_int64_t));
346 	cur.seek = calloc(ndrive, sizeof(u_int64_t));
347 	cur.rbytes = calloc(ndrive, sizeof(u_int64_t));
348 	cur.wbytes = calloc(ndrive, sizeof(u_int64_t));
349 	cur.scale = calloc(ndrive, sizeof(int));
350 	last.time = calloc(ndrive, sizeof(struct timeval));
351 	last.wait = calloc(ndrive, sizeof(struct timeval));
352 	last.waitsum = calloc(ndrive, sizeof(struct timeval));
353 	last.busysum = calloc(ndrive, sizeof(struct timeval));
354 	last.timestamp = calloc(ndrive, sizeof(struct timeval));
355 	last.rxfer = calloc(ndrive, sizeof(u_int64_t));
356 	last.wxfer = calloc(ndrive, sizeof(u_int64_t));
357 	last.seek = calloc(ndrive, sizeof(u_int64_t));
358 	last.rbytes = calloc(ndrive, sizeof(u_int64_t));
359 	last.wbytes = calloc(ndrive, sizeof(u_int64_t));
360 	cur.select = calloc(ndrive, sizeof(int));
361 	cur.name = calloc(ndrive, sizeof(char *));
362 
363 	if (cur.time == NULL || cur.wait == NULL ||
364 	    cur.waitsum == NULL || cur.busysum == NULL ||
365 	    cur.timestamp == NULL ||
366 	    cur.rxfer == NULL || cur.wxfer == NULL ||
367 	    cur.seek == NULL || cur.rbytes == NULL ||
368 	    cur.wbytes == NULL ||
369 	    last.time == NULL || last.wait == NULL ||
370 	    last.waitsum == NULL || last.busysum == NULL ||
371 	    last.timestamp == NULL ||
372 	    last.rxfer == NULL || last.wxfer == NULL ||
373 	    last.seek == NULL || last.rbytes == NULL ||
374 	    last.wbytes == NULL ||
375 	    cur.select == NULL || cur.name == NULL)
376 		errx(1, "Memory allocation failure.");
377 
378 	/* Set up the compatibility interfaces. */
379 	drv_select = cur.select;
380 	dr_name = cur.name;
381 
382 	/* Read the drive names and set initial selection. */
383 	mib[0] = CTL_HW;		/* Should be still set from */
384 	mib[1] = HW_IOSTATS;		/* ... above, but be safe... */
385 	mib[2] = sizeof(struct io_sysctl);
386 	if (sysctl(mib, 3, drives, &size, NULL, 0) == -1)
387 		err(1, "sysctl hw.iostats failed");
388 	/* Recalculate array length */
389 	ndrive = size / sizeof(struct io_sysctl);
390 	for (i = 0; i < ndrive; i++) {
391 		cur.name[i] = strndup(drives[i].name, sizeof(drives[i].name));
392 		if (cur.name[i] == NULL)
393 			errx(1, "Memory allocation failure");
394 		cur.select[i] = selected;
395 	}
396 
397 	/* Never do this initialization again. */
398 	once = 1;
399 	return (1);
400 }
401