xref: /openbsd-src/usr.bin/vmstat/dkstats.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: dkstats.c,v 1.14 2001/07/21 09:21:56 deraadt Exp $	*/
2 /*	$NetBSD: dkstats.c,v 1.1 1996/05/10 23:19:27 thorpej Exp $	*/
3 
4 /*
5  * Copyright (c) 1996 John M. Vinopal
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *      This product includes software developed for the NetBSD Project
19  *      by John M. Vinopal.
20  * 4. The name of the author may not be used to endorse or promote products
21  *    derived from this software without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
24  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include <sys/param.h>
37 #include <sys/dkstat.h>
38 #include <sys/time.h>
39 #include <sys/disk.h>
40 #include <sys/sysctl.h>
41 #include <sys/tty.h>
42 
43 #include <err.h>
44 #include <fcntl.h>
45 #include <kvm.h>
46 #include <limits.h>
47 #include <nlist.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <unistd.h>
52 #include "dkstats.h"
53 
54 static struct nlist namelist[] = {
55 #define	X_TK_NIN	0
56 	{ "_tk_nin" },		/* tty characters in */
57 #define	X_TK_NOUT	1
58 	{ "_tk_nout" },		/* tty characters out */
59 #define	X_CP_TIME	2
60 	{ "_cp_time" },		/* system timer ticks */
61 #define	X_HZ		3
62 	{ "_hz" },		/* ticks per second */
63 #define	X_STATHZ	4
64 	{ "_stathz" },
65 #define X_DISK_COUNT	5
66 	{ "_disk_count" },	/* number of disks */
67 #define X_DISKLIST	6
68 	{ "_disklist" },	/* TAILQ of disks */
69 	{ NULL },
70 };
71 
72 /* Structures to hold the statistics. */
73 struct _disk	cur, last;
74 
75 /* Kernel pointers: nlistf and memf defined in calling program. */
76 static kvm_t	*kd = NULL;
77 extern char	*nlistf;
78 extern char	*memf;
79 
80 /* Pointer to list of disks. */
81 static struct disk	*dk_drivehead = NULL;
82 
83 /* Backward compatibility references. */
84 int	  	dk_ndrive = 0;
85 int		*dk_select;
86 char		**dr_name;
87 
88 #define	KVM_ERROR(_string) {						\
89 	warnx("%s", (_string));						\
90 	errx(1, "%s", kvm_geterr(kd));					\
91 }
92 
93 /*
94  * Dereference the namelist pointer `v' and fill in the local copy
95  * 'p' which is of size 's'.
96  */
97 #define deref_nl(v, p, s) deref_kptr((void *)namelist[(v)].n_value, (p), (s));
98 
99 /* Missing from <sys/time.h> */
100 #define timerset(tvp, uvp) ((uvp)->tv_sec = (tvp)->tv_sec);		\
101 			   ((uvp)->tv_usec = (tvp)->tv_usec)
102 
103 static void deref_kptr __P((void *, void *, size_t));
104 
105 /*
106  * Take the delta between the present values and the last recorded
107  * values, storing the present values in the 'last' structure, and
108  * the delta values in the 'cur' structure.
109  */
110 void
111 dkswap()
112 {
113 #define SWAP(fld)		tmp = cur.fld;				\
114 				cur.fld -= last.fld;			\
115 				last.fld = tmp
116 	u_int64_t tmp;
117 	int	i;
118 
119 	for (i = 0; i < dk_ndrive; i++) {
120 		struct timeval	tmp_timer;
121 
122 		if (!cur.dk_select[i])
123 			continue;
124 
125 		/* Delta Values. */
126 		SWAP(dk_xfer[i]);
127 		SWAP(dk_seek[i]);
128 		SWAP(dk_bytes[i]);
129 
130 		/* Delta Time. */
131 		timerclear(&tmp_timer);
132 		timerset(&(cur.dk_time[i]), &tmp_timer);
133 		timersub(&tmp_timer, &(last.dk_time[i]), &(cur.dk_time[i]));
134 		timerclear(&(last.dk_time[i]));
135 		timerset(&tmp_timer, &(last.dk_time[i]));
136 	}
137 	for (i = 0; i < CPUSTATES; i++) {
138 		SWAP(cp_time[i]);
139 	}
140 	SWAP(tk_nin);
141 	SWAP(tk_nout);
142 
143 #undef SWAP
144 }
145 
146 /*
147  * Read the disk statistics for each disk in the disk list.
148  * Also collect statistics for tty i/o and cpu ticks.
149  */
150 void
151 dkreadstats()
152 {
153 	struct disk	cur_disk, *p;
154 	int		i, mib[3];
155 	size_t		size;
156 	struct diskstats *q;
157 
158 	if (nlistf == NULL && memf == NULL) {
159 		size = dk_ndrive * sizeof(struct diskstats);
160 		mib[0] = CTL_HW;
161 		mib[1] = HW_DISKSTATS;
162 		q = malloc(size);
163 		if (q == NULL)
164 			err(1, NULL);
165 		if (sysctl(mib, 2, q, &size, NULL, 0) < 0) {
166 			warn("could not read hw.diskstats");
167 			bzero(q, dk_ndrive * sizeof(struct diskstats));
168 		}
169 
170 		for (i = 0; i < dk_ndrive; i++)	{
171 			cur.dk_xfer[i] = q[i].ds_xfer;
172 			cur.dk_seek[i] = q[i].ds_seek;
173 			cur.dk_bytes[i] = q[i].ds_bytes;
174 			timerset(&(q[i].ds_time), &(cur.dk_time[i]));
175 		}
176 		free(q);
177 
178 	 	size = sizeof(cur.cp_time);
179 		mib[0] = CTL_KERN;
180 		mib[1] = KERN_CPTIME;
181 		if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0) {
182 			warn("could not read kern.cp_time");
183 			bzero(cur.cp_time, sizeof(cur.cp_time));
184 		}
185 		size = sizeof(cur.tk_nin);
186 		mib[0] = CTL_KERN;
187 		mib[1] = KERN_TTY;
188 		mib[2] = KERN_TTY_TKNIN;
189 		if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0) {
190 			warn("could not read kern.tty.tk_nin");
191 			cur.tk_nin = 0;
192 		}
193 		size = sizeof(cur.tk_nin);
194 		mib[0] = CTL_KERN;
195 		mib[1] = KERN_TTY;
196 		mib[2] = KERN_TTY_TKNOUT;
197 		if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0) {
198 			warn("could not read kern.tty.tk_nout");
199 			cur.tk_nout = 0;
200 		}
201 	} else {
202 		p = dk_drivehead;
203 
204 		for (i = 0; i < dk_ndrive; i++) {
205 			deref_kptr(p, &cur_disk, sizeof(cur_disk));
206 			cur.dk_xfer[i] = cur_disk.dk_xfer;
207 			cur.dk_seek[i] = cur_disk.dk_seek;
208 			cur.dk_bytes[i] = cur_disk.dk_bytes;
209 			timerset(&(cur_disk.dk_time), &(cur.dk_time[i]));
210 			p = cur_disk.dk_link.tqe_next;
211 		}
212 
213 		deref_nl(X_CP_TIME, cur.cp_time, sizeof(cur.cp_time));
214 		deref_nl(X_TK_NIN, &cur.tk_nin, sizeof(cur.tk_nin));
215 		deref_nl(X_TK_NOUT, &cur.tk_nout, sizeof(cur.tk_nout));
216 	}
217 }
218 
219 /*
220  * Perform all of the initialization and memory allocation needed to
221  * track disk statistics.
222  */
223 int
224 dkinit(select)
225 int	select;
226 {
227 	struct disklist_head disk_head;
228 	struct disk	cur_disk, *p;
229         char		errbuf[_POSIX2_LINE_MAX];
230 	static int	once = 0;
231 	extern int	hz;
232 	int		i, mib[2];
233 	size_t		size;
234 	struct clockinfo clkinfo;
235 	char		*disknames, *name, *bufpp;
236 
237 	if (once)
238 		return(1);
239 
240 	if (nlistf != NULL || memf != NULL) {
241 		/* Open the kernel. */
242 		if ((kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY,
243 		    errbuf)) == NULL)
244 			errx(1, "kvm_openfiles: %s", errbuf);
245 
246 		/* Obtain the namelist symbols from the kernel. */
247 		if (kvm_nlist(kd, namelist))
248 			KVM_ERROR("kvm_nlist failed to read symbols.");
249 
250 		/* Get the number of attached drives. */
251 		deref_nl(X_DISK_COUNT, &dk_ndrive, sizeof(dk_ndrive));
252 
253 		if (dk_ndrive < 0)
254 			errx(1, "invalid _disk_count %d.", dk_ndrive);
255 
256 		/* Get a pointer to the first disk. */
257 		deref_nl(X_DISKLIST, &disk_head, sizeof(disk_head));
258 		dk_drivehead = disk_head.tqh_first;
259 
260 		/* Get ticks per second. */
261 		deref_nl(X_STATHZ, &hz, sizeof(hz));
262 		if (!hz)
263 		  deref_nl(X_HZ, &hz, sizeof(hz));
264 	} else {
265 		/* Get the number of attached drives. */
266 		mib[0] = CTL_HW;
267 		mib[1] = HW_DISKCOUNT;
268 		size = sizeof(dk_ndrive);
269 		if (sysctl(mib, 2, &dk_ndrive, &size, NULL, 0) < 0 ) {
270 			warn("could not read hw.diskcount");
271 			dk_ndrive = 0;
272 		}
273 
274 		/* Get ticks per second. */
275 		mib[0] = CTL_KERN;
276 		mib[1] = KERN_CLOCKRATE;
277 		size = sizeof(clkinfo);
278 		if (sysctl(mib, 2, &clkinfo, &size, NULL, 0) < 0) {
279 			warn("could not read kern.clockrate");
280 			hz = 0;
281 		} else
282 			hz = clkinfo.stathz;
283 	}
284 
285 	/* allocate space for the statistics */
286 	cur.dk_time = calloc(dk_ndrive, sizeof(struct timeval));
287 	cur.dk_xfer = calloc(dk_ndrive, sizeof(u_int64_t));
288 	cur.dk_seek = calloc(dk_ndrive, sizeof(u_int64_t));
289 	cur.dk_bytes = calloc(dk_ndrive, sizeof(u_int64_t));
290 	last.dk_time = calloc(dk_ndrive, sizeof(struct timeval));
291 	last.dk_xfer = calloc(dk_ndrive, sizeof(u_int64_t));
292 	last.dk_seek = calloc(dk_ndrive, sizeof(u_int64_t));
293 	last.dk_bytes = calloc(dk_ndrive, sizeof(u_int64_t));
294 	cur.dk_select = calloc(dk_ndrive, sizeof(int));
295 	cur.dk_name = calloc(dk_ndrive, sizeof(char *));
296 
297 	if (!cur.dk_time || !cur.dk_xfer || !cur.dk_seek || !cur.dk_bytes ||
298 	    !last.dk_time || !last.dk_xfer || !last.dk_seek ||
299 	    !last.dk_bytes || !cur.dk_select || !cur.dk_name)
300 		errx(1, "Memory allocation failure.");
301 
302 	/* Set up the compatibility interfaces. */
303 	dk_select = cur.dk_select;
304 	dr_name = cur.dk_name;
305 
306 	/* Read the disk names and set intial selection. */
307 	if (nlistf == NULL && memf == NULL) {
308 		mib[0] = CTL_HW;
309 		mib[1] = HW_DISKNAMES;
310 		size = 0;
311 		if (sysctl(mib, 2, NULL, &size, NULL, 0) < 0)
312 			err(1, "can't get hw.disknames");
313 		disknames = malloc(size);
314 		if (disknames == NULL)
315 			err(1, NULL);
316 		if (sysctl(mib, 2, disknames, &size, NULL, 0) < 0)
317 			err(1, "can't get hw.disknames");
318 		bufpp = disknames;
319 		i = 0;
320 		while ((name = strsep(&bufpp, ",")) != NULL) {
321 		    cur.dk_name[i] = name;
322 		    cur.dk_select[i++] = select;
323 		}
324 	} else {
325 		p = dk_drivehead;
326 		for (i = 0; i < dk_ndrive; i++) {
327 			char	buf[10];
328 			deref_kptr(p, &cur_disk, sizeof(cur_disk));
329 			deref_kptr(cur_disk.dk_name, buf, sizeof(buf));
330 			cur.dk_name[i] = strdup(buf);
331 			if (!cur.dk_name[i])
332 				errx(1, "Memory allocation failure.");
333 			cur.dk_select[i] = select;
334 
335 			p = cur_disk.dk_link.tqe_next;
336 		}
337 	}
338 
339 	/* Never do this initalization again. */
340 	once = 1;
341 	return(1);
342 }
343 
344 /*
345  * Dereference the kernel pointer `kptr' and fill in the local copy
346  * pointed to by `ptr'.  The storage space must be pre-allocated,
347  * and the size of the copy passed in `len'.
348  */
349 static void
350 deref_kptr(kptr, ptr, len)
351 	void *kptr, *ptr;
352 	size_t len;
353 {
354 	char buf[128];
355 
356 	if (kvm_read(kd, (u_long)kptr, ptr, len) != len) {
357 		bzero(buf, sizeof(buf));
358 		snprintf(buf, (sizeof(buf) - 1),
359 		     "can't dereference kptr 0x%lx", (u_long)kptr);
360 		KVM_ERROR(buf);
361 	}
362 }
363