1/*	$NetBSD: drvstats.c,v 1.4 2006/10/17 15:13:08 christos 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 <kvm.h>
44#include <limits.h>
45#include <nlist.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <unistd.h>
50#include "drvstats.h"
51
52static struct nlist namelist[] = {
53#define	X_TK_NIN	0
54	{ .n_name = "_tk_nin" },		/* tty characters in */
55#define	X_TK_NOUT	1
56	{ .n_name = "_tk_nout" },		/* tty characters out */
57#define	X_HZ		2
58	{ .n_name = "_hz" },		/* ticks per second */
59#define	X_STATHZ	3
60	{ .n_name = "_stathz" },
61#define	X_DRIVE_COUNT	4
62	{ .n_name = "_iostat_count" },	/* number of drives */
63#define	X_DRIVELIST	5
64	{ .n_name = "_iostatlist" },	/* TAILQ of drives */
65	{ .n_name = NULL },
66};
67
68/* Structures to hold the statistics. */
69struct _drive	cur, last;
70
71/* Kernel pointers: nlistf and memf defined in calling program. */
72static kvm_t	*kd = NULL;
73extern char	*nlistf;
74extern char	*memf;
75extern int	hz;
76
77/* Pointer to list of drives. */
78static struct io_stats	*iostathead = NULL;
79/* sysctl hw.drivestats buffer. */
80static struct io_sysctl	*drives = NULL;
81
82/* Backward compatibility references. */
83size_t		ndrive = 0;
84int		*drv_select;
85char		**dr_name;
86
87#define	KVM_ERROR(_string) do {						\
88	warnx("%s", (_string));						\
89	errx(1, "%s", kvm_geterr(kd));					\
90} while (/* CONSTCOND */0)
91
92/*
93 * Dereference the namelist pointer `v' and fill in the local copy
94 * 'p' which is of size 's'.
95 */
96#define	deref_nl(v, p, s) do {						\
97	deref_kptr((void *)namelist[(v)].n_value, (p), (s));		\
98} while (/* CONSTCOND */0)
99
100/* Missing from <sys/time.h> */
101#define	timerset(tvp, uvp) do {						\
102	((uvp)->tv_sec = (tvp)->tv_sec);				\
103	((uvp)->tv_usec = (tvp)->tv_usec);				\
104} while (/* CONSTCOND */0)
105
106static void deref_kptr(void *, void *, size_t);
107
108/*
109 * Take the delta between the present values and the last recorded
110 * values, storing the present values in the 'last' structure, and
111 * the delta values in the 'cur' structure.
112 */
113void
114drvswap(void)
115{
116	u_int64_t tmp;
117	size_t	i;
118
119#define	SWAP(fld) do {							\
120	tmp = cur.fld;							\
121	cur.fld -= last.fld;						\
122	last.fld = tmp;							\
123} while (/* CONSTCOND */0)
124
125	for (i = 0; i < ndrive; i++) {
126		struct timeval	tmp_timer;
127
128		if (!cur.select[i])
129			continue;
130
131		/* Delta Values. */
132		SWAP(rxfer[i]);
133		SWAP(wxfer[i]);
134		SWAP(seek[i]);
135		SWAP(rbytes[i]);
136		SWAP(wbytes[i]);
137
138		/* Delta Time. */
139		timerclear(&tmp_timer);
140		timerset(&(cur.time[i]), &tmp_timer);
141		timersub(&tmp_timer, &(last.time[i]), &(cur.time[i]));
142		timerclear(&(last.time[i]));
143		timerset(&tmp_timer, &(last.time[i]));
144	}
145}
146
147void
148tkswap(void)
149{
150	u_int64_t tmp;
151
152	SWAP(tk_nin);
153	SWAP(tk_nout);
154}
155
156void
157cpuswap(void)
158{
159	double etime;
160	u_int64_t tmp;
161	int	i, state;
162
163	for (i = 0; i < CPUSTATES; i++)
164		SWAP(cp_time[i]);
165
166	etime = 0;
167	for (state = 0; state < CPUSTATES; ++state) {
168		etime += cur.cp_time[state];
169	}
170	if (etime == 0)
171		etime = 1;
172	etime /= hz;
173	etime /= cur.cp_ncpu;
174
175	cur.cp_etime = etime;
176}
177#undef SWAP
178
179/*
180 * Read the drive statistics for each drive in the drive list.
181 * Also collect statistics for tty i/o and CPU ticks.
182 */
183void
184drvreadstats(void)
185{
186	struct io_stats	cur_drive, *p;
187	size_t		size, i;
188	int		mib[3];
189
190	p = iostathead;
191
192	if (memf == NULL) {
193		mib[0] = CTL_HW;
194		mib[1] = HW_IOSTATS;
195		mib[2] = sizeof(struct io_sysctl);
196
197		size = ndrive * sizeof(struct io_sysctl);
198		if (sysctl(mib, 3, drives, &size, NULL, 0) < 0)
199			err(1, "sysctl hw.iostats failed");
200		for (i = 0; i < ndrive; i++) {
201			cur.rxfer[i] = drives[i].rxfer;
202			cur.wxfer[i] = drives[i].wxfer;
203			cur.seek[i] = drives[i].seek;
204			cur.rbytes[i] = drives[i].rbytes;
205			cur.wbytes[i] = drives[i].wbytes;
206			cur.time[i].tv_sec = drives[i].time_sec;
207			cur.time[i].tv_usec = drives[i].time_usec;
208		}
209
210		mib[0] = CTL_KERN;
211		mib[1] = KERN_TKSTAT;
212		mib[2] = KERN_TKSTAT_NIN;
213		size = sizeof(cur.tk_nin);
214		if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0)
215			cur.tk_nin = 0;
216
217		mib[2] = KERN_TKSTAT_NOUT;
218		size = sizeof(cur.tk_nout);
219		if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0)
220			cur.tk_nout = 0;
221	} else {
222		for (i = 0; i < ndrive; i++) {
223			deref_kptr(p, &cur_drive, sizeof(cur_drive));
224			cur.rxfer[i] = cur_drive.io_rxfer;
225			cur.wxfer[i] = cur_drive.io_wxfer;
226			cur.seek[i] = cur_drive.io_seek;
227			cur.rbytes[i] = cur_drive.io_rbytes;
228			cur.wbytes[i] = cur_drive.io_wbytes;
229			timerset(&(cur_drive.io_time), &(cur.time[i]));
230			p = cur_drive.io_link.tqe_next;
231		}
232
233		deref_nl(X_TK_NIN, &cur.tk_nin, sizeof(cur.tk_nin));
234		deref_nl(X_TK_NOUT, &cur.tk_nout, sizeof(cur.tk_nout));
235	}
236
237	/*
238	 * XXX Need to locate the `correct' CPU when looking for this
239	 * XXX in crash dumps.  Just don't report it for now, in that
240	 * XXX case.
241	 */
242	size = sizeof(cur.cp_time);
243	(void)memset(cur.cp_time, 0, size);
244	if (memf == NULL) {
245		mib[0] = CTL_KERN;
246		mib[1] = KERN_CP_TIME;
247		if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0)
248			(void)memset(cur.cp_time, 0, sizeof(cur.cp_time));
249	}
250}
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	if (memf == NULL) {
263		mib[0] = CTL_KERN;
264		mib[1] = KERN_TKSTAT;
265		mib[2] = KERN_TKSTAT_NIN;
266		size = sizeof(cur.tk_nin);
267		if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0)
268			cur.tk_nin = 0;
269
270		mib[2] = KERN_TKSTAT_NOUT;
271		size = sizeof(cur.tk_nout);
272		if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0)
273			cur.tk_nout = 0;
274	} else {
275		deref_nl(X_TK_NIN, &cur.tk_nin, sizeof(cur.tk_nin));
276		deref_nl(X_TK_NOUT, &cur.tk_nout, sizeof(cur.tk_nout));
277	}
278}
279
280/*
281 * Read collect statistics for CPU ticks.
282 */
283
284void
285cpureadstats(void)
286{
287	size_t		size;
288	int		mib[2];
289
290	/*
291	 * XXX Need to locate the `correct' CPU when looking for this
292	 * XXX in crash dumps.  Just don't report it for now, in that
293	 * XXX case.
294	 */
295	size = sizeof(cur.cp_time);
296	(void)memset(cur.cp_time, 0, size);
297	if (memf == NULL) {
298		mib[0] = CTL_KERN;
299		mib[1] = KERN_CP_TIME;
300		if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0)
301			(void)memset(cur.cp_time, 0, sizeof(cur.cp_time));
302	}
303}
304
305/*
306 * Perform all of the initialization and memory allocation needed to
307 * track drive statistics.
308 */
309int
310drvinit(int selected)
311{
312	struct iostatlist_head iostat_head;
313	struct io_stats	cur_drive, *p;
314	struct clockinfo clockinfo;
315	char		errbuf[_POSIX2_LINE_MAX];
316	size_t		size, i;
317	static int	once = 0;
318	int		mib[3];
319
320	if (once)
321		return (1);
322
323	if (memf == NULL) {
324		mib[0] = CTL_HW;
325		mib[1] = HW_NCPU;
326		size = sizeof(cur.cp_ncpu);
327		if (sysctl(mib, 2, &cur.cp_ncpu, &size, NULL, 0) == -1)
328			err(1, "sysctl hw.ncpu failed");
329
330		mib[0] = CTL_KERN;
331		mib[1] = KERN_CLOCKRATE;
332		size = sizeof(clockinfo);
333		if (sysctl(mib, 2, &clockinfo, &size, NULL, 0) == -1)
334			err(1, "sysctl kern.clockrate failed");
335		hz = clockinfo.stathz;
336		if (!hz)
337			hz = clockinfo.hz;
338
339		mib[0] = CTL_HW;
340		mib[1] = HW_IOSTATS;
341		mib[2] = sizeof(struct io_sysctl);
342		if (sysctl(mib, 3, NULL, &size, NULL, 0) == -1)
343			err(1, "sysctl hw.drivestats failed");
344		ndrive = size / sizeof(struct io_sysctl);
345
346		if (size == 0) {
347			warnx("No drives attached.");
348		} else {
349			drives = (struct io_sysctl *)malloc(size);
350			if (drives == NULL)
351				errx(1, "Memory allocation failure.");
352		}
353	} else {
354		int drive_count;
355		/* Open the kernel. */
356		if ((kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY,
357		    errbuf)) == NULL)
358			errx(1, "kvm_openfiles: %s", errbuf);
359
360		/* Obtain the namelist symbols from the kernel. */
361		if (kvm_nlist(kd, namelist))
362			KVM_ERROR("kvm_nlist failed to read symbols.");
363
364		/* Get the number of attached drives. */
365		deref_nl(X_DRIVE_COUNT, &drive_count, sizeof(drive_count));
366
367		if (drive_count < 0)
368			errx(1, "invalid _drive_count %d.", drive_count);
369		else if (drive_count == 0) {
370			warnx("No drives attached.");
371		} else {
372			/* Get a pointer to the first drive. */
373			deref_nl(X_DRIVELIST, &iostat_head,
374				 sizeof(iostat_head));
375			iostathead = iostat_head.tqh_first;
376		}
377		ndrive = drive_count;
378
379		/* Get ticks per second. */
380		deref_nl(X_STATHZ, &hz, sizeof(hz));
381		if (!hz)
382			deref_nl(X_HZ, &hz, sizeof(hz));
383	}
384
385	/* Allocate space for the statistics. */
386	cur.time = calloc(ndrive, sizeof(struct timeval));
387	cur.rxfer = calloc(ndrive, sizeof(u_int64_t));
388	cur.wxfer = calloc(ndrive, sizeof(u_int64_t));
389	cur.seek = calloc(ndrive, sizeof(u_int64_t));
390	cur.rbytes = calloc(ndrive, sizeof(u_int64_t));
391	cur.wbytes = calloc(ndrive, sizeof(u_int64_t));
392	last.time = calloc(ndrive, sizeof(struct timeval));
393	last.rxfer = calloc(ndrive, sizeof(u_int64_t));
394	last.wxfer = calloc(ndrive, sizeof(u_int64_t));
395	last.seek = calloc(ndrive, sizeof(u_int64_t));
396	last.rbytes = calloc(ndrive, sizeof(u_int64_t));
397	last.wbytes = calloc(ndrive, sizeof(u_int64_t));
398	cur.select = calloc(ndrive, sizeof(int));
399	cur.name = calloc(ndrive, sizeof(char *));
400
401	if (cur.time == NULL || cur.rxfer == NULL ||
402	    cur.wxfer == NULL || cur.seek == NULL ||
403	    cur.rbytes == NULL || cur.wbytes == NULL ||
404	    last.time == NULL || last.rxfer == NULL ||
405	    last.wxfer == NULL || last.seek == NULL ||
406	    last.rbytes == NULL || last.wbytes == NULL ||
407	    cur.select == NULL || cur.name == NULL)
408		errx(1, "Memory allocation failure.");
409
410	/* Set up the compatibility interfaces. */
411	drv_select = cur.select;
412	dr_name = cur.name;
413
414	/* Read the drive names and set intial selection. */
415	if (memf == NULL) {
416		mib[0] = CTL_HW;		/* Should be still set from */
417		mib[1] = HW_IOSTATS;		/* ... above, but be safe... */
418		mib[2] = sizeof(struct io_sysctl);
419		if (sysctl(mib, 3, drives, &size, NULL, 0) == -1)
420			err(1, "sysctl hw.iostats failed");
421		for (i = 0; i < ndrive; i++) {
422			cur.name[i] = drives[i].name;
423			cur.select[i] = selected;
424		}
425	} else {
426		p = iostathead;
427		for (i = 0; i < ndrive; i++) {
428			char	buf[10];
429			deref_kptr(p, &cur_drive, sizeof(cur_drive));
430			deref_kptr(cur_drive.io_name, buf, sizeof(buf));
431			cur.name[i] = strdup(buf);
432			if (!cur.name[i])
433				err(1, "strdup");
434			cur.select[i] = selected;
435
436			p = cur_drive.io_link.tqe_next;
437		}
438	}
439
440	/* Never do this initialization again. */
441	once = 1;
442	return (1);
443}
444
445/*
446 * Dereference the kernel pointer `kptr' and fill in the local copy
447 * pointed to by `ptr'.  The storage space must be pre-allocated,
448 * and the size of the copy passed in `len'.
449 */
450static void
451deref_kptr(void *kptr, void *ptr, size_t len)
452{
453	char buf[128];
454
455	if ((size_t)kvm_read(kd, (u_long)kptr, (char *)ptr, len) != len) {
456		(void)memset(buf, 0, sizeof(buf));
457		(void)snprintf(buf, sizeof buf, "can't dereference kptr 0x%lx",
458		    (u_long)kptr);
459		KVM_ERROR(buf);
460	}
461}
462