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