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