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