1/* $NetBSD: drvstats.c,v 1.14 2021/11/27 22:16:42 rillig 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 <limits.h> 44#include <stdio.h> 45#include <stdlib.h> 46#include <string.h> 47#include <unistd.h> 48#include "drvstats.h" 49 50/* Structures to hold the statistics. */ 51struct _drive cur, last; 52 53extern int hz; 54 55/* sysctl hw.drivestats buffer. */ 56static struct io_sysctl *drives = NULL; 57 58/* Backward compatibility references. */ 59size_t ndrive = 0; 60int *drv_select; 61char **dr_name; 62 63/* Missing from <sys/time.h> */ 64#define timerset(tvp, uvp) do { \ 65 ((uvp)->tv_sec = (tvp)->tv_sec); \ 66 ((uvp)->tv_usec = (tvp)->tv_usec); \ 67} while (0) 68 69/* 70 * Take the delta between the present values and the last recorded 71 * values, storing the present values in the 'last' structure, and 72 * the delta values in the 'cur' structure. 73 */ 74void 75drvswap(void) 76{ 77 u_int64_t tmp; 78 size_t i; 79 80#define SWAP(fld) do { \ 81 tmp = cur.fld; \ 82 cur.fld -= last.fld; \ 83 last.fld = tmp; \ 84} while (0) 85 86#define DELTA(x) do { \ 87 timerclear(&tmp_timer); \ 88 timerset(&(cur.x), &tmp_timer); \ 89 timersub(&tmp_timer, &(last.x), &(cur.x)); \ 90 timerclear(&(last.x)); \ 91 timerset(&tmp_timer, &(last.x)); \ 92} while (0) 93 94 for (i = 0; i < ndrive; i++) { 95 struct timeval tmp_timer; 96 97 if (!cur.select[i]) 98 continue; 99 100 /* 101 * When a drive is replaced with one of the same 102 * name, the previous statistics are invalid. Try 103 * to detect this by validating counters and timestamp 104 */ 105 if ((cur.rxfer[i] == 0 && cur.wxfer[i] == 0) 106 || cur.rxfer[i] - last.rxfer[i] > INT64_MAX 107 || cur.wxfer[i] - last.wxfer[i] > INT64_MAX 108 || cur.seek[i] - last.seek[i] > INT64_MAX 109 || (cur.timestamp[i].tv_sec == 0 && 110 cur.timestamp[i].tv_usec == 0)) { 111 112 last.rxfer[i] = cur.rxfer[i]; 113 last.wxfer[i] = cur.wxfer[i]; 114 last.seek[i] = cur.seek[i]; 115 last.rbytes[i] = cur.rbytes[i]; 116 last.wbytes[i] = cur.wbytes[i]; 117 118 timerclear(&last.wait[i]); 119 timerclear(&last.time[i]); 120 timerclear(&last.waitsum[i]); 121 timerclear(&last.busysum[i]); 122 timerclear(&last.timestamp[i]); 123 } 124 125 /* Delta Values. */ 126 SWAP(rxfer[i]); 127 SWAP(wxfer[i]); 128 SWAP(seek[i]); 129 SWAP(rbytes[i]); 130 SWAP(wbytes[i]); 131 132 DELTA(wait[i]); 133 DELTA(time[i]); 134 DELTA(waitsum[i]); 135 DELTA(busysum[i]); 136 DELTA(timestamp[i]); 137 } 138} 139 140void 141tkswap(void) 142{ 143 u_int64_t tmp; 144 145 SWAP(tk_nin); 146 SWAP(tk_nout); 147} 148 149void 150cpuswap(void) 151{ 152 double etime; 153 u_int64_t tmp; 154 int i, state; 155 156 for (i = 0; i < CPUSTATES; i++) 157 SWAP(cp_time[i]); 158 159 etime = 0; 160 for (state = 0; state < CPUSTATES; ++state) { 161 etime += cur.cp_time[state]; 162 } 163 if (etime == 0) 164 etime = 1; 165 etime /= hz; 166 etime /= cur.cp_ncpu; 167 168 cur.cp_etime = etime; 169} 170#undef DELTA 171#undef SWAP 172 173/* 174 * Read the drive statistics for each drive in the drive list. 175 * Also collect statistics for tty i/o and CPU ticks. 176 */ 177void 178drvreadstats(void) 179{ 180 size_t size, i, j, count; 181 int mib[3]; 182 183 mib[0] = CTL_HW; 184 mib[1] = HW_IOSTATS; 185 mib[2] = sizeof(struct io_sysctl); 186 187 size = ndrive * sizeof(struct io_sysctl); 188 if (sysctl(mib, 3, drives, &size, NULL, 0) < 0) 189 err(1, "sysctl hw.iostats failed"); 190 /* recalculate array length */ 191 count = size / sizeof(struct io_sysctl); 192 193#define COPYF(x,k,l) cur.x[k] = drives[l].x 194#define COPYT(x,k,l) do { \ 195 cur.x[k].tv_sec = drives[l].x##_sec; \ 196 cur.x[k].tv_usec = drives[l].x##_usec; \ 197} while (0) 198 199 for (i = 0, j = 0; i < ndrive && j < count; i++) { 200 201 /* 202 * skip removed entries 203 * 204 * we cannot detect entries replaced with 205 * devices of the same name (e.g. unplug/replug). 206 */ 207 if (strcmp(cur.name[i], drives[j].name)) { 208 cur.select[i] = 0; 209 continue; 210 } 211 212 COPYF(rxfer, i, j); 213 COPYF(wxfer, i, j); 214 COPYF(seek, i, j); 215 COPYF(rbytes, i, j); 216 COPYF(wbytes, i, j); 217 218 COPYT(wait, i, j); 219 COPYT(time, i, j); 220 COPYT(waitsum, i, j); 221 COPYT(busysum, i, j); 222 COPYT(timestamp, i, j); 223 224 ++j; 225 } 226 227 /* shrink table to new size */ 228 ndrive = j; 229 230 mib[0] = CTL_KERN; 231 mib[1] = KERN_TKSTAT; 232 mib[2] = KERN_TKSTAT_NIN; 233 size = sizeof(cur.tk_nin); 234 if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0) 235 cur.tk_nin = 0; 236 237 mib[2] = KERN_TKSTAT_NOUT; 238 size = sizeof(cur.tk_nout); 239 if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0) 240 cur.tk_nout = 0; 241 242 size = sizeof(cur.cp_time); 243 (void)memset(cur.cp_time, 0, size); 244 mib[0] = CTL_KERN; 245 mib[1] = KERN_CP_TIME; 246 if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0) 247 (void)memset(cur.cp_time, 0, sizeof(cur.cp_time)); 248} 249#undef COPYT 250#undef COPYF 251 252/* 253 * Read collect statistics for tty i/o. 254 */ 255 256void 257tkreadstats(void) 258{ 259 size_t size; 260 int mib[3]; 261 262 mib[0] = CTL_KERN; 263 mib[1] = KERN_TKSTAT; 264 mib[2] = KERN_TKSTAT_NIN; 265 size = sizeof(cur.tk_nin); 266 if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0) 267 cur.tk_nin = 0; 268 269 mib[2] = KERN_TKSTAT_NOUT; 270 size = sizeof(cur.tk_nout); 271 if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0) 272 cur.tk_nout = 0; 273} 274 275/* 276 * Read collect statistics for CPU ticks. 277 */ 278 279void 280cpureadstats(void) 281{ 282 size_t size; 283 int mib[2]; 284 285 size = sizeof(cur.cp_time); 286 (void)memset(cur.cp_time, 0, size); 287 mib[0] = CTL_KERN; 288 mib[1] = KERN_CP_TIME; 289 if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0) 290 (void)memset(cur.cp_time, 0, sizeof(cur.cp_time)); 291} 292 293/* 294 * Perform all of the initialization and memory allocation needed to 295 * track drive statistics. 296 */ 297int 298drvinit(int selected) 299{ 300 struct clockinfo clockinfo; 301 size_t size, i; 302 static int once = 0; 303 int mib[3]; 304 305 if (once) 306 return (1); 307 308 mib[0] = CTL_HW; 309 mib[1] = HW_NCPU; 310 size = sizeof(cur.cp_ncpu); 311 if (sysctl(mib, 2, &cur.cp_ncpu, &size, NULL, 0) == -1) 312 err(1, "sysctl hw.ncpu failed"); 313 314 mib[0] = CTL_KERN; 315 mib[1] = KERN_CLOCKRATE; 316 size = sizeof(clockinfo); 317 if (sysctl(mib, 2, &clockinfo, &size, NULL, 0) == -1) 318 err(1, "sysctl kern.clockrate failed"); 319 hz = clockinfo.stathz; 320 if (!hz) 321 hz = clockinfo.hz; 322 323 mib[0] = CTL_HW; 324 mib[1] = HW_IOSTATS; 325 mib[2] = sizeof(struct io_sysctl); 326 if (sysctl(mib, 3, NULL, &size, NULL, 0) == -1) 327 err(1, "sysctl hw.drivestats failed"); 328 ndrive = size / sizeof(struct io_sysctl); 329 330 if (size == 0) { 331 warnx("No drives attached."); 332 } else { 333 drives = (struct io_sysctl *)malloc(size); 334 if (drives == NULL) 335 errx(1, "Memory allocation failure."); 336 } 337 338 /* Allocate space for the statistics. */ 339 cur.time = calloc(ndrive, sizeof(struct timeval)); 340 cur.wait = calloc(ndrive, sizeof(struct timeval)); 341 cur.waitsum = calloc(ndrive, sizeof(struct timeval)); 342 cur.busysum = calloc(ndrive, sizeof(struct timeval)); 343 cur.timestamp = calloc(ndrive, sizeof(struct timeval)); 344 cur.rxfer = calloc(ndrive, sizeof(u_int64_t)); 345 cur.wxfer = calloc(ndrive, sizeof(u_int64_t)); 346 cur.seek = calloc(ndrive, sizeof(u_int64_t)); 347 cur.rbytes = calloc(ndrive, sizeof(u_int64_t)); 348 cur.wbytes = calloc(ndrive, sizeof(u_int64_t)); 349 cur.scale = calloc(ndrive, sizeof(int)); 350 last.time = calloc(ndrive, sizeof(struct timeval)); 351 last.wait = calloc(ndrive, sizeof(struct timeval)); 352 last.waitsum = calloc(ndrive, sizeof(struct timeval)); 353 last.busysum = calloc(ndrive, sizeof(struct timeval)); 354 last.timestamp = calloc(ndrive, sizeof(struct timeval)); 355 last.rxfer = calloc(ndrive, sizeof(u_int64_t)); 356 last.wxfer = calloc(ndrive, sizeof(u_int64_t)); 357 last.seek = calloc(ndrive, sizeof(u_int64_t)); 358 last.rbytes = calloc(ndrive, sizeof(u_int64_t)); 359 last.wbytes = calloc(ndrive, sizeof(u_int64_t)); 360 cur.select = calloc(ndrive, sizeof(int)); 361 cur.name = calloc(ndrive, sizeof(char *)); 362 363 if (cur.time == NULL || cur.wait == NULL || 364 cur.waitsum == NULL || cur.busysum == NULL || 365 cur.timestamp == NULL || 366 cur.rxfer == NULL || cur.wxfer == NULL || 367 cur.seek == NULL || cur.rbytes == NULL || 368 cur.wbytes == NULL || 369 last.time == NULL || last.wait == NULL || 370 last.waitsum == NULL || last.busysum == NULL || 371 last.timestamp == NULL || 372 last.rxfer == NULL || last.wxfer == NULL || 373 last.seek == NULL || last.rbytes == NULL || 374 last.wbytes == NULL || 375 cur.select == NULL || cur.name == NULL) 376 errx(1, "Memory allocation failure."); 377 378 /* Set up the compatibility interfaces. */ 379 drv_select = cur.select; 380 dr_name = cur.name; 381 382 /* Read the drive names and set initial selection. */ 383 mib[0] = CTL_HW; /* Should be still set from */ 384 mib[1] = HW_IOSTATS; /* ... above, but be safe... */ 385 mib[2] = sizeof(struct io_sysctl); 386 if (sysctl(mib, 3, drives, &size, NULL, 0) == -1) 387 err(1, "sysctl hw.iostats failed"); 388 /* Recalculate array length */ 389 ndrive = size / sizeof(struct io_sysctl); 390 for (i = 0; i < ndrive; i++) { 391 cur.name[i] = strndup(drives[i].name, sizeof(drives[i].name)); 392 if (cur.name[i] == NULL) 393 errx(1, "Memory allocation failure"); 394 cur.select[i] = selected; 395 } 396 397 /* Never do this initialization again. */ 398 once = 1; 399 return (1); 400} 401