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. */
51 struct _drive       cur, last;
52 
53 extern int          hz;
54 
55 /* sysctl hw.drivestats buffer. */
56 static struct io_sysctl       *drives = NULL;
57 
58 /* Backward compatibility references. */
59 size_t              ndrive = 0;
60 int                 *drv_select;
61 char                **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  */
74 void
drvswap(void)75 drvswap(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 
140 void
tkswap(void)141 tkswap(void)
142 {
143           u_int64_t tmp;
144 
145           SWAP(tk_nin);
146           SWAP(tk_nout);
147 }
148 
149 void
cpuswap(void)150 cpuswap(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  */
177 void
drvreadstats(void)178 drvreadstats(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 
256 void
tkreadstats(void)257 tkreadstats(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 
279 void
cpureadstats(void)280 cpureadstats(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  */
297 int
drvinit(int selected)298 drvinit(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