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