xref: /titanic_41/usr/src/lib/libdladm/common/libdlstat.c (revision bbb1277b6ec1b0daad4e3ed1a2b891d3e2ece2eb)
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 ((status = dladm_datalink_id2info(handle, linkid, &flags, NULL,
658 	    &media, link, DLPI_LINKNAME_MAX)) != DLADM_STATUS_OK)
659 		return (status);
660 
661 	if (media != DL_ETHER)
662 		return (DLADM_STATUS_LINKINVAL);
663 
664 	status = dladm_phys_info(handle, linkid, &dpap, DLADM_OPT_PERSIST);
665 
666 	if (status != DLADM_STATUS_OK)
667 		return (status);
668 
669 	status = dladm_parselink(dpap.dp_dev, module, &instance);
670 
671 	if (status != DLADM_STATUS_OK)
672 		return (status);
673 
674 	if ((kcp = kstat_open()) == NULL) {
675 		warn("kstat_open operation failed");
676 		return (-1);
677 	}
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