1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <strings.h> 29 #include <err.h> 30 #include <errno.h> 31 #include <fcntl.h> 32 #include <kstat.h> 33 #include <limits.h> 34 #include <unistd.h> 35 #include <signal.h> 36 #include <sys/dld.h> 37 #include <sys/ddi.h> 38 39 #include <libdllink.h> 40 #include <libdlflow.h> 41 #include <libdlstat.h> 42 43 /* 44 * x86 <sys/regs> ERR conflicts with <curses.h> ERR. 45 * Include curses.h last. 46 */ 47 #if defined(ERR) 48 #undef ERR 49 #endif 50 #include <curses.h> 51 52 struct flowlist { 53 char flowname[MAXFLOWNAMELEN]; 54 char linkname[MAXLINKNAMELEN]; 55 datalink_id_t linkid; 56 int fd; 57 uint64_t ifspeed; 58 boolean_t first; 59 boolean_t display; 60 pktsum_t prevstats; 61 pktsum_t diffstats; 62 }; 63 64 static int maxx, maxy, redraw = 0; 65 static volatile uint_t handle_resize = 0, handle_break = 0; 66 67 pktsum_t totalstats; 68 struct flowlist *stattable = NULL; 69 static int statentry = -1, maxstatentries = 0; 70 71 #define STATGROWSIZE 16 72 73 /* 74 * Search for flowlist entry in stattable which matches 75 * the flowname and linkide. If no match is found, use 76 * next available slot. If no slots are available, 77 * reallocate table with more slots. 78 * 79 * Return: *flowlist of matching flow 80 * NULL if realloc fails 81 */ 82 83 static struct flowlist * 84 findstat(const char *flowname, datalink_id_t linkid) 85 { 86 int match = 0; 87 struct flowlist *flist; 88 89 /* Look for match in the stattable */ 90 for (match = 0, flist = stattable; 91 match <= statentry; 92 match++, flist++) { 93 94 if (flist == NULL) 95 break; 96 /* match the flowname */ 97 if (flowname != NULL) { 98 if (strncmp(flowname, flist->flowname, MAXFLOWNAMELEN) 99 == NULL) 100 return (flist); 101 /* match the linkid */ 102 } else { 103 if (linkid == flist->linkid) 104 return (flist); 105 } 106 } 107 108 /* 109 * No match found in the table. Store statistics in the next slot. 110 * If necessary, make room for this entry. 111 */ 112 statentry++; 113 if ((maxstatentries == 0) || (maxstatentries == statentry)) { 114 maxstatentries += STATGROWSIZE; 115 stattable = realloc(stattable, 116 maxstatentries * sizeof (struct flowlist)); 117 if (stattable == NULL) { 118 perror("realloc"); 119 return (struct flowlist *)(NULL); 120 } 121 } 122 flist = &stattable[statentry]; 123 bzero(flist, sizeof (struct flowlist)); 124 125 if (flowname != NULL) 126 (void) strncpy(flist->flowname, flowname, MAXFLOWNAMELEN); 127 flist->linkid = linkid; 128 flist->fd = INT32_MAX; 129 130 return (flist); 131 } 132 133 /*ARGSUSED*/ 134 static void 135 print_flow_stats(dladm_handle_t handle, struct flowlist *flist) 136 { 137 struct flowlist *fcurr; 138 double ikbs, okbs; 139 double ipks, opks; 140 double dlt; 141 int fcount; 142 static boolean_t first = B_TRUE; 143 144 if (first) { 145 first = B_FALSE; 146 (void) printw("please wait...\n"); 147 return; 148 } 149 150 for (fcount = 0, fcurr = flist; 151 fcount <= statentry; 152 fcount++, fcurr++) { 153 if (fcurr->flowname && fcurr->display) { 154 dlt = (double)fcurr->diffstats.snaptime/(double)NANOSEC; 155 ikbs = fcurr->diffstats.rbytes * 8 / dlt / 1024; 156 okbs = fcurr->diffstats.obytes * 8 / dlt / 1024; 157 ipks = fcurr->diffstats.ipackets / dlt; 158 opks = fcurr->diffstats.opackets / dlt; 159 (void) printw("%-15.15s", fcurr->flowname); 160 (void) printw("%-10.10s", fcurr->linkname); 161 (void) printw("%9.2f %9.2f %9.2f %9.2f ", 162 ikbs, okbs, ipks, opks); 163 (void) printw("\n"); 164 } 165 } 166 } 167 168 /*ARGSUSED*/ 169 static int 170 flow_kstats(dladm_handle_t handle, dladm_flow_attr_t *attr, void *arg) 171 { 172 kstat_ctl_t *kcp = (kstat_ctl_t *)arg; 173 kstat_t *ksp; 174 struct flowlist *flist; 175 pktsum_t currstats, *prevstats, *diffstats; 176 177 flist = findstat(attr->fa_flowname, attr->fa_linkid); 178 if (flist == NULL) 179 return (DLADM_WALK_CONTINUE); 180 181 flist->display = B_FALSE; 182 prevstats = &flist->prevstats; 183 diffstats = &flist->diffstats; 184 185 (void) dladm_datalink_id2info(handle, attr->fa_linkid, NULL, NULL, 186 NULL, flist->linkname, sizeof (flist->linkname)); 187 188 189 /* lookup kstat entry */ 190 ksp = dladm_kstat_lookup(kcp, NULL, -1, attr->fa_flowname, "flow"); 191 192 if (ksp == NULL) 193 return (DLADM_WALK_CONTINUE); 194 195 /* read packet and byte stats */ 196 dladm_get_stats(kcp, ksp, &currstats); 197 198 if (flist->ifspeed == 0) 199 (void) dladm_kstat_value(ksp, "ifspeed", KSTAT_DATA_UINT64, 200 &flist->ifspeed); 201 202 if (flist->first) { 203 flist->first = B_FALSE; 204 } else { 205 dladm_stats_diff(diffstats, &currstats, prevstats); 206 if (diffstats->snaptime == 0) 207 return (DLADM_WALK_CONTINUE); 208 dladm_stats_total(&totalstats, diffstats, &totalstats); 209 } 210 211 bcopy(&currstats, prevstats, sizeof (pktsum_t)); 212 flist->display = B_TRUE; 213 214 return (DLADM_WALK_CONTINUE); 215 } 216 217 /*ARGSUSED*/ 218 static void 219 print_link_stats(dladm_handle_t handle, struct flowlist *flist) 220 { 221 struct flowlist *fcurr; 222 double ikbs, okbs; 223 double ipks, opks; 224 double util; 225 double dlt; 226 int fcount; 227 static boolean_t first = B_TRUE; 228 229 if (first) { 230 first = B_FALSE; 231 (void) printw("please wait...\n"); 232 return; 233 } 234 235 for (fcount = 0, fcurr = flist; 236 fcount <= statentry; 237 fcount++, fcurr++) { 238 if ((fcurr->linkid != DATALINK_INVALID_LINKID) && 239 fcurr->display) { 240 dlt = (double)fcurr->diffstats.snaptime/(double)NANOSEC; 241 ikbs = (double)fcurr->diffstats.rbytes * 8 / dlt / 1024; 242 okbs = (double)fcurr->diffstats.obytes * 8 / dlt / 1024; 243 ipks = (double)fcurr->diffstats.ipackets / dlt; 244 opks = (double)fcurr->diffstats.opackets / dlt; 245 (void) printw("%-10.10s", fcurr->linkname); 246 (void) printw("%9.2f %9.2f %9.2f %9.2f ", 247 ikbs, okbs, ipks, opks); 248 if (fcurr->ifspeed != 0) 249 util = ((ikbs + okbs) * 1024) * 250 100/ fcurr->ifspeed; 251 else 252 util = (double)0; 253 (void) attron(A_BOLD); 254 (void) printw(" %6.2f", util); 255 (void) attroff(A_BOLD); 256 (void) printw("\n"); 257 } 258 } 259 } 260 261 /* 262 * This function is called through the dladm_walk_datalink_id() walker and 263 * calls the dladm_walk_flow() walker. 264 */ 265 266 /*ARGSUSED*/ 267 static int 268 link_flowstats(dladm_handle_t handle, datalink_id_t linkid, void *arg) 269 { 270 dladm_status_t status; 271 272 status = dladm_walk_flow(flow_kstats, handle, linkid, arg, B_FALSE); 273 if (status == DLADM_STATUS_OK) 274 return (DLADM_WALK_CONTINUE); 275 else 276 return (DLADM_WALK_TERMINATE); 277 } 278 279 /*ARGSUSED*/ 280 static int 281 link_kstats(dladm_handle_t handle, datalink_id_t linkid, void *arg) 282 { 283 kstat_ctl_t *kcp = (kstat_ctl_t *)arg; 284 struct flowlist *flist; 285 pktsum_t currstats, *prevstats, *diffstats; 286 datalink_class_t class; 287 kstat_t *ksp; 288 char dnlink[MAXPATHLEN]; 289 290 /* find the flist entry */ 291 flist = findstat(NULL, linkid); 292 flist->display = B_FALSE; 293 if (flist != NULL) { 294 prevstats = &flist->prevstats; 295 diffstats = &flist->diffstats; 296 } else { 297 return (DLADM_WALK_CONTINUE); 298 } 299 300 if (dladm_datalink_id2info(handle, linkid, NULL, &class, NULL, 301 flist->linkname, sizeof (flist->linkname)) != DLADM_STATUS_OK) 302 return (DLADM_WALK_CONTINUE); 303 304 if (flist->fd == INT32_MAX) { 305 if (class == DATALINK_CLASS_PHYS) { 306 (void) snprintf(dnlink, MAXPATHLEN, "/dev/net/%s", 307 flist->linkname); 308 if ((flist->fd = open(dnlink, O_RDWR)) < 0) 309 return (DLADM_WALK_CONTINUE); 310 } else { 311 flist->fd = -1; 312 } 313 (void) kstat_chain_update(kcp); 314 } 315 316 /* lookup kstat entry */ 317 ksp = dladm_kstat_lookup(kcp, NULL, -1, flist->linkname, "net"); 318 319 if (ksp == NULL) 320 return (DLADM_WALK_CONTINUE); 321 322 /* read packet and byte stats */ 323 dladm_get_stats(kcp, ksp, &currstats); 324 325 if (flist->ifspeed == 0) 326 (void) dladm_kstat_value(ksp, "ifspeed", KSTAT_DATA_UINT64, 327 &flist->ifspeed); 328 329 if (flist->first) { 330 flist->first = B_FALSE; 331 } else { 332 dladm_stats_diff(diffstats, &currstats, prevstats); 333 if (diffstats->snaptime == 0) 334 return (DLADM_WALK_CONTINUE); 335 } 336 337 bcopy(&currstats, prevstats, sizeof (*prevstats)); 338 flist->display = B_TRUE; 339 340 return (DLADM_WALK_CONTINUE); 341 } 342 343 static void 344 closedevnet() 345 { 346 int index = 0; 347 struct flowlist *flist; 348 349 /* Close all open /dev/net/ files */ 350 for (flist = stattable; index <= maxstatentries; index++, flist++) { 351 if (flist->linkid == DATALINK_INVALID_LINKID) 352 break; 353 if (flist->fd != -1 && flist->fd != INT32_MAX) 354 (void) close(flist->fd); 355 } 356 } 357 358 /*ARGSUSED*/ 359 static void 360 sig_break(int s) 361 { 362 handle_break = 1; 363 } 364 365 /*ARGSUSED*/ 366 static void 367 sig_resize(int s) 368 { 369 handle_resize = 1; 370 } 371 372 static void 373 curses_init() 374 { 375 maxx = maxx; /* lint */ 376 maxy = maxy; /* lint */ 377 378 /* Install signal handlers */ 379 (void) signal(SIGINT, sig_break); 380 (void) signal(SIGQUIT, sig_break); 381 (void) signal(SIGTERM, sig_break); 382 (void) signal(SIGWINCH, sig_resize); 383 384 /* Initialize ncurses */ 385 (void) initscr(); 386 (void) cbreak(); 387 (void) noecho(); 388 (void) curs_set(0); 389 timeout(0); 390 getmaxyx(stdscr, maxy, maxx); 391 } 392 393 static void 394 curses_fin() 395 { 396 (void) printw("\n"); 397 (void) curs_set(1); 398 (void) nocbreak(); 399 (void) endwin(); 400 401 free(stattable); 402 } 403 404 static void 405 stat_report(dladm_handle_t handle, kstat_ctl_t *kcp, datalink_id_t linkid, 406 const char *flowname, int opt) 407 { 408 409 double dlt, ikbs, okbs, ipks, opks; 410 411 struct flowlist *fstable = stattable; 412 413 if ((opt != LINK_REPORT) && (opt != FLOW_REPORT)) 414 return; 415 416 /* Handle window resizes */ 417 if (handle_resize) { 418 (void) endwin(); 419 (void) initscr(); 420 (void) cbreak(); 421 (void) noecho(); 422 (void) curs_set(0); 423 timeout(0); 424 getmaxyx(stdscr, maxy, maxx); 425 redraw = 1; 426 handle_resize = 0; 427 } 428 429 /* Print title */ 430 (void) erase(); 431 (void) attron(A_BOLD); 432 (void) move(0, 0); 433 if (opt == FLOW_REPORT) 434 (void) printw("%-15.15s", "Flow"); 435 (void) printw("%-10.10s", "Link"); 436 (void) printw("%9.9s %9.9s %9.9s %9.9s ", 437 "iKb/s", "oKb/s", "iPk/s", "oPk/s"); 438 if (opt == LINK_REPORT) 439 (void) printw(" %6.6s", "%Util"); 440 (void) printw("\n"); 441 (void) attroff(A_BOLD); 442 443 (void) move(2, 0); 444 445 /* Print stats for each link or flow */ 446 bzero(&totalstats, sizeof (totalstats)); 447 if (opt == LINK_REPORT) { 448 /* Display all links */ 449 if (linkid == DATALINK_ALL_LINKID) { 450 (void) dladm_walk_datalink_id(link_kstats, handle, 451 (void *)kcp, DATALINK_CLASS_ALL, 452 DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE); 453 /* Display 1 link */ 454 } else { 455 (void) link_kstats(handle, linkid, kcp); 456 } 457 print_link_stats(handle, fstable); 458 459 } else if (opt == FLOW_REPORT) { 460 /* Display 1 flow */ 461 if (flowname != NULL) { 462 dladm_flow_attr_t fattr; 463 if (dladm_flow_info(handle, flowname, &fattr) != 464 DLADM_STATUS_OK) 465 return; 466 (void) flow_kstats(handle, &fattr, kcp); 467 /* Display all flows on all links */ 468 } else if (linkid == DATALINK_ALL_LINKID) { 469 (void) dladm_walk_datalink_id(link_flowstats, handle, 470 (void *)kcp, DATALINK_CLASS_ALL, 471 DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE); 472 /* Display all flows on a link */ 473 } else if (linkid != DATALINK_INVALID_LINKID) { 474 (void) dladm_walk_flow(flow_kstats, handle, linkid, kcp, 475 B_FALSE); 476 } 477 print_flow_stats(handle, fstable); 478 479 /* Print totals */ 480 (void) attron(A_BOLD); 481 dlt = (double)totalstats.snaptime / (double)NANOSEC; 482 ikbs = totalstats.rbytes / dlt / 1024; 483 okbs = totalstats.obytes / dlt / 1024; 484 ipks = totalstats.ipackets / dlt; 485 opks = totalstats.opackets / dlt; 486 (void) printw("\n%-25.25s", "Totals"); 487 (void) printw("%9.2f %9.2f %9.2f %9.2f ", 488 ikbs, okbs, ipks, opks); 489 (void) attroff(A_BOLD); 490 } 491 492 if (redraw) 493 (void) clearok(stdscr, 1); 494 495 if (refresh() == ERR) 496 return; 497 498 if (redraw) { 499 (void) clearok(stdscr, 0); 500 redraw = 0; 501 } 502 } 503 504 /* Exported functions */ 505 506 /* 507 * Continuously display link or flow statstics using a libcurses 508 * based display. 509 */ 510 511 void 512 dladm_continuous(dladm_handle_t handle, datalink_id_t linkid, 513 const char *flowname, int interval, int opt) 514 { 515 kstat_ctl_t *kcp; 516 517 if ((kcp = kstat_open()) == NULL) { 518 warn("kstat open operation failed"); 519 return; 520 } 521 522 curses_init(); 523 524 for (;;) { 525 526 if (handle_break) 527 break; 528 529 stat_report(handle, kcp, linkid, flowname, opt); 530 531 (void) sleep(max(1, interval)); 532 } 533 534 closedevnet(); 535 curses_fin(); 536 (void) kstat_close(kcp); 537 } 538 539 /* 540 * dladm_kstat_lookup() is a modified version of kstat_lookup which 541 * adds the class as a selector. 542 */ 543 544 kstat_t * 545 dladm_kstat_lookup(kstat_ctl_t *kcp, const char *module, int instance, 546 const char *name, const char *class) 547 { 548 kstat_t *ksp = NULL; 549 550 for (ksp = kcp->kc_chain; ksp != NULL; ksp = ksp->ks_next) { 551 if ((module == NULL || strcmp(ksp->ks_module, module) == 0) && 552 (instance == -1 || ksp->ks_instance == instance) && 553 (name == NULL || strcmp(ksp->ks_name, name) == 0) && 554 (class == NULL || strcmp(ksp->ks_class, class) == 0)) 555 return (ksp); 556 } 557 558 errno = ENOENT; 559 return (NULL); 560 } 561 562 /* 563 * dladm_get_stats() populates the supplied pktsum_t structure with 564 * the input and output packet and byte kstats from the kstat_t 565 * found with dladm_kstat_lookup. 566 */ 567 void 568 dladm_get_stats(kstat_ctl_t *kcp, kstat_t *ksp, pktsum_t *stats) 569 { 570 571 if (kstat_read(kcp, ksp, NULL) == -1) 572 return; 573 574 stats->snaptime = gethrtime(); 575 576 if (dladm_kstat_value(ksp, "ipackets64", KSTAT_DATA_UINT64, 577 &stats->ipackets) < 0) { 578 if (dladm_kstat_value(ksp, "ipackets", KSTAT_DATA_UINT64, 579 &stats->ipackets) < 0) 580 return; 581 } 582 583 if (dladm_kstat_value(ksp, "opackets64", KSTAT_DATA_UINT64, 584 &stats->opackets) < 0) { 585 if (dladm_kstat_value(ksp, "opackets", KSTAT_DATA_UINT64, 586 &stats->opackets) < 0) 587 return; 588 } 589 590 if (dladm_kstat_value(ksp, "rbytes64", KSTAT_DATA_UINT64, 591 &stats->rbytes) < 0) { 592 if (dladm_kstat_value(ksp, "rbytes", KSTAT_DATA_UINT64, 593 &stats->rbytes) < 0) 594 return; 595 } 596 597 if (dladm_kstat_value(ksp, "obytes64", KSTAT_DATA_UINT64, 598 &stats->obytes) < 0) { 599 if (dladm_kstat_value(ksp, "obytes", KSTAT_DATA_UINT64, 600 &stats->obytes) < 0) 601 return; 602 } 603 604 if (dladm_kstat_value(ksp, "ierrors", KSTAT_DATA_UINT32, 605 &stats->ierrors) < 0) { 606 if (dladm_kstat_value(ksp, "ierrors", KSTAT_DATA_UINT64, 607 &stats->ierrors) < 0) 608 return; 609 } 610 611 if (dladm_kstat_value(ksp, "oerrors", KSTAT_DATA_UINT32, 612 &stats->oerrors) < 0) { 613 if (dladm_kstat_value(ksp, "oerrors", KSTAT_DATA_UINT64, 614 &stats->oerrors) < 0) 615 return; 616 } 617 } 618 619 int 620 dladm_kstat_value(kstat_t *ksp, const char *name, uint8_t type, void *buf) 621 { 622 kstat_named_t *knp; 623 624 if ((knp = kstat_data_lookup(ksp, (char *)name)) == NULL) 625 return (-1); 626 627 if (knp->data_type != type) 628 return (-1); 629 630 switch (type) { 631 case KSTAT_DATA_UINT64: 632 *(uint64_t *)buf = knp->value.ui64; 633 break; 634 case KSTAT_DATA_UINT32: 635 *(uint32_t *)buf = knp->value.ui32; 636 break; 637 default: 638 return (-1); 639 } 640 641 return (0); 642 } 643 644 dladm_status_t 645 dladm_get_single_mac_stat(dladm_handle_t handle, datalink_id_t linkid, 646 const char *name, uint8_t type, void *val) 647 { 648 kstat_ctl_t *kcp; 649 char module[DLPI_LINKNAME_MAX]; 650 uint_t instance; 651 char link[DLPI_LINKNAME_MAX]; 652 dladm_status_t status; 653 uint32_t flags, media; 654 kstat_t *ksp; 655 dladm_phys_attr_t dpap; 656 657 if ((kcp = kstat_open()) == NULL) { 658 warn("kstat_open operation failed"); 659 return (-1); 660 } 661 662 if ((status = dladm_datalink_id2info(handle, linkid, &flags, NULL, 663 &media, link, DLPI_LINKNAME_MAX)) != DLADM_STATUS_OK) 664 return (status); 665 666 if (media != DL_ETHER) 667 return (DLADM_STATUS_LINKINVAL); 668 669 status = dladm_phys_info(handle, linkid, &dpap, DLADM_OPT_PERSIST); 670 671 if (status != DLADM_STATUS_OK) 672 return (status); 673 674 status = dladm_parselink(dpap.dp_dev, module, &instance); 675 676 if (status != DLADM_STATUS_OK) 677 return (status); 678 679 /* 680 * The kstat query could fail if the underlying MAC 681 * driver was already detached. 682 */ 683 if ((ksp = kstat_lookup(kcp, module, instance, "mac")) == NULL && 684 (ksp = kstat_lookup(kcp, module, instance, NULL)) == NULL) 685 goto bail; 686 687 if (kstat_read(kcp, ksp, NULL) == -1) 688 goto bail; 689 690 if (dladm_kstat_value(ksp, name, type, val) < 0) 691 goto bail; 692 693 (void) kstat_close(kcp); 694 return (DLADM_STATUS_OK); 695 696 bail: 697 (void) kstat_close(kcp); 698 return (dladm_errno2status(errno)); 699 } 700 701 /* Compute sum of 2 pktsums (s1 = s2 + s3) */ 702 void 703 dladm_stats_total(pktsum_t *s1, pktsum_t *s2, pktsum_t *s3) 704 { 705 s1->rbytes = s2->rbytes + s3->rbytes; 706 s1->ipackets = s2->ipackets + s3->ipackets; 707 s1->ierrors = s2->ierrors + s3->ierrors; 708 s1->obytes = s2->obytes + s3->obytes; 709 s1->opackets = s2->opackets + s3->opackets; 710 s1->oerrors = s2->oerrors + s3->oerrors; 711 s1->snaptime = s2->snaptime; 712 } 713 714 #define DIFF_STAT(s2, s3) ((s2) > (s3) ? (s2 - s3) : 0) 715 716 717 /* Compute differences between 2 pktsums (s1 = s2 - s3) */ 718 void 719 dladm_stats_diff(pktsum_t *s1, pktsum_t *s2, pktsum_t *s3) 720 { 721 s1->rbytes = DIFF_STAT(s2->rbytes, s3->rbytes); 722 s1->ipackets = DIFF_STAT(s2->ipackets, s3->ipackets); 723 s1->ierrors = DIFF_STAT(s2->ierrors, s3->ierrors); 724 s1->obytes = DIFF_STAT(s2->obytes, s3->obytes); 725 s1->opackets = DIFF_STAT(s2->opackets, s3->opackets); 726 s1->oerrors = DIFF_STAT(s2->oerrors, s3->oerrors); 727 s1->snaptime = DIFF_STAT(s2->snaptime, s3->snaptime); 728 } 729