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