1 /* Copyright (c) 2003 RogerSeguin <roger_seguin@msn.com>
2 * Copyright (c) 2003 Benedikt Meurer <benedikt.meurer@unix-ag.uni-siegen.de>
3 * Copyright (c) 2011 Peter Tribble <peter.tribble@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20
21 #include "devperf.h"
22
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <stdio.h>
32 #include <memory.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <sys/time.h>
36 /* for major() and minor() */
37 #define _BSD_SOURCE
38 #ifdef HAVE_SYS_SYSMACROS_H
39 #include <sys/sysmacros.h>
40 #endif
41 #include <sys/types.h>
42
43
44 #if defined(__linux__)
45 /**************************************************************/
46 /************************** Linux ***********************/
47 /**************************************************************/
48
49 static const char STATISTICS_FILE_1[] = "/proc/diskstats"; /* Kernel
50 2.6 */
51 static const char STATISTICS_FILE_2[] = "/proc/partitions"; /* Kernel
52 2.4 */
53
54 static const uint64_t SECTOR_SIZE = 512;
55
56 static int m_iInitStatus = 0;
57 static const char *m_pcStatFile = 0;
58
59 typedef int (*GetPerfData_t) (dev_t dev, struct devperf_t * perf);
60
61 static GetPerfData_t m_mGetPerfData = 0;
62
63 /**************************************************************/
64
DevGetPerfData1(dev_t p_iDevice,struct devperf_t * p_poPerf)65 static int DevGetPerfData1 (dev_t p_iDevice, struct devperf_t *p_poPerf)
66 /* Get disk performance statistics from STATISTICS_FILE_1 */
67 {
68 const int iMajorNo = major(p_iDevice),
69 iMinorNo = minor(p_iDevice);
70 struct timeval oTimeStamp;
71 FILE *pF;
72 unsigned int major, minor, rsect, wsect, ruse, wuse, use;
73 int running;
74 char acStats[128];
75 int c, n;
76
77 pF = fopen (STATISTICS_FILE_1, "r");
78 if (!pF) {
79 perror (STATISTICS_FILE_1);
80 return (-1);
81 }
82 while (1) {
83 n = fscanf (pF, "%u %u", &major, &minor);
84 if (n != 2)
85 goto Error;
86 if ((major != iMajorNo) || (minor != iMinorNo)) {
87 while ((c = fgetc (pF)) && (c != '\n')); /* Goto next line */
88 continue;
89 }
90 fscanf (pF, "%*s"); /* Skip device name */
91 /* Read rest of line into acStats */
92 if (!(fgets (acStats, sizeof (acStats), pF)))
93 goto Error;
94 n = sscanf (acStats,
95 "%*u %*u %u %u %*u %*u %u %u %d %u %*u",
96 &rsect, &ruse, &wsect, &wuse, &running, &use);
97 if (n != 6) {
98 /* Not a full-statistics line */
99 n = sscanf (acStats, "%*u %u %*u %u", &rsect, &wsect);
100 if (n != 2)
101 goto Error;
102 running = -1, ruse = wuse = 0;
103 }
104 fclose (pF);
105 gettimeofday (&oTimeStamp, 0);
106 p_poPerf->timestamp_ns =
107 (uint64_t) 1000 *1000 * 1000 * oTimeStamp.tv_sec +
108 1000 * oTimeStamp.tv_usec;
109 p_poPerf->rbytes = SECTOR_SIZE * rsect;
110 p_poPerf->wbytes = SECTOR_SIZE * wsect;
111 p_poPerf->qlen = running;
112 p_poPerf->rbusy_ns = (uint64_t) 1000 *1000 * ruse;
113 p_poPerf->wbusy_ns = (uint64_t) 1000 *1000 * wuse;
114 return (0);
115 }
116 Error:
117 fclose (pF);
118 return (-1);
119 } /* DevGetPerfData1() */
120
121
DevGetPerfData2(dev_t p_iDevice,struct devperf_t * p_poPerf)122 static int DevGetPerfData2 (dev_t p_iDevice, struct devperf_t *p_poPerf)
123 /* Get disk performance statistics from STATISTICS_FILE_2 */
124 {
125 const int iMajorNo = (p_iDevice >> 8) & 0xFF, /**/
126 iMinorNo = p_iDevice & 0xFF;
127 struct timeval oTimeStamp;
128 FILE *pF;
129 unsigned int major, minor, rsect, wsect, ruse, wuse, use;
130 int running;
131 int c, n;
132
133 pF = fopen (STATISTICS_FILE_2, "r");
134 if (!pF) {
135 perror (STATISTICS_FILE_2);
136 return (-1);
137 }
138 while ((c = fgetc (pF)) && (c != '\n')); /* Skip the header line */
139 while ((n = fscanf (pF,
140 "%u %u %*u %*s %*u %*u %u %u %*u %*u %u %u %d %u %*u",
141 &major, &minor, &rsect, &ruse, &wsect,
142 &wuse, &running, &use)) == 8)
143 if ((major == iMajorNo) && (minor == iMinorNo)) {
144 fclose (pF);
145 gettimeofday (&oTimeStamp, 0);
146 p_poPerf->timestamp_ns =
147 (uint64_t) 1000 *1000 * 1000 * oTimeStamp.tv_sec +
148 1000 * oTimeStamp.tv_usec;
149 p_poPerf->rbytes = SECTOR_SIZE * rsect;
150 p_poPerf->wbytes = SECTOR_SIZE * wsect;
151 p_poPerf->qlen = running;
152 p_poPerf->rbusy_ns = (uint64_t) 1000 *1000 * ruse;
153 p_poPerf->wbusy_ns = (uint64_t) 1000 *1000 * wuse;
154 return (0);
155 }
156 fclose (pF);
157 return (-1);
158 } /* DevGetPerfData2() */
159
160 /**************************************************************/
161
DevPerfInit(void)162 int DevPerfInit (void)
163 {
164 FILE *pF = 0;
165 char acLine[256];
166
167 /* Kernel 2.6 ? */
168 m_pcStatFile = STATISTICS_FILE_1;
169 m_mGetPerfData = DevGetPerfData1;
170 pF = fopen (m_pcStatFile, "r");
171 m_iInitStatus = 0;
172 if (pF)
173 goto End;
174
175 /* Kernel 2.4 */
176 m_pcStatFile = STATISTICS_FILE_2;
177 m_mGetPerfData = DevGetPerfData2;
178 pF = fopen (m_pcStatFile, "r");
179 if (pF)
180 m_iInitStatus = (((fgets (acLine, sizeof (acLine), pF))
181 && (strstr (acLine, "rsect"))) ? 0 :
182 NO_EXTENDED_STATS);
183 else
184 m_iInitStatus = -errno;
185
186 End:
187 if (pF)
188 fclose (pF);
189 return (m_iInitStatus);
190 } /* DevPerfInit() */
191
192
DevCheckStatAvailability(char const ** p_ppcStatFile)193 int DevCheckStatAvailability (char const **p_ppcStatFile)
194 {
195 if (p_ppcStatFile)
196 *p_ppcStatFile = m_pcStatFile;
197 return (m_iInitStatus);
198 } /* DevCheckStatAvailability() */
199
200
DevGetPerfData(const void * p_pvDevice,struct devperf_t * p_poPerf)201 int DevGetPerfData (const void *p_pvDevice, struct devperf_t *p_poPerf)
202 {
203 const dev_t p_iDevice = *((dev_t *) p_pvDevice);
204 return ((m_mGetPerfData && !m_iInitStatus) ?
205 (*m_mGetPerfData) (p_iDevice, p_poPerf) : -1);
206 } /* DevGetPerfData() */
207
208 /**************************************************************/
209
210 #if 0 /* Standalone test purpose */
211 int main ()
212 {
213 int iMajor = 3, /**/ iMinor = 3;
214 dev_t iDev = (iMajor << 8) + iMinor;
215 struct devperf_t oPerf;
216 unsigned int rsect, wsect;
217 int status;
218
219 status = DevPerfInit ();
220 if (status)
221 fprintf (stderr, "DevPerfInit() error\n");
222 status = DevGetPerfData (&iDev, &oPerf);
223 if (status)
224 fprintf (stderr, "DevGetPerfData() error\n");
225 rsect = oPerf.rbytes / SECTOR_SIZE;
226 wsect = oPerf.wbytes / SECTOR_SIZE;
227 printf ("%u\t%u\n", rsect, wsect);
228 return (0);
229 }
230 #endif
231
232 /************************** Linux End ***************/
233
234 #elif defined(__DragonFly__)
235 #include <sys/param.h>
236 #include <sys/sysctl.h>
237 #include <sys/types.h>
238 #include <sys/errno.h>
239 #include <sys/resource.h>
240 #include <sys/time.h>
241 #include <devstat.h>
242 #include <fcntl.h>
243 #include <limits.h>
244 #include <string.h>
245 #include <syslog.h>
246 #include <stdarg.h>
247
248 #define MAXNAMELEN 256
249
DevPerfInit()250 int DevPerfInit ()
251 {
252 return (0);
253 }
254
DevCheckStatAvailability(char const ** strptr)255 int DevCheckStatAvailability(char const **strptr)
256 {
257 return (0);
258 }
259
DevGetPerfData(const void * p_pvDevice,struct devperf_t * perf)260 int DevGetPerfData (const void *p_pvDevice, struct devperf_t *perf)
261 {
262 struct devinfo dinfo;
263 struct statinfo stats = {.dinfo = &dinfo};
264 char *check_dev = (char *) p_pvDevice;
265 struct devstat dev;
266 struct timeval tv;
267 int found, i;
268
269 if (getdevs(&stats) == -1) {
270 syslog(0, "DISKPERF: getdevs fail");
271 return (-1);
272 }
273
274 for(found = 0, i = 0; i < (stats.dinfo)->numdevs; i++) {
275 char dev_name[MAXNAMELEN];
276 dev = (stats.dinfo)->devices[i];
277 snprintf(dev_name, MAXNAMELEN-1, "%s%d",
278 dev.device_name, dev.unit_number);
279 if ((check_dev != NULL) && (strcmp(check_dev, dev_name) != 0))
280 continue;
281 else {
282 found = 1;
283 break;
284 }
285
286 }
287 if(check_dev != NULL && found) {
288 perf->wbytes = dev.bytes_written;
289 perf->rbytes = dev.bytes_read;
290 gettimeofday (&tv, 0);
291 perf->timestamp_ns = (uint64_t)1000ull * 1000ull * 1000ull *
292 tv.tv_sec + 1000ull * tv.tv_usec;
293 perf->qlen = dev.busy_count;
294 // I'm not sure about rbusy and wbusy calculation
295 perf->rbusy_ns = (uint64_t) dev.busy_time.tv_usec * 1000ull;
296 perf->wbusy_ns = perf->rbusy_ns;
297 }
298 return (0);
299 }
300
301 #if 0 /* Standalone test purpose */
302 int main ()
303 {
304 struct devperf_t oPerf;
305 bzero(&oPerf, sizeof(oPerf));
306 DevGetPerfData ((void*)"da0", &oPerf);
307 printf ("%lu\t%lu\n", oPerf.rbytes, oPerf.wbytes);
308 return (0);
309 }
310 #endif
311
312 #elif defined(__FreeBSD__)
313
314 #include <sys/disk.h>
315 #include <sys/param.h>
316 #include <sys/sysctl.h>
317 #include <sys/types.h>
318 #include <sys/errno.h>
319 #include <sys/resource.h>
320 #include <sys/time.h>
321 #include <devstat.h>
322 #include <fcntl.h>
323 #include <limits.h>
324 #include <string.h>
325 #include <syslog.h>
326 #include <stdarg.h>
327
328 #define MAXNAMELEN 256
329
DevPerfInit(void)330 int DevPerfInit (void)
331 {
332 return (0);
333 }
334
DevCheckStatAvailability(char const ** strptr)335 int DevCheckStatAvailability(char const **strptr)
336 {
337 return (0);
338 }
339
DevGetPerfData(const void * p_pvDevice,struct devperf_t * perf)340 int DevGetPerfData (const void *p_pvDevice, struct devperf_t *perf)
341 {
342 struct timeval tv;
343 struct timespec ts;
344 static struct devinfo dinfo;
345 static struct statinfo stats = {.dinfo = &dinfo};
346 struct devstat dev;
347 kvm_t *kd = NULL;
348 int i, found = 0;
349 char *check_dev = (char *) p_pvDevice;
350
351 if(devstat_getdevs(kd, &stats) == -1) {
352 syslog(0, "DISKPERF: getdevs fail");
353 return (-1);
354 }
355
356 for(found = 0, i = 0; i < (stats.dinfo)->numdevs; i++) {
357 char dev_name[MAXNAMELEN];
358 dev = (stats.dinfo)->devices[i];
359 snprintf(dev_name, MAXNAMELEN-1, "%s%d",
360 dev.device_name, dev.unit_number);
361 if ((check_dev != NULL) && (strcmp(check_dev, dev_name) != 0))
362 continue;
363 else {
364 found = 1;
365 break;
366 }
367
368 }
369
370 if(check_dev != NULL && found) {
371 perf->wbytes = dev.bytes[DEVSTAT_WRITE];
372 perf->rbytes = dev.bytes[DEVSTAT_READ];
373 gettimeofday (&tv, 0);
374 perf->timestamp_ns = (uint64_t)1000ull * 1000ull * 1000ull *
375 tv.tv_sec + 1000ull * tv.tv_usec;
376 perf->qlen = dev.start_count - dev.end_count;
377 // I'm not sure about rbusy and wbusy calculation
378 bintime2timespec(&dev.busy_time, &ts);
379 perf->rbusy_ns = (uint64_t) ts.tv_nsec;
380 perf->wbusy_ns = perf->rbusy_ns;
381 }
382
383 return (0);
384 }
385
386 #if 0 /* Standalone test purpose */
387 int main ()
388 {
389 struct devperf_t oPerf;
390 DevGetPerfData ((void*)"ada0", &oPerf);
391 printf ("%lu\t%lu\n", oPerf.rbytes, oPerf.wbytes);
392 return (0);
393 }
394 #endif
395
396
397
398 #elif defined(__NetBSD__)
399 /**************************************************************/
400 /************************** NetBSD ***********************/
401 /**************************************************************/
402 /* *INDENT-OFF* */
403
404 #include <sys/disk.h>
405 #include <sys/param.h>
406 #include <sys/sysctl.h>
407
DevPerfInit(void)408 int DevPerfInit (void)
409 {
410 return (0);
411 }
412
DevCheckStatAvailability(char const ** strptr)413 int DevCheckStatAvailability(char const **strptr)
414 {
415 return (0);
416 }
417
DevGetPerfData(const void * p_pvDevice,struct devperf_t * perf)418 int DevGetPerfData (const void *p_pvDevice, struct devperf_t *perf)
419 {
420 const char *device = (const char *) p_pvDevice;
421 struct timeval tv;
422 size_t size, i, ndrives;
423 struct disk_sysctl *drives, drive;
424 int mib[3];
425
426 mib[0] = CTL_HW;
427 mib[1] = HW_DISKSTATS;
428 mib[2] = sizeof(struct disk_sysctl);
429 if (sysctl(mib, 3, NULL, &size, NULL, 0) == -1)
430 return(-1);
431 ndrives = size / sizeof(struct disk_sysctl);
432 drives = malloc(size);
433 if (sysctl(mib, 3, drives, &size, NULL, 0) == -1)
434 return(-1);
435
436 for (i = 0; i < ndrives; i++) {
437 if (strcmp(drives[i].dk_name, device) == 0) {
438 drive = drives[i];
439 break;
440 }
441 }
442
443 free(drives);
444
445 if (i == ndrives)
446 return(-1);
447
448 gettimeofday (&tv, 0);
449 perf->timestamp_ns = (uint64_t)1000ull * 1000ull * 1000ull *
450 tv.tv_sec + 1000ull * tv.tv_usec;
451 #if defined(__NetBSD_Version__) && (__NetBSD_Version__ < 106110000)
452 /* NetBSD < 1.6K does not have separate read/write statistics. */
453 perf->rbytes = drive.dk_bytes;
454 perf->wbytes = drive.dk_bytes;
455 #else
456 perf->rbytes = drive.dk_rbytes;
457 perf->wbytes = drive.dk_wbytes;
458 #endif
459
460 /*
461 * XXX - Currently, I don't know of any way to determine write/read busy
462 * time separatly.
463 * -- Benedikt
464 */
465 perf->qlen = drive.dk_xfer;
466 perf->rbusy_ns = ((uint64_t)1000ull * 1000ull * 1000ull * drive.dk_time_sec
467 + 1000ull * drive.dk_time_usec) / 2ull;
468 perf->wbusy_ns = perf->rbusy_ns;
469
470 return(0);
471 }
472
473 /* *INDENT-ON* */
474 /************************** NetBSD End ***************/
475
476 #elif defined(__OpenBSD__)
477 /*
478 * OpenBSD support, taken from ports-tree cvs.
479 * x11/xfce4/xfce4-diskperf/patches/patch-panel-plugin_devperf_c
480 */
481
482 #include <sys/param.h>
483 #include <sys/sysctl.h>
484 #include <sys/disk.h>
485
DevPerfInit(void)486 int DevPerfInit (void)
487 {
488 return (0);
489 }
490
DevCheckStatAvailability(char const ** strptr)491 int DevCheckStatAvailability(char const **strptr)
492 {
493 return (0);
494 }
495
DevGetPerfData(const void * p_pvDevice,struct devperf_t * perf)496 int DevGetPerfData (const void *p_pvDevice, struct devperf_t *perf)
497 {
498 int mib[3], diskn, x;
499 size_t len;
500 char *devname = (char *)p_pvDevice;
501 struct diskstats *ds;
502 struct timeval tv;
503
504 mib[0] = CTL_HW;
505 mib[1] = HW_DISKCOUNT;
506 len = sizeof(diskn);
507
508 if (sysctl(mib, 2, &diskn, &len, NULL, 0) < 0)
509 return (-1);
510
511 mib[0] = CTL_HW;
512 mib[1] = HW_DISKSTATS;
513 len = diskn * sizeof(struct diskstats);
514
515 ds = malloc(len);
516 if (ds == NULL)
517 return (-1);
518
519 if (sysctl(mib, 2, ds, &len, NULL, 0) < 0) {
520 free(ds);
521 return (-1);
522 }
523
524 for (x = 0; x < diskn; x++)
525 if (!strcmp(ds[x].ds_name, devname))
526 break;
527
528 if (x == diskn) {
529 free(ds);
530 return (-1);
531 }
532
533 if (gettimeofday(&tv, NULL)) {
534 free(ds);
535 return (-1);
536 }
537
538 perf->timestamp_ns = (uint64_t)1000ull * 1000ull * 1000ull * tv.tv_sec
539 + 1000ull * tv.tv_usec;
540 perf->rbusy_ns = ((uint64_t)1000ull * 1000ull * 1000ull *
541 ds[x].ds_time.tv_sec + 1000ull * ds[x].ds_time.tv_usec) / 2ull;
542
543 perf->wbusy_ns = perf->rbusy_ns / 2ull;
544 perf->rbytes = ds[x].ds_rbytes;
545 perf->wbytes = ds[x].ds_wbytes;
546 perf->qlen = ds[x].ds_rxfer + ds[x].ds_wxfer;
547
548 free(ds);
549
550 return (0);
551 }
552
553 #elif defined (__sun__)
554 /*
555 * Solaris (and OpenSolaris derivatives) support via kstat
556 * Peter Tribble <peter.tribble@gmail.com>
557 */
558 #include <kstat.h>
559 static kstat_ctl_t *kc;
560
DevPerfInit(void)561 int DevPerfInit (void)
562 {
563 kc = kstat_open ();
564 return (0);
565 }
566
DevCheckStatAvailability(char const ** strptr)567 int DevCheckStatAvailability(char const **strptr)
568 {
569 return (0);
570 }
571
DevGetPerfData(const void * p_pvDevice,struct devperf_t * perf)572 int DevGetPerfData (const void *p_pvDevice, struct devperf_t *perf)
573 {
574 kstat_t *ksp;
575 kstat_io_t *kiot;
576 char *devname = (char *)p_pvDevice;
577
578 if(!kc)
579 DevPerfInit();
580
581 /*
582 * Use the device name. This is something like "sd3", after the
583 * module and instance. The user is expected to work out the
584 * possible device names. The command "iostat -x" is one way to
585 * enumerate them. It would be really neat to have a way to present
586 * this list to the user and get them to pick the one they want.
587 */
588 if(!(ksp = kstat_lookup (kc, NULL, -1, devname))) {
589 return (-1);
590 }
591 if (kstat_read(kc, ksp, 0) == -1) {
592 return (-1);
593 }
594 /*
595 * Just in case we accidentally matched something that wasn't
596 * an I/O device.
597 */
598 if (ksp->ks_type != KSTAT_TYPE_IO) {
599 return (-1);
600 }
601 kiot = KSTAT_IO_PTR(ksp);
602 perf->timestamp_ns = (uint64_t)ksp->ks_snaptime;
603 perf->rbytes = (uint64_t)kiot->nread;
604 perf->wbytes = (uint64_t)kiot->nwritten;
605 /*
606 * Solaris keeps separate wait and run queues, but they aren't
607 * separated by read and write. So allocate half to each.
608 */
609 perf->wbusy_ns = (uint64_t) (kiot->wtime + kiot->rtime) / 2ull;
610 perf->rbusy_ns = perf->wbusy_ns;
611 /*
612 * qlen isn't used, so set it to zero rather than calculate it.
613 */
614 perf->qlen = 0;
615 return (0);
616 }
617
618 #else
619 /**************************************************************/
620 /******************** Unsupported platform ***************/
621 /**************************************************************/
622 #error "Your platform is not yet supported"
623 #endif
624