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