xref: /netbsd/usr.bin/vmstat/drvstats.c (revision 6550d01e)
1 /*	$NetBSD: drvstats.c,v 1.5 2009/01/18 07:20:00 lukem 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 
52 static 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. */
69 struct _drive	cur, last;
70 
71 /* Kernel pointers: nlistf and memf defined in calling program. */
72 static kvm_t	*kd = NULL;
73 extern char	*nlistf;
74 extern char	*memf;
75 extern int	hz;
76 
77 /* Pointer to list of drives. */
78 static struct io_stats	*iostathead = NULL;
79 /* sysctl hw.drivestats buffer. */
80 static struct io_sysctl	*drives = NULL;
81 
82 /* Backward compatibility references. */
83 size_t		ndrive = 0;
84 int		*drv_select;
85 char		**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 
106 static 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  */
113 void
114 drvswap(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 
147 void
148 tkswap(void)
149 {
150 	u_int64_t tmp;
151 
152 	SWAP(tk_nin);
153 	SWAP(tk_nout);
154 }
155 
156 void
157 cpuswap(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  */
183 void
184 drvreadstats(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 
256 void
257 tkreadstats(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 
284 void
285 cpureadstats(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  */
309 int
310 drvinit(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  */
450 static void
451 deref_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