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