1 /* $OpenBSD: dkstats.c,v 1.26 2005/07/04 01:54:10 djm 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 #if !defined(NOKVM)
55 static struct nlist namelist[] = {
56 #define X_TK_NIN 0 /* sysctl */
57 { "_tk_nin" },
58 #define X_TK_NOUT 1 /* sysctl */
59 { "_tk_nout" },
60 #define X_CP_TIME 2 /* sysctl */
61 { "_cp_time" },
62 #define X_HZ 3 /* sysctl */
63 { "_hz" },
64 #define X_STATHZ 4 /* sysctl */
65 { "_stathz" },
66 #define X_DISK_COUNT 5 /* sysctl */
67 { "_disk_count" },
68 #define X_DISKLIST 6 /* sysctl */
69 { "_disklist" },
70 { NULL },
71 };
72 #define KVM_ERROR(_string) { \
73 warnx("%s", (_string)); \
74 errx(1, "%s", kvm_geterr(kd)); \
75 }
76
77 /*
78 * Dereference the namelist pointer `v' and fill in the local copy
79 * 'p' which is of size 's'.
80 */
81 #define deref_nl(v, p, s) deref_kptr((void *)namelist[(v)].n_value, (p), (s));
82 static void deref_kptr(void *, void *, size_t);
83 #endif /* !defined(NOKVM) */
84
85 /* Structures to hold the statistics. */
86 struct _disk cur, last;
87
88 /* Kernel pointers: nlistf and memf defined in calling program. */
89 #if !defined(NOKVM)
90 extern kvm_t *kd;
91 #endif
92 extern char *nlistf;
93 extern char *memf;
94
95 #if !defined(NOKVM)
96 /* Pointer to list of disks. */
97 static struct disk *dk_drivehead = NULL;
98 #endif
99
100 /* Backward compatibility references. */
101 int dk_ndrive = 0;
102 int *dk_select;
103 char **dr_name;
104
105 /* Missing from <sys/time.h> */
106 #define timerset(tvp, uvp) \
107 ((uvp)->tv_sec = (tvp)->tv_sec); \
108 ((uvp)->tv_usec = (tvp)->tv_usec)
109
110 #define SWAP(fld) tmp = cur.fld; \
111 cur.fld -= last.fld; \
112 last.fld = tmp
113
114 /*
115 * Take the delta between the present values and the last recorded
116 * values, storing the present values in the 'last' structure, and
117 * the delta values in the 'cur' structure.
118 */
119 void
dkswap(void)120 dkswap(void)
121 {
122 u_int64_t tmp;
123 int i;
124
125 for (i = 0; i < cur.dk_ndrive; i++) {
126 struct timeval tmp_timer;
127
128 if (!cur.dk_select[i])
129 continue;
130
131 /* Delta Values. */
132 SWAP(dk_rxfer[i]);
133 SWAP(dk_wxfer[i]);
134 SWAP(dk_seek[i]);
135 SWAP(dk_rbytes[i]);
136 SWAP(dk_wbytes[i]);
137
138 /* Delta Time. */
139 timerclear(&tmp_timer);
140 timerset(&(cur.dk_time[i]), &tmp_timer);
141 timersub(&tmp_timer, &(last.dk_time[i]), &(cur.dk_time[i]));
142 timerclear(&(last.dk_time[i]));
143 timerset(&tmp_timer, &(last.dk_time[i]));
144 }
145 for (i = 0; i < CPUSTATES; i++) {
146 SWAP(cp_time[i]);
147 }
148 SWAP(tk_nin);
149 SWAP(tk_nout);
150
151 #undef SWAP
152 }
153
154 /*
155 * Read the disk statistics for each disk in the disk list.
156 * Also collect statistics for tty i/o and cpu ticks.
157 */
158 void
dkreadstats(void)159 dkreadstats(void)
160 {
161 #if !defined(NOKVM)
162 struct disk cur_disk, *p;
163 #endif
164 int i, j, mib[3];
165 size_t size;
166 char *disknames, *name, *bufpp, **dk_name;
167 struct diskstats *q;
168
169 last.dk_ndrive = cur.dk_ndrive;
170
171 if (nlistf == NULL && memf == NULL) {
172 /* Get the number of attached drives. */
173 mib[0] = CTL_HW;
174 mib[1] = HW_DISKCOUNT;
175 size = sizeof(dk_ndrive);
176 if (sysctl(mib, 2, &dk_ndrive, &size, NULL, 0) < 0 ) {
177 warn("could not read hw.diskcount");
178 dk_ndrive = 0;
179 }
180
181 if (cur.dk_ndrive != dk_ndrive) {
182 /* Re-read the disk names. */
183 dk_name = calloc(dk_ndrive, sizeof(char *));
184 if (dk_name == NULL)
185 err(1, NULL);
186 mib[0] = CTL_HW;
187 mib[1] = HW_DISKNAMES;
188 size = 0;
189 if (sysctl(mib, 2, NULL, &size, NULL, 0) < 0)
190 err(1, "can't get hw.disknames");
191 disknames = malloc(size);
192 if (disknames == NULL)
193 err(1, NULL);
194 if (sysctl(mib, 2, disknames, &size, NULL, 0) < 0)
195 err(1, "can't get hw.disknames");
196 bufpp = disknames;
197 for (i = 0; i < dk_ndrive && (name = strsep(&bufpp, ",")) != NULL; i++)
198 dk_name[i] = name;
199 disknames = cur.dk_name[0]; /* To free old names. */
200
201 if (dk_ndrive < cur.dk_ndrive) {
202 for (i = 0, j = 0; i < dk_ndrive; i++, j++) {
203 while (j < cur.dk_ndrive &&
204 strcmp(cur.dk_name[j], dk_name[i]))
205 j++;
206 if (i == j) continue;
207
208 if (j >= cur.dk_ndrive) {
209 cur.dk_select[i] = 1;
210 last.dk_rxfer[i] = 0;
211 last.dk_wxfer[i] = 0;
212 last.dk_seek[i] = 0;
213 last.dk_rbytes[i] = 0;
214 last.dk_wbytes[i] = 0;
215 bzero(&last.dk_time[i],
216 sizeof(struct timeval));
217 continue;
218 }
219
220 cur.dk_select[i] = cur.dk_select[j];
221 last.dk_rxfer[i] = last.dk_rxfer[j];
222 last.dk_wxfer[i] = last.dk_wxfer[j];
223 last.dk_seek[i] = last.dk_seek[j];
224 last.dk_rbytes[i] = last.dk_rbytes[j];
225 last.dk_wbytes[i] = last.dk_wbytes[j];
226 last.dk_time[i] = last.dk_time[j];
227 }
228
229 cur.dk_select = realloc(cur.dk_select,
230 dk_ndrive * sizeof(*cur.dk_select));
231 cur.dk_rxfer = realloc(cur.dk_rxfer,
232 dk_ndrive * sizeof(*cur.dk_rxfer));
233 cur.dk_wxfer = realloc(cur.dk_wxfer,
234 dk_ndrive * sizeof(*cur.dk_wxfer));
235 cur.dk_seek = realloc(cur.dk_seek,
236 dk_ndrive * sizeof(*cur.dk_seek));
237 cur.dk_rbytes = realloc(cur.dk_rbytes,
238 dk_ndrive * sizeof(*cur.dk_rbytes));
239 cur.dk_wbytes = realloc(cur.dk_wbytes,
240 dk_ndrive * sizeof(*cur.dk_wbytes));
241 cur.dk_time = realloc(cur.dk_time,
242 dk_ndrive * sizeof(*cur.dk_time));
243 last.dk_rxfer = realloc(last.dk_rxfer,
244 dk_ndrive * sizeof(*last.dk_rxfer));
245 last.dk_wxfer = realloc(last.dk_wxfer,
246 dk_ndrive * sizeof(*last.dk_wxfer));
247 last.dk_seek = realloc(last.dk_seek,
248 dk_ndrive * sizeof(*last.dk_seek));
249 last.dk_rbytes = realloc(last.dk_rbytes,
250 dk_ndrive * sizeof(*last.dk_rbytes));
251 last.dk_wbytes = realloc(last.dk_wbytes,
252 dk_ndrive * sizeof(*last.dk_wbytes));
253 last.dk_time = realloc(last.dk_time,
254 dk_ndrive * sizeof(*last.dk_time));
255 } else {
256 cur.dk_select = realloc(cur.dk_select,
257 dk_ndrive * sizeof(*cur.dk_select));
258 cur.dk_rxfer = realloc(cur.dk_rxfer,
259 dk_ndrive * sizeof(*cur.dk_rxfer));
260 cur.dk_wxfer = realloc(cur.dk_wxfer,
261 dk_ndrive * sizeof(*cur.dk_wxfer));
262 cur.dk_seek = realloc(cur.dk_seek,
263 dk_ndrive * sizeof(*cur.dk_seek));
264 cur.dk_rbytes = realloc(cur.dk_rbytes,
265 dk_ndrive * sizeof(*cur.dk_rbytes));
266 cur.dk_wbytes = realloc(cur.dk_wbytes,
267 dk_ndrive * sizeof(*cur.dk_wbytes));
268 cur.dk_time = realloc(cur.dk_time,
269 dk_ndrive * sizeof(*cur.dk_time));
270 last.dk_rxfer = realloc(last.dk_rxfer,
271 dk_ndrive * sizeof(*last.dk_rxfer));
272 last.dk_wxfer = realloc(last.dk_wxfer,
273 dk_ndrive * sizeof(*last.dk_wxfer));
274 last.dk_seek = realloc(last.dk_seek,
275 dk_ndrive * sizeof(*last.dk_seek));
276 last.dk_rbytes = realloc(last.dk_rbytes,
277 dk_ndrive * sizeof(*last.dk_rbytes));
278 last.dk_wbytes = realloc(last.dk_wbytes,
279 dk_ndrive * sizeof(*last.dk_wbytes));
280 last.dk_time = realloc(last.dk_time,
281 dk_ndrive * sizeof(*last.dk_time));
282
283 for (i = dk_ndrive - 1, j = cur.dk_ndrive - 1;
284 i >= 0; i--) {
285
286 if (j < 0 ||
287 strcmp(cur.dk_name[j], dk_name[i]))
288 {
289 cur.dk_select[i] = 1;
290 last.dk_rxfer[i] = 0;
291 last.dk_wxfer[i] = 0;
292 last.dk_seek[i] = 0;
293 last.dk_rbytes[i] = 0;
294 last.dk_wbytes[i] = 0;
295 bzero(&last.dk_time[i],
296 sizeof(struct timeval));
297 continue;
298 }
299
300 if (i > j) {
301 cur.dk_select[i] =
302 cur.dk_select[j];
303 last.dk_rxfer[i] =
304 last.dk_rxfer[j];
305 last.dk_wxfer[i] =
306 last.dk_wxfer[j];
307 last.dk_seek[i] =
308 last.dk_seek[j];
309 last.dk_rbytes[i] =
310 last.dk_rbytes[j];
311 last.dk_wbytes[i] =
312 last.dk_wbytes[j];
313 last.dk_time[i] =
314 last.dk_time[j];
315 }
316 j--;
317 }
318 }
319
320 cur.dk_ndrive = dk_ndrive;
321 free(disknames);
322 cur.dk_name = dk_name;
323 dr_name = cur.dk_name;
324 dk_select = cur.dk_select;
325 }
326
327 size = cur.dk_ndrive * sizeof(struct diskstats);
328 mib[0] = CTL_HW;
329 mib[1] = HW_DISKSTATS;
330 q = malloc(size);
331 if (q == NULL)
332 err(1, NULL);
333 if (sysctl(mib, 2, q, &size, NULL, 0) < 0) {
334 #ifdef DEBUG
335 warn("could not read hw.diskstats");
336 #endif /* DEBUG */
337 bzero(q, cur.dk_ndrive * sizeof(struct diskstats));
338 }
339
340 for (i = 0; i < cur.dk_ndrive; i++) {
341 cur.dk_rxfer[i] = q[i].ds_rxfer;
342 cur.dk_wxfer[i] = q[i].ds_wxfer;
343 cur.dk_seek[i] = q[i].ds_seek;
344 cur.dk_rbytes[i] = q[i].ds_rbytes;
345 cur.dk_wbytes[i] = q[i].ds_wbytes;
346 timerset(&(q[i].ds_time), &(cur.dk_time[i]));
347 }
348 free(q);
349
350 size = sizeof(cur.cp_time);
351 mib[0] = CTL_KERN;
352 mib[1] = KERN_CPTIME;
353 if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0) {
354 warn("could not read kern.cp_time");
355 bzero(cur.cp_time, sizeof(cur.cp_time));
356 }
357 size = sizeof(cur.tk_nin);
358 mib[0] = CTL_KERN;
359 mib[1] = KERN_TTY;
360 mib[2] = KERN_TTY_TKNIN;
361 if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0) {
362 warn("could not read kern.tty.tk_nin");
363 cur.tk_nin = 0;
364 }
365 size = sizeof(cur.tk_nin);
366 mib[0] = CTL_KERN;
367 mib[1] = KERN_TTY;
368 mib[2] = KERN_TTY_TKNOUT;
369 if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0) {
370 warn("could not read kern.tty.tk_nout");
371 cur.tk_nout = 0;
372 }
373 } else {
374 #if !defined(NOKVM)
375 p = dk_drivehead;
376
377 for (i = 0; i < cur.dk_ndrive; i++) {
378 deref_kptr(p, &cur_disk, sizeof(cur_disk));
379 cur.dk_rxfer[i] = cur_disk.dk_rxfer;
380 cur.dk_wxfer[i] = cur_disk.dk_wxfer;
381 cur.dk_seek[i] = cur_disk.dk_seek;
382 cur.dk_rbytes[i] = cur_disk.dk_rbytes;
383 cur.dk_wbytes[i] = cur_disk.dk_wbytes;
384 timerset(&(cur_disk.dk_time), &(cur.dk_time[i]));
385 p = cur_disk.dk_link.tqe_next;
386 }
387 deref_nl(X_CP_TIME, cur.cp_time, sizeof(cur.cp_time));
388 deref_nl(X_TK_NIN, &cur.tk_nin, sizeof(cur.tk_nin));
389 deref_nl(X_TK_NOUT, &cur.tk_nout, sizeof(cur.tk_nout));
390 #endif /* !defined(NOKVM) */
391 }
392 }
393
394 /*
395 * Perform all of the initialization and memory allocation needed to
396 * track disk statistics.
397 */
398 int
dkinit(int select)399 dkinit(int select)
400 {
401 #if !defined(NOKVM)
402 struct disklist_head disk_head;
403 struct disk cur_disk, *p;
404 char errbuf[_POSIX2_LINE_MAX];
405 #endif
406 static int once = 0;
407 extern int hz;
408 int i, mib[2];
409 size_t size;
410 struct clockinfo clkinfo;
411 char *disknames, *name, *bufpp;
412 gid_t gid;
413
414 if (once)
415 return(1);
416
417 gid = getgid();
418 if (nlistf != NULL || memf != NULL) {
419 #if !defined(NOKVM)
420 if (memf != NULL)
421 if (setresgid(gid, gid, gid) == -1)
422 err(1, "setresgid");
423
424 /* Open the kernel. */
425 if (kd == NULL &&
426 (kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY,
427 errbuf)) == NULL)
428 errx(1, "kvm_openfiles: %s", errbuf);
429
430 if (memf == NULL)
431 if (setresgid(gid, gid, gid) == -1)
432 err(1, "setresgid");
433
434 /* Obtain the namelist symbols from the kernel. */
435 if (kvm_nlist(kd, namelist))
436 KVM_ERROR("kvm_nlist failed to read symbols.");
437
438 /* Get the number of attached drives. */
439 deref_nl(X_DISK_COUNT, &cur.dk_ndrive, sizeof(cur.dk_ndrive));
440
441 if (cur.dk_ndrive < 0)
442 errx(1, "invalid _disk_count %d.", cur.dk_ndrive);
443
444 /* Get a pointer to the first disk. */
445 deref_nl(X_DISKLIST, &disk_head, sizeof(disk_head));
446 dk_drivehead = disk_head.tqh_first;
447
448 /* Get ticks per second. */
449 deref_nl(X_STATHZ, &hz, sizeof(hz));
450 if (!hz)
451 deref_nl(X_HZ, &hz, sizeof(hz));
452 #endif /* !defined(NOKVM) */
453 } else {
454 /* Get the number of attached drives. */
455 mib[0] = CTL_HW;
456 mib[1] = HW_DISKCOUNT;
457 size = sizeof(cur.dk_ndrive);
458 if (sysctl(mib, 2, &cur.dk_ndrive, &size, NULL, 0) < 0 ) {
459 warn("could not read hw.diskcount");
460 cur.dk_ndrive = 0;
461 }
462
463 /* Get ticks per second. */
464 mib[0] = CTL_KERN;
465 mib[1] = KERN_CLOCKRATE;
466 size = sizeof(clkinfo);
467 if (sysctl(mib, 2, &clkinfo, &size, NULL, 0) < 0) {
468 warn("could not read kern.clockrate");
469 hz = 0;
470 } else
471 hz = clkinfo.stathz;
472 }
473
474 /* allocate space for the statistics */
475 cur.dk_time = calloc(cur.dk_ndrive, sizeof(struct timeval));
476 cur.dk_rxfer = calloc(cur.dk_ndrive, sizeof(u_int64_t));
477 cur.dk_wxfer = calloc(cur.dk_ndrive, sizeof(u_int64_t));
478 cur.dk_seek = calloc(cur.dk_ndrive, sizeof(u_int64_t));
479 cur.dk_rbytes = calloc(cur.dk_ndrive, sizeof(u_int64_t));
480 cur.dk_wbytes = calloc(cur.dk_ndrive, sizeof(u_int64_t));
481 last.dk_time = calloc(cur.dk_ndrive, sizeof(struct timeval));
482 last.dk_rxfer = calloc(cur.dk_ndrive, sizeof(u_int64_t));
483 last.dk_wxfer = calloc(cur.dk_ndrive, sizeof(u_int64_t));
484 last.dk_seek = calloc(cur.dk_ndrive, sizeof(u_int64_t));
485 last.dk_rbytes = calloc(cur.dk_ndrive, sizeof(u_int64_t));
486 last.dk_wbytes = calloc(cur.dk_ndrive, sizeof(u_int64_t));
487 cur.dk_select = calloc(cur.dk_ndrive, sizeof(int));
488 cur.dk_name = calloc(cur.dk_ndrive, sizeof(char *));
489
490 if (!cur.dk_time || !cur.dk_rxfer || !cur.dk_wxfer || !cur.dk_seek ||
491 !cur.dk_rbytes || !cur.dk_wbytes || !last.dk_time ||
492 !last.dk_rxfer || !last.dk_wxfer || !last.dk_seek ||
493 !cur.dk_select || !cur.dk_name)
494 errx(1, "Memory allocation failure.");
495
496 /* Set up the compatibility interfaces. */
497 dk_ndrive = cur.dk_ndrive;
498 dk_select = cur.dk_select;
499 dr_name = cur.dk_name;
500
501 /* Read the disk names and set initial selection. */
502 if (nlistf == NULL && memf == NULL) {
503 mib[0] = CTL_HW;
504 mib[1] = HW_DISKNAMES;
505 size = 0;
506 if (sysctl(mib, 2, NULL, &size, NULL, 0) < 0)
507 err(1, "can't get hw.disknames");
508 disknames = malloc(size);
509 if (disknames == NULL)
510 err(1, NULL);
511 if (sysctl(mib, 2, disknames, &size, NULL, 0) < 0)
512 err(1, "can't get hw.disknames");
513 bufpp = disknames;
514 for (i = 0; i < dk_ndrive && (name = strsep(&bufpp, ",")) != NULL; i++) {
515 cur.dk_name[i] = name;
516 cur.dk_select[i] = select;
517 }
518 } else {
519 #if !defined(NOKVM)
520 p = dk_drivehead;
521 for (i = 0; i < cur.dk_ndrive; i++) {
522 char buf[10];
523
524 deref_kptr(p, &cur_disk, sizeof(cur_disk));
525 deref_kptr(cur_disk.dk_name, buf, sizeof(buf));
526 cur.dk_name[i] = strdup(buf);
527 if (!cur.dk_name[i])
528 errx(1, "Memory allocation failure.");
529 cur.dk_select[i] = select;
530
531 p = cur_disk.dk_link.tqe_next;
532 }
533 #endif /* !defined(NOKVM) */
534 }
535
536 /* Never do this initalization again. */
537 once = 1;
538 return(1);
539 }
540
541 #if !defined(NOKVM)
542 /*
543 * Dereference the kernel pointer `kptr' and fill in the local copy
544 * pointed to by `ptr'. The storage space must be pre-allocated,
545 * and the size of the copy passed in `len'.
546 */
547 static void
deref_kptr(void * kptr,void * ptr,size_t len)548 deref_kptr(void *kptr, void *ptr, size_t len)
549 {
550 char buf[128];
551
552 if (kvm_read(kd, (u_long)kptr, ptr, len) != len) {
553 bzero(buf, sizeof(buf));
554 snprintf(buf, (sizeof(buf) - 1),
555 "can't dereference kptr 0x%lx", (u_long)kptr);
556 KVM_ERROR(buf);
557 }
558 }
559 #endif /* !defined(NOKVM) */
560