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