xref: /freebsd/usr.sbin/diskinfo/diskinfo.c (revision f37852c17391fdf0e8309bcf684384dd0d854e43)
1 /*-
2  * Copyright (c) 2003 Poul-Henning Kamp
3  * Copyright (c) 2015 Spectra Logic Corporation
4  * Copyright (c) 2017 Alexander Motin <mav@FreeBSD.org>
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. The names of the authors may not be used to endorse or promote
16  *    products derived from this software without specific prior written
17  *    permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  *
31  * $FreeBSD$
32  */
33 
34 #include <stdio.h>
35 #include <stdint.h>
36 #include <stdlib.h>
37 #include <strings.h>
38 #include <unistd.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <libutil.h>
42 #include <paths.h>
43 #include <err.h>
44 #include <sysexits.h>
45 #include <sys/aio.h>
46 #include <sys/disk.h>
47 #include <sys/param.h>
48 #include <sys/stat.h>
49 #include <sys/time.h>
50 
51 #define	NAIO	128
52 
53 static void
54 usage(void)
55 {
56 	fprintf(stderr, "usage: diskinfo [-cipsStvw] disk ...\n");
57 	exit (1);
58 }
59 
60 static int opt_c, opt_i, opt_p, opt_s, opt_S, opt_t, opt_v, opt_w;
61 
62 static void speeddisk(int fd, off_t mediasize, u_int sectorsize);
63 static void commandtime(int fd, off_t mediasize, u_int sectorsize);
64 static void iopsbench(int fd, off_t mediasize, u_int sectorsize);
65 static void slogbench(int fd, int isreg, off_t mediasize, u_int sectorsize);
66 static int zonecheck(int fd, uint32_t *zone_mode, char *zone_str,
67 		     size_t zone_str_len);
68 
69 int
70 main(int argc, char **argv)
71 {
72 	struct stat sb;
73 	int i, ch, fd, error, exitval = 0;
74 	char buf[BUFSIZ], ident[DISK_IDENT_SIZE], physpath[MAXPATHLEN];
75 	char zone_desc[64];
76 	off_t	mediasize, stripesize, stripeoffset;
77 	u_int	sectorsize, fwsectors, fwheads, zoned = 0, isreg;
78 	uint32_t zone_mode;
79 
80 	while ((ch = getopt(argc, argv, "cipsStvw")) != -1) {
81 		switch (ch) {
82 		case 'c':
83 			opt_c = 1;
84 			opt_v = 1;
85 			break;
86 		case 'i':
87 			opt_i = 1;
88 			opt_v = 1;
89 			break;
90 		case 'p':
91 			opt_p = 1;
92 			break;
93 		case 's':
94 			opt_s = 1;
95 			break;
96 		case 'S':
97 			opt_S = 1;
98 			opt_v = 1;
99 			break;
100 		case 't':
101 			opt_t = 1;
102 			opt_v = 1;
103 			break;
104 		case 'v':
105 			opt_v = 1;
106 			break;
107 		case 'w':
108 			opt_w = 1;
109 			break;
110 		default:
111 			usage();
112 		}
113 	}
114 	argc -= optind;
115 	argv += optind;
116 
117 	if (argc < 1)
118 		usage();
119 
120 	if ((opt_p && opt_s) || ((opt_p || opt_s) && (opt_c || opt_i || opt_t || opt_v))) {
121 		warnx("-p or -s cannot be used with other options");
122 		usage();
123 	}
124 
125 	if (opt_S && !opt_w) {
126 		warnx("-S require also -w");
127 		usage();
128 	}
129 
130 	for (i = 0; i < argc; i++) {
131 		fd = open(argv[i], (opt_w ? O_RDWR : O_RDONLY) | O_DIRECT);
132 		if (fd < 0 && errno == ENOENT && *argv[i] != '/') {
133 			snprintf(buf, BUFSIZ, "%s%s", _PATH_DEV, argv[i]);
134 			fd = open(buf, O_RDONLY);
135 		}
136 		if (fd < 0) {
137 			warn("%s", argv[i]);
138 			exit(1);
139 		}
140 		error = fstat(fd, &sb);
141 		if (error != 0) {
142 			warn("cannot stat %s", argv[i]);
143 			exitval = 1;
144 			goto out;
145 		}
146 		isreg = S_ISREG(sb.st_mode);
147 		if (isreg) {
148 			mediasize = sb.st_size;
149 			sectorsize = S_BLKSIZE;
150 			fwsectors = 0;
151 			fwheads = 0;
152 			stripesize = sb.st_blksize;
153 			stripeoffset = 0;
154 			if (opt_p || opt_s) {
155 				warnx("-p and -s only operate on physical devices: %s", argv[i]);
156 				goto out;
157 			}
158 		} else {
159 			if (opt_p) {
160 				if (ioctl(fd, DIOCGPHYSPATH, physpath) == 0) {
161 					printf("%s\n", physpath);
162 				} else {
163 					warnx("Failed to determine physpath for: %s", argv[i]);
164 				}
165 				goto out;
166 			}
167 			if (opt_s) {
168 				if (ioctl(fd, DIOCGIDENT, ident) == 0) {
169 					printf("%s\n", ident);
170 				} else {
171 					warnx("Failed to determine serial number for: %s", argv[i]);
172 				}
173 				goto out;
174 			}
175 			error = ioctl(fd, DIOCGMEDIASIZE, &mediasize);
176 			if (error) {
177 				warnx("%s: ioctl(DIOCGMEDIASIZE) failed, probably not a disk.", argv[i]);
178 				exitval = 1;
179 				goto out;
180 			}
181 			error = ioctl(fd, DIOCGSECTORSIZE, &sectorsize);
182 			if (error) {
183 				warnx("%s: ioctl(DIOCGSECTORSIZE) failed, probably not a disk.", argv[i]);
184 				exitval = 1;
185 				goto out;
186 			}
187 			error = ioctl(fd, DIOCGFWSECTORS, &fwsectors);
188 			if (error)
189 				fwsectors = 0;
190 			error = ioctl(fd, DIOCGFWHEADS, &fwheads);
191 			if (error)
192 				fwheads = 0;
193 			error = ioctl(fd, DIOCGSTRIPESIZE, &stripesize);
194 			if (error)
195 				stripesize = 0;
196 			error = ioctl(fd, DIOCGSTRIPEOFFSET, &stripeoffset);
197 			if (error)
198 				stripeoffset = 0;
199 			error = zonecheck(fd, &zone_mode, zone_desc, sizeof(zone_desc));
200 			if (error == 0)
201 				zoned = 1;
202 		}
203 		if (!opt_v) {
204 			printf("%s", argv[i]);
205 			printf("\t%u", sectorsize);
206 			printf("\t%jd", (intmax_t)mediasize);
207 			printf("\t%jd", (intmax_t)mediasize/sectorsize);
208 			printf("\t%jd", (intmax_t)stripesize);
209 			printf("\t%jd", (intmax_t)stripeoffset);
210 			if (fwsectors != 0 && fwheads != 0) {
211 				printf("\t%jd", (intmax_t)mediasize /
212 				    (fwsectors * fwheads * sectorsize));
213 				printf("\t%u", fwheads);
214 				printf("\t%u", fwsectors);
215 			}
216 		} else {
217 			humanize_number(buf, 5, (int64_t)mediasize, "",
218 			    HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
219 			printf("%s\n", argv[i]);
220 			printf("\t%-12u\t# sectorsize\n", sectorsize);
221 			printf("\t%-12jd\t# mediasize in bytes (%s)\n",
222 			    (intmax_t)mediasize, buf);
223 			printf("\t%-12jd\t# mediasize in sectors\n",
224 			    (intmax_t)mediasize/sectorsize);
225 			printf("\t%-12jd\t# stripesize\n", stripesize);
226 			printf("\t%-12jd\t# stripeoffset\n", stripeoffset);
227 			if (fwsectors != 0 && fwheads != 0) {
228 				printf("\t%-12jd\t# Cylinders according to firmware.\n", (intmax_t)mediasize /
229 				    (fwsectors * fwheads * sectorsize));
230 				printf("\t%-12u\t# Heads according to firmware.\n", fwheads);
231 				printf("\t%-12u\t# Sectors according to firmware.\n", fwsectors);
232 			}
233 			if (ioctl(fd, DIOCGIDENT, ident) == 0)
234 				printf("\t%-12s\t# Disk ident.\n", ident);
235 			if (ioctl(fd, DIOCGPHYSPATH, physpath) == 0)
236 				printf("\t%-12s\t# Physical path\n", physpath);
237 			if (zoned != 0)
238 				printf("\t%-12s\t# Zone Mode\n", zone_desc);
239 		}
240 		printf("\n");
241 		if (opt_c)
242 			commandtime(fd, mediasize, sectorsize);
243 		if (opt_t)
244 			speeddisk(fd, mediasize, sectorsize);
245 		if (opt_i)
246 			iopsbench(fd, mediasize, sectorsize);
247 		if (opt_S)
248 			slogbench(fd, isreg, mediasize, sectorsize);
249 out:
250 		close(fd);
251 	}
252 	exit (exitval);
253 }
254 
255 #define MAXTX (8*1024*1024)
256 #define MEGATX (1024*1024)
257 static uint8_t buf[MAXTX];
258 
259 static void
260 rdsect(int fd, off_t blockno, u_int sectorsize)
261 {
262 	int error;
263 
264 	if (lseek(fd, (off_t)blockno * sectorsize, SEEK_SET) == -1)
265 		err(1, "lseek");
266 	error = read(fd, buf, sectorsize);
267 	if (error == -1)
268 		err(1, "read");
269 	if (error != (int)sectorsize)
270 		errx(1, "disk too small for test.");
271 }
272 
273 static void
274 rdmega(int fd)
275 {
276 	int error;
277 
278 	error = read(fd, buf, MEGATX);
279 	if (error == -1)
280 		err(1, "read");
281 	if (error != MEGATX)
282 		errx(1, "disk too small for test.");
283 }
284 
285 static struct timeval tv1, tv2;
286 
287 static void
288 T0(void)
289 {
290 
291 	fflush(stdout);
292 	sync();
293 	sleep(1);
294 	sync();
295 	sync();
296 	gettimeofday(&tv1, NULL);
297 }
298 
299 static double
300 delta_t(void)
301 {
302 	double dt;
303 
304 	gettimeofday(&tv2, NULL);
305 	dt = (tv2.tv_usec - tv1.tv_usec) / 1e6;
306 	dt += (tv2.tv_sec - tv1.tv_sec);
307 
308 	return (dt);
309 }
310 
311 static void
312 TN(int count)
313 {
314 	double dt;
315 
316 	dt = delta_t();
317 	printf("%5d iter in %10.6f sec = %8.3f msec\n",
318 		count, dt, dt * 1000.0 / count);
319 }
320 
321 static void
322 TR(double count)
323 {
324 	double dt;
325 
326 	dt = delta_t();
327 	printf("%8.0f kbytes in %10.6f sec = %8.0f kbytes/sec\n",
328 		count, dt, count / dt);
329 }
330 
331 static void
332 TI(double count)
333 {
334 	double dt;
335 
336 	dt = delta_t();
337 	printf("%8.0f ops in  %10.6f sec = %8.0f IOPS\n",
338 		count, dt, count / dt);
339 }
340 
341 static void
342 TS(u_int size, int count)
343 {
344 	double dt;
345 
346 	dt = delta_t();
347 	printf("%8.1f usec/IO = %8.1f Mbytes/s\n",
348 	    dt * 1000000.0 / count, size * count / dt / (1024 * 1024));
349 }
350 
351 static void
352 speeddisk(int fd, off_t mediasize, u_int sectorsize)
353 {
354 	int bulk, i;
355 	off_t b0, b1, sectorcount, step;
356 
357 	sectorcount = mediasize / sectorsize;
358 	if (sectorcount <= 0)
359 		return;		/* Can't test devices with no sectors */
360 
361 	step = 1ULL << (flsll(sectorcount / (4 * 200)) - 1);
362 	if (step > 16384)
363 		step = 16384;
364 	bulk = mediasize / (1024 * 1024);
365 	if (bulk > 100)
366 		bulk = 100;
367 
368 	printf("Seek times:\n");
369 	printf("\tFull stroke:\t");
370 	b0 = 0;
371 	b1 = sectorcount - step;
372 	T0();
373 	for (i = 0; i < 125; i++) {
374 		rdsect(fd, b0, sectorsize);
375 		b0 += step;
376 		rdsect(fd, b1, sectorsize);
377 		b1 -= step;
378 	}
379 	TN(250);
380 
381 	printf("\tHalf stroke:\t");
382 	b0 = sectorcount / 4;
383 	b1 = b0 + sectorcount / 2;
384 	T0();
385 	for (i = 0; i < 125; i++) {
386 		rdsect(fd, b0, sectorsize);
387 		b0 += step;
388 		rdsect(fd, b1, sectorsize);
389 		b1 += step;
390 	}
391 	TN(250);
392 	printf("\tQuarter stroke:\t");
393 	b0 = sectorcount / 4;
394 	b1 = b0 + sectorcount / 4;
395 	T0();
396 	for (i = 0; i < 250; i++) {
397 		rdsect(fd, b0, sectorsize);
398 		b0 += step;
399 		rdsect(fd, b1, sectorsize);
400 		b1 += step;
401 	}
402 	TN(500);
403 
404 	printf("\tShort forward:\t");
405 	b0 = sectorcount / 2;
406 	T0();
407 	for (i = 0; i < 400; i++) {
408 		rdsect(fd, b0, sectorsize);
409 		b0 += step;
410 	}
411 	TN(400);
412 
413 	printf("\tShort backward:\t");
414 	b0 = sectorcount / 2;
415 	T0();
416 	for (i = 0; i < 400; i++) {
417 		rdsect(fd, b0, sectorsize);
418 		b0 -= step;
419 	}
420 	TN(400);
421 
422 	printf("\tSeq outer:\t");
423 	b0 = 0;
424 	T0();
425 	for (i = 0; i < 2048; i++) {
426 		rdsect(fd, b0, sectorsize);
427 		b0++;
428 	}
429 	TN(2048);
430 
431 	printf("\tSeq inner:\t");
432 	b0 = sectorcount - 2048;
433 	T0();
434 	for (i = 0; i < 2048; i++) {
435 		rdsect(fd, b0, sectorsize);
436 		b0++;
437 	}
438 	TN(2048);
439 
440 	printf("\nTransfer rates:\n");
441 	printf("\toutside:     ");
442 	rdsect(fd, 0, sectorsize);
443 	T0();
444 	for (i = 0; i < bulk; i++) {
445 		rdmega(fd);
446 	}
447 	TR(bulk * 1024);
448 
449 	printf("\tmiddle:      ");
450 	b0 = sectorcount / 2 - bulk * (1024*1024 / sectorsize) / 2 - 1;
451 	rdsect(fd, b0, sectorsize);
452 	T0();
453 	for (i = 0; i < bulk; i++) {
454 		rdmega(fd);
455 	}
456 	TR(bulk * 1024);
457 
458 	printf("\tinside:      ");
459 	b0 = sectorcount - bulk * (1024*1024 / sectorsize) - 1;
460 	rdsect(fd, b0, sectorsize);
461 	T0();
462 	for (i = 0; i < bulk; i++) {
463 		rdmega(fd);
464 	}
465 	TR(bulk * 1024);
466 
467 	printf("\n");
468 	return;
469 }
470 
471 static void
472 commandtime(int fd, off_t mediasize, u_int sectorsize)
473 {
474 	double dtmega, dtsector;
475 	int i;
476 
477 	printf("I/O command overhead:\n");
478 	i = mediasize;
479 	rdsect(fd, 0, sectorsize);
480 	T0();
481 	for (i = 0; i < 10; i++)
482 		rdmega(fd);
483 	dtmega = delta_t();
484 
485 	printf("\ttime to read 10MB block    %10.6f sec\t= %8.3f msec/sector\n",
486 		dtmega, dtmega*100/2048);
487 
488 	rdsect(fd, 0, sectorsize);
489 	T0();
490 	for (i = 0; i < 20480; i++)
491 		rdsect(fd, 0, sectorsize);
492 	dtsector = delta_t();
493 
494 	printf("\ttime to read 20480 sectors %10.6f sec\t= %8.3f msec/sector\n",
495 		dtsector, dtsector*100/2048);
496 	printf("\tcalculated command overhead\t\t\t= %8.3f msec/sector\n",
497 		(dtsector - dtmega)*100/2048);
498 
499 	printf("\n");
500 	return;
501 }
502 
503 static void
504 iops(int fd, off_t mediasize, u_int sectorsize)
505 {
506 	struct aiocb aios[NAIO], *aiop;
507 	ssize_t ret;
508 	off_t sectorcount;
509 	int error, i, queued, completed;
510 
511 	sectorcount = mediasize / sectorsize;
512 
513 	for (i = 0; i < NAIO; i++) {
514 		aiop = &(aios[i]);
515 		bzero(aiop, sizeof(*aiop));
516 		aiop->aio_buf = malloc(sectorsize);
517 		if (aiop->aio_buf == NULL)
518 			err(1, "malloc");
519 	}
520 
521 	T0();
522 	for (i = 0; i < NAIO; i++) {
523 		aiop = &(aios[i]);
524 
525 		aiop->aio_fildes = fd;
526 		aiop->aio_offset = (random() % (sectorcount)) * sectorsize;
527 		aiop->aio_nbytes = sectorsize;
528 
529 		error = aio_read(aiop);
530 		if (error != 0)
531 			err(1, "aio_read");
532 	}
533 
534 	queued = i;
535 	completed = 0;
536 
537 	for (;;) {
538 		ret = aio_waitcomplete(&aiop, NULL);
539 		if (ret < 0)
540 			err(1, "aio_waitcomplete");
541 		if (ret != (ssize_t)sectorsize)
542 			errx(1, "short read");
543 
544 		completed++;
545 
546 		if (delta_t() < 3.0) {
547 			aiop->aio_fildes = fd;
548 			aiop->aio_offset = (random() % (sectorcount)) * sectorsize;
549 			aiop->aio_nbytes = sectorsize;
550 
551 			error = aio_read(aiop);
552 			if (error != 0)
553 				err(1, "aio_read");
554 
555 			queued++;
556 		} else if (completed == queued) {
557 			break;
558 		}
559 	}
560 
561 	TI(completed);
562 
563 	return;
564 }
565 
566 static void
567 iopsbench(int fd, off_t mediasize, u_int sectorsize)
568 {
569 	printf("Asynchronous random reads:\n");
570 
571 	printf("\tsectorsize:  ");
572 	iops(fd, mediasize, sectorsize);
573 
574 	if (sectorsize != 4096) {
575 		printf("\t4 kbytes:    ");
576 		iops(fd, mediasize, 4096);
577 	}
578 
579 	printf("\t32 kbytes:   ");
580 	iops(fd, mediasize, 32 * 1024);
581 
582 	printf("\t128 kbytes:  ");
583 	iops(fd, mediasize, 128 * 1024);
584 
585 	printf("\n");
586 }
587 
588 #define MAXIO (128*1024)
589 #define MAXIOS (MAXTX / MAXIO)
590 
591 static void
592 parwrite(int fd, size_t size, off_t off)
593 {
594 	struct aiocb aios[MAXIOS];
595 	off_t o;
596 	size_t s;
597 	int n, error;
598 	struct aiocb *aiop;
599 
600 	for (n = 0, o = 0; size > MAXIO; n++, size -= s, o += s) {
601 		s = (size >= MAXIO) ? MAXIO : size;
602 		aiop = &aios[n];
603 		bzero(aiop, sizeof(*aiop));
604 		aiop->aio_buf = &buf[o];
605 		aiop->aio_fildes = fd;
606 		aiop->aio_offset = off + o;
607 		aiop->aio_nbytes = s;
608 		error = aio_write(aiop);
609 		if (error != 0)
610 			err(EX_IOERR, "AIO write submit error");
611 	}
612 	error = pwrite(fd, &buf[o], size, off + o);
613 	if (error < 0)
614 		err(EX_IOERR, "Sync write error");
615 	for (; n > 0; n--) {
616 		error = aio_waitcomplete(&aiop, NULL);
617 		if (error < 0)
618 			err(EX_IOERR, "AIO write wait error");
619 	}
620 }
621 
622 static void
623 slogbench(int fd, int isreg, off_t mediasize, u_int sectorsize)
624 {
625 	off_t off;
626 	u_int size;
627 	int error, n, N;
628 
629 	printf("Synchronous random writes:\n");
630 	for (size = sectorsize; size <= MAXTX; size *= 2) {
631 		printf("\t%4.4g kbytes: ", (double)size / 1024);
632 		N = 0;
633 		T0();
634 		do {
635 			for (n = 0; n < 250; n++) {
636 				off = random() % (mediasize / size);
637 				parwrite(fd, size, off * size);
638 				if (isreg)
639 					error = fsync(fd);
640 				else
641 					error = ioctl(fd, DIOCGFLUSH);
642 				if (error < 0)
643 					err(EX_IOERR, "Flush error");
644 			}
645 			N += 250;
646 		} while (delta_t() < 1.0);
647 		TS(size, N);
648 	}
649 }
650 
651 static int
652 zonecheck(int fd, uint32_t *zone_mode, char *zone_str, size_t zone_str_len)
653 {
654 	struct disk_zone_args zone_args;
655 	int error;
656 
657 	bzero(&zone_args, sizeof(zone_args));
658 
659 	zone_args.zone_cmd = DISK_ZONE_GET_PARAMS;
660 	error = ioctl(fd, DIOCZONECMD, &zone_args);
661 
662 	if (error == 0) {
663 		*zone_mode = zone_args.zone_params.disk_params.zone_mode;
664 
665 		switch (*zone_mode) {
666 		case DISK_ZONE_MODE_NONE:
667 			snprintf(zone_str, zone_str_len, "Not_Zoned");
668 			break;
669 		case DISK_ZONE_MODE_HOST_AWARE:
670 			snprintf(zone_str, zone_str_len, "Host_Aware");
671 			break;
672 		case DISK_ZONE_MODE_DRIVE_MANAGED:
673 			snprintf(zone_str, zone_str_len, "Drive_Managed");
674 			break;
675 		case DISK_ZONE_MODE_HOST_MANAGED:
676 			snprintf(zone_str, zone_str_len, "Host_Managed");
677 			break;
678 		default:
679 			snprintf(zone_str, zone_str_len, "Unknown_zone_mode_%u",
680 			    *zone_mode);
681 			break;
682 		}
683 	}
684 	return (error);
685 }
686