1*1d6b489cSrillig /* $NetBSD: drvstats.c,v 1.14 2021/11/27 22:16:42 rillig Exp $ */
27d866d26Sblymn
37d866d26Sblymn /*
47d866d26Sblymn * Copyright (c) 1996 John M. Vinopal
57d866d26Sblymn * All rights reserved.
67d866d26Sblymn *
77d866d26Sblymn * Redistribution and use in source and binary forms, with or without
87d866d26Sblymn * modification, are permitted provided that the following conditions
97d866d26Sblymn * are met:
107d866d26Sblymn * 1. Redistributions of source code must retain the above copyright
117d866d26Sblymn * notice, this list of conditions and the following disclaimer.
127d866d26Sblymn * 2. Redistributions in binary form must reproduce the above copyright
137d866d26Sblymn * notice, this list of conditions and the following disclaimer in the
147d866d26Sblymn * documentation and/or other materials provided with the distribution.
157d866d26Sblymn * 3. All advertising materials mentioning features or use of this software
167d866d26Sblymn * must display the following acknowledgement:
177d866d26Sblymn * This product includes software developed for the NetBSD Project
187d866d26Sblymn * by John M. Vinopal.
197d866d26Sblymn * 4. The name of the author may not be used to endorse or promote products
207d866d26Sblymn * derived from this software without specific prior written permission.
217d866d26Sblymn *
227d866d26Sblymn * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
237d866d26Sblymn * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
247d866d26Sblymn * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
257d866d26Sblymn * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
267d866d26Sblymn * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
277d866d26Sblymn * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
287d866d26Sblymn * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
297d866d26Sblymn * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
307d866d26Sblymn * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
317d866d26Sblymn * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
327d866d26Sblymn * SUCH DAMAGE.
337d866d26Sblymn */
347d866d26Sblymn
357d866d26Sblymn #include <sys/param.h>
367d866d26Sblymn #include <sys/sched.h>
377d866d26Sblymn #include <sys/sysctl.h>
387d866d26Sblymn #include <sys/time.h>
397d866d26Sblymn #include <sys/iostat.h>
407d866d26Sblymn
417d866d26Sblymn #include <err.h>
427d866d26Sblymn #include <fcntl.h>
437d866d26Sblymn #include <limits.h>
447d866d26Sblymn #include <stdio.h>
457d866d26Sblymn #include <stdlib.h>
467d866d26Sblymn #include <string.h>
477d866d26Sblymn #include <unistd.h>
487d866d26Sblymn #include "drvstats.h"
497d866d26Sblymn
507d866d26Sblymn /* Structures to hold the statistics. */
517d866d26Sblymn struct _drive cur, last;
527d866d26Sblymn
537d866d26Sblymn extern int hz;
547d866d26Sblymn
557d866d26Sblymn /* sysctl hw.drivestats buffer. */
567d866d26Sblymn static struct io_sysctl *drives = NULL;
577d866d26Sblymn
587d866d26Sblymn /* Backward compatibility references. */
593ceb6c1cSchristos size_t ndrive = 0;
607d866d26Sblymn int *drv_select;
617d866d26Sblymn char **dr_name;
627d866d26Sblymn
637d866d26Sblymn /* Missing from <sys/time.h> */
647d866d26Sblymn #define timerset(tvp, uvp) do { \
657d866d26Sblymn ((uvp)->tv_sec = (tvp)->tv_sec); \
667d866d26Sblymn ((uvp)->tv_usec = (tvp)->tv_usec); \
67*1d6b489cSrillig } while (0)
687d866d26Sblymn
697d866d26Sblymn /*
707d866d26Sblymn * Take the delta between the present values and the last recorded
717d866d26Sblymn * values, storing the present values in the 'last' structure, and
727d866d26Sblymn * the delta values in the 'cur' structure.
737d866d26Sblymn */
747d866d26Sblymn void
drvswap(void)757d866d26Sblymn drvswap(void)
767d866d26Sblymn {
777d866d26Sblymn u_int64_t tmp;
78c493aef5Slukem size_t i;
797d866d26Sblymn
807d866d26Sblymn #define SWAP(fld) do { \
817d866d26Sblymn tmp = cur.fld; \
827d866d26Sblymn cur.fld -= last.fld; \
837d866d26Sblymn last.fld = tmp; \
84*1d6b489cSrillig } while (0)
857d866d26Sblymn
86ba576b71Smlelstv #define DELTA(x) do { \
87ba576b71Smlelstv timerclear(&tmp_timer); \
88ba576b71Smlelstv timerset(&(cur.x), &tmp_timer); \
89ba576b71Smlelstv timersub(&tmp_timer, &(last.x), &(cur.x)); \
90ba576b71Smlelstv timerclear(&(last.x)); \
91ba576b71Smlelstv timerset(&tmp_timer, &(last.x)); \
92*1d6b489cSrillig } while (0)
93ba576b71Smlelstv
947d866d26Sblymn for (i = 0; i < ndrive; i++) {
957d866d26Sblymn struct timeval tmp_timer;
967d866d26Sblymn
977d866d26Sblymn if (!cur.select[i])
987d866d26Sblymn continue;
997d866d26Sblymn
100245a1a45Smlelstv /*
101245a1a45Smlelstv * When a drive is replaced with one of the same
102245a1a45Smlelstv * name, the previous statistics are invalid. Try
103245a1a45Smlelstv * to detect this by validating counters and timestamp
104245a1a45Smlelstv */
105245a1a45Smlelstv if ((cur.rxfer[i] == 0 && cur.wxfer[i] == 0)
106245a1a45Smlelstv || cur.rxfer[i] - last.rxfer[i] > INT64_MAX
107245a1a45Smlelstv || cur.wxfer[i] - last.wxfer[i] > INT64_MAX
108245a1a45Smlelstv || cur.seek[i] - last.seek[i] > INT64_MAX
109245a1a45Smlelstv || (cur.timestamp[i].tv_sec == 0 &&
110245a1a45Smlelstv cur.timestamp[i].tv_usec == 0)) {
111245a1a45Smlelstv
112245a1a45Smlelstv last.rxfer[i] = cur.rxfer[i];
113245a1a45Smlelstv last.wxfer[i] = cur.wxfer[i];
114245a1a45Smlelstv last.seek[i] = cur.seek[i];
115245a1a45Smlelstv last.rbytes[i] = cur.rbytes[i];
116245a1a45Smlelstv last.wbytes[i] = cur.wbytes[i];
117245a1a45Smlelstv
118245a1a45Smlelstv timerclear(&last.wait[i]);
119245a1a45Smlelstv timerclear(&last.time[i]);
120245a1a45Smlelstv timerclear(&last.waitsum[i]);
121245a1a45Smlelstv timerclear(&last.busysum[i]);
122245a1a45Smlelstv timerclear(&last.timestamp[i]);
123245a1a45Smlelstv }
124245a1a45Smlelstv
1257d866d26Sblymn /* Delta Values. */
1267d866d26Sblymn SWAP(rxfer[i]);
1277d866d26Sblymn SWAP(wxfer[i]);
1287d866d26Sblymn SWAP(seek[i]);
1297d866d26Sblymn SWAP(rbytes[i]);
1307d866d26Sblymn SWAP(wbytes[i]);
1317d866d26Sblymn
132ba576b71Smlelstv DELTA(wait[i]);
133ba576b71Smlelstv DELTA(time[i]);
134ba576b71Smlelstv DELTA(waitsum[i]);
135ba576b71Smlelstv DELTA(busysum[i]);
136245a1a45Smlelstv DELTA(timestamp[i]);
1377d866d26Sblymn }
1387d866d26Sblymn }
1397d866d26Sblymn
1407d866d26Sblymn void
tkswap(void)1417d866d26Sblymn tkswap(void)
1427d866d26Sblymn {
1437d866d26Sblymn u_int64_t tmp;
1447d866d26Sblymn
1457d866d26Sblymn SWAP(tk_nin);
1467d866d26Sblymn SWAP(tk_nout);
1477d866d26Sblymn }
1487d866d26Sblymn
1497d866d26Sblymn void
cpuswap(void)1507d866d26Sblymn cpuswap(void)
1517d866d26Sblymn {
1527d866d26Sblymn double etime;
1537d866d26Sblymn u_int64_t tmp;
1547d866d26Sblymn int i, state;
1557d866d26Sblymn
1567d866d26Sblymn for (i = 0; i < CPUSTATES; i++)
1577d866d26Sblymn SWAP(cp_time[i]);
1587d866d26Sblymn
1597d866d26Sblymn etime = 0;
1607d866d26Sblymn for (state = 0; state < CPUSTATES; ++state) {
1617d866d26Sblymn etime += cur.cp_time[state];
1627d866d26Sblymn }
1637d866d26Sblymn if (etime == 0)
1647d866d26Sblymn etime = 1;
1657d866d26Sblymn etime /= hz;
1667d866d26Sblymn etime /= cur.cp_ncpu;
1677d866d26Sblymn
1687d866d26Sblymn cur.cp_etime = etime;
1697d866d26Sblymn }
170ba576b71Smlelstv #undef DELTA
1717d866d26Sblymn #undef SWAP
1727d866d26Sblymn
1737d866d26Sblymn /*
1747d866d26Sblymn * Read the drive statistics for each drive in the drive list.
1757d866d26Sblymn * Also collect statistics for tty i/o and CPU ticks.
1767d866d26Sblymn */
1777d866d26Sblymn void
drvreadstats(void)1787d866d26Sblymn drvreadstats(void)
1797d866d26Sblymn {
180245a1a45Smlelstv size_t size, i, j, count;
1817d866d26Sblymn int mib[3];
1827d866d26Sblymn
1837d866d26Sblymn mib[0] = CTL_HW;
1847d866d26Sblymn mib[1] = HW_IOSTATS;
1857d866d26Sblymn mib[2] = sizeof(struct io_sysctl);
1867d866d26Sblymn
1877d866d26Sblymn size = ndrive * sizeof(struct io_sysctl);
1887d866d26Sblymn if (sysctl(mib, 3, drives, &size, NULL, 0) < 0)
1897d866d26Sblymn err(1, "sysctl hw.iostats failed");
190245a1a45Smlelstv /* recalculate array length */
191245a1a45Smlelstv count = size / sizeof(struct io_sysctl);
192ba576b71Smlelstv
193245a1a45Smlelstv #define COPYF(x,k,l) cur.x[k] = drives[l].x
194245a1a45Smlelstv #define COPYT(x,k,l) do { \
195245a1a45Smlelstv cur.x[k].tv_sec = drives[l].x##_sec; \
196245a1a45Smlelstv cur.x[k].tv_usec = drives[l].x##_usec; \
197*1d6b489cSrillig } while (0)
198ba576b71Smlelstv
199245a1a45Smlelstv for (i = 0, j = 0; i < ndrive && j < count; i++) {
200ba576b71Smlelstv
201245a1a45Smlelstv /*
202245a1a45Smlelstv * skip removed entries
203245a1a45Smlelstv *
204245a1a45Smlelstv * we cannot detect entries replaced with
205245a1a45Smlelstv * devices of the same name (e.g. unplug/replug).
206245a1a45Smlelstv */
207245a1a45Smlelstv if (strcmp(cur.name[i], drives[j].name)) {
208245a1a45Smlelstv cur.select[i] = 0;
209245a1a45Smlelstv continue;
2107d866d26Sblymn }
2117d866d26Sblymn
212245a1a45Smlelstv COPYF(rxfer, i, j);
213245a1a45Smlelstv COPYF(wxfer, i, j);
214245a1a45Smlelstv COPYF(seek, i, j);
215245a1a45Smlelstv COPYF(rbytes, i, j);
216245a1a45Smlelstv COPYF(wbytes, i, j);
217245a1a45Smlelstv
218245a1a45Smlelstv COPYT(wait, i, j);
219245a1a45Smlelstv COPYT(time, i, j);
220245a1a45Smlelstv COPYT(waitsum, i, j);
221245a1a45Smlelstv COPYT(busysum, i, j);
222245a1a45Smlelstv COPYT(timestamp, i, j);
223245a1a45Smlelstv
224245a1a45Smlelstv ++j;
225245a1a45Smlelstv }
226245a1a45Smlelstv
227245a1a45Smlelstv /* shrink table to new size */
228245a1a45Smlelstv ndrive = j;
229245a1a45Smlelstv
2307d866d26Sblymn mib[0] = CTL_KERN;
2317d866d26Sblymn mib[1] = KERN_TKSTAT;
2327d866d26Sblymn mib[2] = KERN_TKSTAT_NIN;
2337d866d26Sblymn size = sizeof(cur.tk_nin);
2347d866d26Sblymn if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0)
2357d866d26Sblymn cur.tk_nin = 0;
2367d866d26Sblymn
2377d866d26Sblymn mib[2] = KERN_TKSTAT_NOUT;
2387d866d26Sblymn size = sizeof(cur.tk_nout);
2397d866d26Sblymn if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0)
2407d866d26Sblymn cur.tk_nout = 0;
2417d866d26Sblymn
2427d866d26Sblymn size = sizeof(cur.cp_time);
2433ceb6c1cSchristos (void)memset(cur.cp_time, 0, size);
2447d866d26Sblymn mib[0] = CTL_KERN;
2457d866d26Sblymn mib[1] = KERN_CP_TIME;
2467d866d26Sblymn if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0)
2473ceb6c1cSchristos (void)memset(cur.cp_time, 0, sizeof(cur.cp_time));
2487d866d26Sblymn }
249ba576b71Smlelstv #undef COPYT
250ba576b71Smlelstv #undef COPYF
2517d866d26Sblymn
2527d866d26Sblymn /*
2537d866d26Sblymn * Read collect statistics for tty i/o.
2547d866d26Sblymn */
2557d866d26Sblymn
2567d866d26Sblymn void
tkreadstats(void)2577d866d26Sblymn tkreadstats(void)
2587d866d26Sblymn {
2597d866d26Sblymn size_t size;
2607d866d26Sblymn int mib[3];
2617d866d26Sblymn
2627d866d26Sblymn mib[0] = CTL_KERN;
2637d866d26Sblymn mib[1] = KERN_TKSTAT;
2647d866d26Sblymn mib[2] = KERN_TKSTAT_NIN;
2657d866d26Sblymn size = sizeof(cur.tk_nin);
2667d866d26Sblymn if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0)
2677d866d26Sblymn cur.tk_nin = 0;
2687d866d26Sblymn
2697d866d26Sblymn mib[2] = KERN_TKSTAT_NOUT;
2707d866d26Sblymn size = sizeof(cur.tk_nout);
2717d866d26Sblymn if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0)
2727d866d26Sblymn cur.tk_nout = 0;
2737d866d26Sblymn }
2747d866d26Sblymn
2757d866d26Sblymn /*
2767d866d26Sblymn * Read collect statistics for CPU ticks.
2777d866d26Sblymn */
2787d866d26Sblymn
2797d866d26Sblymn void
cpureadstats(void)2807d866d26Sblymn cpureadstats(void)
2817d866d26Sblymn {
2827d866d26Sblymn size_t size;
2837d866d26Sblymn int mib[2];
2847d866d26Sblymn
2857d866d26Sblymn size = sizeof(cur.cp_time);
2863ceb6c1cSchristos (void)memset(cur.cp_time, 0, size);
2877d866d26Sblymn mib[0] = CTL_KERN;
2887d866d26Sblymn mib[1] = KERN_CP_TIME;
2897d866d26Sblymn if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0)
2903ceb6c1cSchristos (void)memset(cur.cp_time, 0, sizeof(cur.cp_time));
2917d866d26Sblymn }
2927d866d26Sblymn
2937d866d26Sblymn /*
2947d866d26Sblymn * Perform all of the initialization and memory allocation needed to
2957d866d26Sblymn * track drive statistics.
2967d866d26Sblymn */
2977d866d26Sblymn int
drvinit(int selected)2987d866d26Sblymn drvinit(int selected)
2997d866d26Sblymn {
3007d866d26Sblymn struct clockinfo clockinfo;
301c493aef5Slukem size_t size, i;
3027d866d26Sblymn static int once = 0;
303c493aef5Slukem int mib[3];
3047d866d26Sblymn
3057d866d26Sblymn if (once)
3067d866d26Sblymn return (1);
3077d866d26Sblymn
3087d866d26Sblymn mib[0] = CTL_HW;
3097d866d26Sblymn mib[1] = HW_NCPU;
3107d866d26Sblymn size = sizeof(cur.cp_ncpu);
3117d866d26Sblymn if (sysctl(mib, 2, &cur.cp_ncpu, &size, NULL, 0) == -1)
3127d866d26Sblymn err(1, "sysctl hw.ncpu failed");
3137d866d26Sblymn
3147d866d26Sblymn mib[0] = CTL_KERN;
3157d866d26Sblymn mib[1] = KERN_CLOCKRATE;
3167d866d26Sblymn size = sizeof(clockinfo);
3177d866d26Sblymn if (sysctl(mib, 2, &clockinfo, &size, NULL, 0) == -1)
3187d866d26Sblymn err(1, "sysctl kern.clockrate failed");
3197d866d26Sblymn hz = clockinfo.stathz;
3207d866d26Sblymn if (!hz)
3217d866d26Sblymn hz = clockinfo.hz;
3227d866d26Sblymn
3237d866d26Sblymn mib[0] = CTL_HW;
3247d866d26Sblymn mib[1] = HW_IOSTATS;
3257d866d26Sblymn mib[2] = sizeof(struct io_sysctl);
3267d866d26Sblymn if (sysctl(mib, 3, NULL, &size, NULL, 0) == -1)
3277d866d26Sblymn err(1, "sysctl hw.drivestats failed");
3287d866d26Sblymn ndrive = size / sizeof(struct io_sysctl);
3297d866d26Sblymn
3307d866d26Sblymn if (size == 0) {
3317d866d26Sblymn warnx("No drives attached.");
3327d866d26Sblymn } else {
3337d866d26Sblymn drives = (struct io_sysctl *)malloc(size);
3347d866d26Sblymn if (drives == NULL)
3357d866d26Sblymn errx(1, "Memory allocation failure.");
3367d866d26Sblymn }
3377d866d26Sblymn
3387d866d26Sblymn /* Allocate space for the statistics. */
3397d866d26Sblymn cur.time = calloc(ndrive, sizeof(struct timeval));
340ba576b71Smlelstv cur.wait = calloc(ndrive, sizeof(struct timeval));
341ba576b71Smlelstv cur.waitsum = calloc(ndrive, sizeof(struct timeval));
342ba576b71Smlelstv cur.busysum = calloc(ndrive, sizeof(struct timeval));
343245a1a45Smlelstv cur.timestamp = calloc(ndrive, sizeof(struct timeval));
3447d866d26Sblymn cur.rxfer = calloc(ndrive, sizeof(u_int64_t));
3457d866d26Sblymn cur.wxfer = calloc(ndrive, sizeof(u_int64_t));
3467d866d26Sblymn cur.seek = calloc(ndrive, sizeof(u_int64_t));
3477d866d26Sblymn cur.rbytes = calloc(ndrive, sizeof(u_int64_t));
3487d866d26Sblymn cur.wbytes = calloc(ndrive, sizeof(u_int64_t));
349feecb28eShe cur.scale = calloc(ndrive, sizeof(int));
3507d866d26Sblymn last.time = calloc(ndrive, sizeof(struct timeval));
351ba576b71Smlelstv last.wait = calloc(ndrive, sizeof(struct timeval));
352ba576b71Smlelstv last.waitsum = calloc(ndrive, sizeof(struct timeval));
353ba576b71Smlelstv last.busysum = calloc(ndrive, sizeof(struct timeval));
354245a1a45Smlelstv last.timestamp = calloc(ndrive, sizeof(struct timeval));
3557d866d26Sblymn last.rxfer = calloc(ndrive, sizeof(u_int64_t));
3567d866d26Sblymn last.wxfer = calloc(ndrive, sizeof(u_int64_t));
3577d866d26Sblymn last.seek = calloc(ndrive, sizeof(u_int64_t));
3587d866d26Sblymn last.rbytes = calloc(ndrive, sizeof(u_int64_t));
3597d866d26Sblymn last.wbytes = calloc(ndrive, sizeof(u_int64_t));
3607d866d26Sblymn cur.select = calloc(ndrive, sizeof(int));
3617d866d26Sblymn cur.name = calloc(ndrive, sizeof(char *));
3627d866d26Sblymn
363ba576b71Smlelstv if (cur.time == NULL || cur.wait == NULL ||
364ba576b71Smlelstv cur.waitsum == NULL || cur.busysum == NULL ||
365245a1a45Smlelstv cur.timestamp == NULL ||
366ba576b71Smlelstv cur.rxfer == NULL || cur.wxfer == NULL ||
367ba576b71Smlelstv cur.seek == NULL || cur.rbytes == NULL ||
368ba576b71Smlelstv cur.wbytes == NULL ||
369ba576b71Smlelstv last.time == NULL || last.wait == NULL ||
370ba576b71Smlelstv last.waitsum == NULL || last.busysum == NULL ||
371245a1a45Smlelstv last.timestamp == NULL ||
372ba576b71Smlelstv last.rxfer == NULL || last.wxfer == NULL ||
373ba576b71Smlelstv last.seek == NULL || last.rbytes == NULL ||
374ba576b71Smlelstv last.wbytes == NULL ||
3757d866d26Sblymn cur.select == NULL || cur.name == NULL)
3767d866d26Sblymn errx(1, "Memory allocation failure.");
3777d866d26Sblymn
3787d866d26Sblymn /* Set up the compatibility interfaces. */
3797d866d26Sblymn drv_select = cur.select;
3807d866d26Sblymn dr_name = cur.name;
3817d866d26Sblymn
38232cded6cSdholland /* Read the drive names and set initial selection. */
3837d866d26Sblymn mib[0] = CTL_HW; /* Should be still set from */
3847d866d26Sblymn mib[1] = HW_IOSTATS; /* ... above, but be safe... */
3857d866d26Sblymn mib[2] = sizeof(struct io_sysctl);
3867d866d26Sblymn if (sysctl(mib, 3, drives, &size, NULL, 0) == -1)
3877d866d26Sblymn err(1, "sysctl hw.iostats failed");
388245a1a45Smlelstv /* Recalculate array length */
389245a1a45Smlelstv ndrive = size / sizeof(struct io_sysctl);
3907d866d26Sblymn for (i = 0; i < ndrive; i++) {
391245a1a45Smlelstv cur.name[i] = strndup(drives[i].name, sizeof(drives[i].name));
392245a1a45Smlelstv if (cur.name[i] == NULL)
393245a1a45Smlelstv errx(1, "Memory allocation failure");
3947d866d26Sblymn cur.select[i] = selected;
3957d866d26Sblymn }
3967d866d26Sblymn
3977d866d26Sblymn /* Never do this initialization again. */
3987d866d26Sblymn once = 1;
3997d866d26Sblymn return (1);
4007d866d26Sblymn }
401