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. */
51struct _drive	cur, last;
52
53extern int	hz;
54
55/* sysctl hw.drivestats buffer. */
56static struct io_sysctl	*drives = NULL;
57
58/* Backward compatibility references. */
59size_t		ndrive = 0;
60int		*drv_select;
61char		**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 */
74void
75drvswap(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
140void
141tkswap(void)
142{
143	u_int64_t tmp;
144
145	SWAP(tk_nin);
146	SWAP(tk_nout);
147}
148
149void
150cpuswap(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 */
177void
178drvreadstats(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
256void
257tkreadstats(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
279void
280cpureadstats(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 */
297int
298drvinit(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