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, §orsize); 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