1 /* $NetBSD: drvstats.c,v 1.3 2006/06/07 20:58:23 kardel 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 { "_tk_nin" }, /* tty characters in */ 55 #define X_TK_NOUT 1 56 { "_tk_nout" }, /* tty characters out */ 57 #define X_HZ 2 58 { "_hz" }, /* ticks per second */ 59 #define X_STATHZ 3 60 { "_stathz" }, 61 #define X_DRIVE_COUNT 4 62 { "_iostat_count" }, /* number of drives */ 63 #define X_DRIVELIST 5 64 { "_iostatlist" }, /* TAILQ of drives */ 65 { 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 int 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 int 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; 188 int mib[3]; 189 int i; 190 191 p = iostathead; 192 193 if (memf == NULL) { 194 mib[0] = CTL_HW; 195 mib[1] = HW_IOSTATS; 196 mib[2] = sizeof(struct io_sysctl); 197 198 size = ndrive * sizeof(struct io_sysctl); 199 if (sysctl(mib, 3, drives, &size, NULL, 0) < 0) 200 err(1, "sysctl hw.iostats failed"); 201 for (i = 0; i < ndrive; i++) { 202 cur.rxfer[i] = drives[i].rxfer; 203 cur.wxfer[i] = drives[i].wxfer; 204 cur.seek[i] = drives[i].seek; 205 cur.rbytes[i] = drives[i].rbytes; 206 cur.wbytes[i] = drives[i].wbytes; 207 cur.time[i].tv_sec = drives[i].time_sec; 208 cur.time[i].tv_usec = drives[i].time_usec; 209 } 210 211 mib[0] = CTL_KERN; 212 mib[1] = KERN_TKSTAT; 213 mib[2] = KERN_TKSTAT_NIN; 214 size = sizeof(cur.tk_nin); 215 if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0) 216 cur.tk_nin = 0; 217 218 mib[2] = KERN_TKSTAT_NOUT; 219 size = sizeof(cur.tk_nout); 220 if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0) 221 cur.tk_nout = 0; 222 } else { 223 for (i = 0; i < ndrive; i++) { 224 deref_kptr(p, &cur_drive, sizeof(cur_drive)); 225 cur.rxfer[i] = cur_drive.io_rxfer; 226 cur.wxfer[i] = cur_drive.io_wxfer; 227 cur.seek[i] = cur_drive.io_seek; 228 cur.rbytes[i] = cur_drive.io_rbytes; 229 cur.wbytes[i] = cur_drive.io_wbytes; 230 timerset(&(cur_drive.io_time), &(cur.time[i])); 231 p = cur_drive.io_link.tqe_next; 232 } 233 234 deref_nl(X_TK_NIN, &cur.tk_nin, sizeof(cur.tk_nin)); 235 deref_nl(X_TK_NOUT, &cur.tk_nout, sizeof(cur.tk_nout)); 236 } 237 238 /* 239 * XXX Need to locate the `correct' CPU when looking for this 240 * XXX in crash dumps. Just don't report it for now, in that 241 * XXX case. 242 */ 243 size = sizeof(cur.cp_time); 244 memset(cur.cp_time, 0, size); 245 if (memf == NULL) { 246 mib[0] = CTL_KERN; 247 mib[1] = KERN_CP_TIME; 248 if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0) 249 memset(cur.cp_time, 0, sizeof(cur.cp_time)); 250 } 251 } 252 253 /* 254 * Read collect statistics for tty i/o. 255 */ 256 257 void 258 tkreadstats(void) 259 { 260 size_t size; 261 int mib[3]; 262 263 if (memf == NULL) { 264 mib[0] = CTL_KERN; 265 mib[1] = KERN_TKSTAT; 266 mib[2] = KERN_TKSTAT_NIN; 267 size = sizeof(cur.tk_nin); 268 if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0) 269 cur.tk_nin = 0; 270 271 mib[2] = KERN_TKSTAT_NOUT; 272 size = sizeof(cur.tk_nout); 273 if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0) 274 cur.tk_nout = 0; 275 } else { 276 deref_nl(X_TK_NIN, &cur.tk_nin, sizeof(cur.tk_nin)); 277 deref_nl(X_TK_NOUT, &cur.tk_nout, sizeof(cur.tk_nout)); 278 } 279 } 280 281 /* 282 * Read collect statistics for CPU ticks. 283 */ 284 285 void 286 cpureadstats(void) 287 { 288 size_t size; 289 int mib[2]; 290 291 /* 292 * XXX Need to locate the `correct' CPU when looking for this 293 * XXX in crash dumps. Just don't report it for now, in that 294 * XXX case. 295 */ 296 size = sizeof(cur.cp_time); 297 memset(cur.cp_time, 0, size); 298 if (memf == NULL) { 299 mib[0] = CTL_KERN; 300 mib[1] = KERN_CP_TIME; 301 if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0) 302 memset(cur.cp_time, 0, sizeof(cur.cp_time)); 303 } 304 } 305 306 /* 307 * Perform all of the initialization and memory allocation needed to 308 * track drive statistics. 309 */ 310 int 311 drvinit(int selected) 312 { 313 struct iostatlist_head iostat_head; 314 struct io_stats cur_drive, *p; 315 struct clockinfo clockinfo; 316 char errbuf[_POSIX2_LINE_MAX]; 317 size_t size; 318 static int once = 0; 319 int i, mib[3]; 320 321 if (once) 322 return (1); 323 324 if (memf == NULL) { 325 mib[0] = CTL_HW; 326 mib[1] = HW_NCPU; 327 size = sizeof(cur.cp_ncpu); 328 if (sysctl(mib, 2, &cur.cp_ncpu, &size, NULL, 0) == -1) 329 err(1, "sysctl hw.ncpu failed"); 330 331 mib[0] = CTL_KERN; 332 mib[1] = KERN_CLOCKRATE; 333 size = sizeof(clockinfo); 334 if (sysctl(mib, 2, &clockinfo, &size, NULL, 0) == -1) 335 err(1, "sysctl kern.clockrate failed"); 336 hz = clockinfo.stathz; 337 if (!hz) 338 hz = clockinfo.hz; 339 340 mib[0] = CTL_HW; 341 mib[1] = HW_IOSTATS; 342 mib[2] = sizeof(struct io_sysctl); 343 if (sysctl(mib, 3, NULL, &size, NULL, 0) == -1) 344 err(1, "sysctl hw.drivestats failed"); 345 ndrive = size / sizeof(struct io_sysctl); 346 347 if (size == 0) { 348 warnx("No drives attached."); 349 } else { 350 drives = (struct io_sysctl *)malloc(size); 351 if (drives == NULL) 352 errx(1, "Memory allocation failure."); 353 } 354 } else { 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, &ndrive, sizeof(ndrive)); 366 367 if (ndrive < 0) 368 errx(1, "invalid _drive_count %d.", ndrive); 369 else if (ndrive == 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 378 /* Get ticks per second. */ 379 deref_nl(X_STATHZ, &hz, sizeof(hz)); 380 if (!hz) 381 deref_nl(X_HZ, &hz, sizeof(hz)); 382 } 383 384 /* Allocate space for the statistics. */ 385 cur.time = calloc(ndrive, sizeof(struct timeval)); 386 cur.rxfer = calloc(ndrive, sizeof(u_int64_t)); 387 cur.wxfer = calloc(ndrive, sizeof(u_int64_t)); 388 cur.seek = calloc(ndrive, sizeof(u_int64_t)); 389 cur.rbytes = calloc(ndrive, sizeof(u_int64_t)); 390 cur.wbytes = calloc(ndrive, sizeof(u_int64_t)); 391 last.time = calloc(ndrive, sizeof(struct timeval)); 392 last.rxfer = calloc(ndrive, sizeof(u_int64_t)); 393 last.wxfer = calloc(ndrive, sizeof(u_int64_t)); 394 last.seek = calloc(ndrive, sizeof(u_int64_t)); 395 last.rbytes = calloc(ndrive, sizeof(u_int64_t)); 396 last.wbytes = calloc(ndrive, sizeof(u_int64_t)); 397 cur.select = calloc(ndrive, sizeof(int)); 398 cur.name = calloc(ndrive, sizeof(char *)); 399 400 if (cur.time == NULL || cur.rxfer == NULL || 401 cur.wxfer == NULL || cur.seek == NULL || 402 cur.rbytes == NULL || cur.wbytes == NULL || 403 last.time == NULL || last.rxfer == NULL || 404 last.wxfer == NULL || last.seek == NULL || 405 last.rbytes == NULL || last.wbytes == NULL || 406 cur.select == NULL || cur.name == NULL) 407 errx(1, "Memory allocation failure."); 408 409 /* Set up the compatibility interfaces. */ 410 drv_select = cur.select; 411 dr_name = cur.name; 412 413 /* Read the drive names and set intial selection. */ 414 if (memf == NULL) { 415 mib[0] = CTL_HW; /* Should be still set from */ 416 mib[1] = HW_IOSTATS; /* ... above, but be safe... */ 417 mib[2] = sizeof(struct io_sysctl); 418 if (sysctl(mib, 3, drives, &size, NULL, 0) == -1) 419 err(1, "sysctl hw.iostats failed"); 420 for (i = 0; i < ndrive; i++) { 421 cur.name[i] = drives[i].name; 422 cur.select[i] = selected; 423 } 424 } else { 425 p = iostathead; 426 for (i = 0; i < ndrive; i++) { 427 char buf[10]; 428 deref_kptr(p, &cur_drive, sizeof(cur_drive)); 429 deref_kptr(cur_drive.io_name, buf, sizeof(buf)); 430 cur.name[i] = strdup(buf); 431 if (!cur.name[i]) 432 err(1, "strdup"); 433 cur.select[i] = selected; 434 435 p = cur_drive.io_link.tqe_next; 436 } 437 } 438 439 /* Never do this initialization again. */ 440 once = 1; 441 return (1); 442 } 443 444 /* 445 * Dereference the kernel pointer `kptr' and fill in the local copy 446 * pointed to by `ptr'. The storage space must be pre-allocated, 447 * and the size of the copy passed in `len'. 448 */ 449 static void 450 deref_kptr(void *kptr, void *ptr, size_t len) 451 { 452 char buf[128]; 453 454 if (kvm_read(kd, (u_long)kptr, (char *)ptr, len) != len) { 455 memset(buf, 0, sizeof(buf)); 456 snprintf(buf, sizeof buf, "can't dereference kptr 0x%lx", 457 (u_long)kptr); 458 KVM_ERROR(buf); 459 } 460 } 461