xref: /dragonfly/usr.bin/systat/altqs.c (revision e5a92d33)
1 /*
2  * Copyright (c) 2003, Trent Nelson, <trent@arpa.com>.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. The name of the author may not be used to endorse or promote products
14  *    derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTIFSTAT_ERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <sys/sysctl.h>
32 #include <net/if.h>
33 #include <net/if_mib.h>
34 
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <err.h>
39 #include <errno.h>
40 
41 #include <net/altq/altq.h>
42 #include <net/altq/altq_cbq.h>
43 #include <net/altq/altq_priq.h>
44 #include <net/altq/altq_hfsc.h>
45 #include <net/altq/altq_fairq.h>
46 
47 #include <net/pf/pfvar.h>
48 
49 #include "systat.h"
50 #include "extern.h"
51 #include "convtbl.h"
52 
53 static SLIST_HEAD(, qcol) qcols;
54 static SLIST_HEAD(, if_stat) curlist;
55 static int pf_fd = -1;
56 static int qccols;
57 static int TopSection1;
58 static int TopSection2;
59 static int TopSection3;
60 
61 struct qcol {
62 	SLIST_ENTRY(qcol) link;
63 	char *qname;
64 	int col;
65 };
66 
67 typedef struct qcol qcol_t;
68 
69 union class_stats {
70 	class_stats_t		cbq;
71 	struct priq_classstats	priq;
72 	struct hfsc_classstats	hfsc;
73 	struct fairq_classstats	fairq;
74 };
75 
76 struct queue_stats {
77 	SLIST_ENTRY(queue_stats) link;
78 	struct pfioc_altq	pa;
79 	struct pfioc_qstats	pq;
80 	qcol_t			*qc;
81 	union class_stats	ostats;
82 	union class_stats	nstats;
83 };
84 
85 typedef struct queue_stats queue_stats_t;
86 
87 struct if_stat {
88 	SLIST_ENTRY(if_stat) link;
89 	SLIST_HEAD(, queue_stats) queues;
90 	char	if_name[IF_NAMESIZE];
91 	struct	ifmibdata if_mib;
92 	struct	timeval tv;
93 	struct	timeval tv_lastchanged;
94 	u_long	if_in_curtraffic;
95 	u_long	if_out_curtraffic;
96 	u_long	if_in_traffic_peak;
97 	u_long	if_out_traffic_peak;
98 	u_int	if_row;			/* Index into ifmib sysctl */
99 	u_int	row;			/* display row (relative) */
100 	u_int	display;
101 };
102 
103 typedef struct if_stat if_stat_t;
104 
105 extern	 u_int curscale;
106 
107 static	 void  load_altqs(void);
108 static	 void  print_altq(if_stat_t *p, queue_stats_t *q);
109 static	 void  right_align_string(if_stat_t *);
110 static	 void  getifmibdata(const int, struct ifmibdata *);
111 static	 void  sort_interface_list(void);
112 static	 u_int getifnum(void);
113 
114 #define IFSTAT_ERR(n, s)	do {					\
115 	putchar('');							\
116 	closealtqs(wnd);						\
117 	err((n), (s));							\
118 } while (0)
119 
120 #define TOPLINE		1
121 #define TOPQSTART	20
122 #define TOPQWIDTH	10
123 
124 WINDOW *
125 openaltqs(void)
126 {
127 	if_stat_t *p = NULL;
128 	u_int	 n = 0, i = 0;
129 
130 	pf_fd = open("/dev/pf", O_RDONLY);
131 
132 	n = getifnum();		/* NOTE: can return < 0 */
133 
134 	SLIST_INIT(&curlist);
135 	SLIST_INIT(&qcols);
136 	for (i = 0; i < n; i++) {
137 		p = (if_stat_t *)calloc(1, sizeof(if_stat_t));
138 		if (p == NULL)
139 			IFSTAT_ERR(1, "out of memory");
140 		SLIST_INSERT_HEAD(&curlist, p, link);
141 		SLIST_INIT(&p->queues);
142 		p->if_row = i+1;
143 		getifmibdata(p->if_row, &p->if_mib);
144 		right_align_string(p);
145 
146 		/*
147 		 * Initially, we only display interfaces that have
148 		 * received some traffic.
149 		 */
150 		if (p->if_mib.ifmd_data.ifi_ibytes != 0)
151 			p->display = 1;
152 	}
153 	load_altqs();
154 
155 	sort_interface_list();
156 
157 	return (subwin(stdscr, LINES-1-5, 0, 5, 0));
158 }
159 
160 void
161 closealtqs(WINDOW *w)
162 {
163 	if_stat_t	*node = NULL;
164 	queue_stats_t	*q;
165 
166 	while (!SLIST_EMPTY(&curlist)) {
167 		node = SLIST_FIRST(&curlist);
168 		SLIST_REMOVE_HEAD(&curlist, link);
169 		while ((q = SLIST_FIRST(&node->queues)) != NULL) {
170 			SLIST_REMOVE_HEAD(&node->queues, link);
171 			free(q);
172 		}
173 		free(node);
174 	}
175 	while (!SLIST_EMPTY(&qcols)) {
176 		qcol_t *qc = SLIST_FIRST(&qcols);
177 		SLIST_REMOVE_HEAD(&qcols, link);
178 		free(qc->qname);
179 		free(qc);
180 	}
181 	qccols = 0;
182 
183 	if (w != NULL) {
184 		wclear(w);
185 		wrefresh(w);
186 		delwin(w);
187 	}
188 
189 	if (pf_fd >= 0) {
190 		close(pf_fd);
191 		pf_fd = -1;
192 	}
193 
194 	return;
195 }
196 
197 void
198 labelaltqs(void)
199 {
200 	wmove(wnd, TOPLINE, 0);
201 	wclrtoeol(wnd);
202 }
203 
204 void
205 showaltqs(void)
206 {
207 	if_stat_t *p = NULL;
208 	queue_stats_t	*q;
209 	qcol_t		*qc;
210 
211 	mvprintw(TopSection1, 0, "        PACKETS");
212 	mvprintw(TopSection2, 0, "        BYTES");
213 	mvprintw(TopSection3, 0, "   DROPS/QLEN");
214 	SLIST_FOREACH(qc, &qcols, link) {
215 		mvprintw(TopSection1, TOPQSTART + TOPQWIDTH * qc->col,
216 			 "%9s", qc->qname);
217 		mvprintw(TopSection2, TOPQSTART + TOPQWIDTH * qc->col,
218 			 "%9s", qc->qname);
219 		mvprintw(TopSection3, TOPQSTART + TOPQWIDTH * qc->col,
220 			 "%9s", qc->qname);
221 	}
222 
223 	SLIST_FOREACH(p, &curlist, link) {
224 		if (p->display == 0)
225 			continue;
226 		mvprintw(TopSection1 + p->row, 0, "%s", p->if_name);
227 		mvprintw(TopSection2 + p->row, 0, "%s", p->if_name);
228 		mvprintw(TopSection3 + p->row, 0, "%s", p->if_name);
229 		SLIST_FOREACH(q, &p->queues, link) {
230 			print_altq(p, q);
231 		}
232 	}
233 }
234 
235 int
236 initaltqs(void)
237 {
238 	TopSection1 = TOPLINE;
239 
240 	return 1;
241 }
242 
243 void
244 fetchaltqs(void)
245 {
246 	struct	if_stat *ifp = NULL;
247 	struct	timeval tv, new_tv, old_tv;
248 	double	elapsed = 0.0;
249 	u_int	new_inb, new_outb, old_inb, old_outb = 0;
250 	u_int	we_need_to_sort_interface_list = 0;
251 
252 	SLIST_FOREACH(ifp, &curlist, link) {
253 		/*
254 		 * Grab a copy of the old input/output values before we
255 		 * call getifmibdata().
256 		 */
257 		old_inb = ifp->if_mib.ifmd_data.ifi_ibytes;
258 		old_outb = ifp->if_mib.ifmd_data.ifi_obytes;
259 		ifp->tv_lastchanged = ifp->if_mib.ifmd_data.ifi_lastchange;
260 
261 		if (gettimeofday(&new_tv, NULL) != 0)
262 			IFSTAT_ERR(2, "error getting time of day");
263 		(void)getifmibdata(ifp->if_row, &ifp->if_mib);
264 
265 
266                 new_inb = ifp->if_mib.ifmd_data.ifi_ibytes;
267                 new_outb = ifp->if_mib.ifmd_data.ifi_obytes;
268 
269 		/* Display interface if it's received some traffic. */
270 		if (new_inb > 0 && old_inb == 0) {
271 			ifp->display = 1;
272 			we_need_to_sort_interface_list++;
273 		}
274 
275 		/*
276 		 * The rest is pretty trivial.  Calculate the new values
277 		 * for our current traffic rates, and while we're there,
278 		 * see if we have new peak rates.
279 		 */
280                 old_tv = ifp->tv;
281                 timersub(&new_tv, &old_tv, &tv);
282                 elapsed = tv.tv_sec + (tv.tv_usec * 1e-6);
283 
284 		ifp->if_in_curtraffic = new_inb - old_inb;
285 		ifp->if_out_curtraffic = new_outb - old_outb;
286 
287 		/*
288 		 * Rather than divide by the time specified on the comm-
289 		 * and line, we divide by ``elapsed'' as this is likely
290 		 * to be more accurate.
291 		 */
292                 ifp->if_in_curtraffic /= elapsed;
293                 ifp->if_out_curtraffic /= elapsed;
294 
295 		if (ifp->if_in_curtraffic > ifp->if_in_traffic_peak)
296 			ifp->if_in_traffic_peak = ifp->if_in_curtraffic;
297 
298 		if (ifp->if_out_curtraffic > ifp->if_out_traffic_peak)
299 			ifp->if_out_traffic_peak = ifp->if_out_curtraffic;
300 
301 		ifp->tv.tv_sec = new_tv.tv_sec;
302 		ifp->tv.tv_usec = new_tv.tv_usec;
303 
304 	}
305 
306 	load_altqs();
307 
308 	if (we_need_to_sort_interface_list)
309 		sort_interface_list();
310 
311 	return;
312 }
313 
314 /*
315  * We want to right justify our interface names against the first column
316  * (first sixteen or so characters), so we need to do some alignment.
317  */
318 static void
319 right_align_string(if_stat_t *ifp)
320 {
321 	int	 str_len = 0, pad_len = 0;
322 	char	*newstr = NULL, *ptr = NULL;
323 
324 	if (ifp == NULL || ifp->if_mib.ifmd_name == NULL)
325 		return;
326 	else {
327 		/* string length + '\0' */
328 		str_len = strlen(ifp->if_mib.ifmd_name)+1;
329 		pad_len = IF_NAMESIZE-(str_len);
330 
331 		newstr = ifp->if_name;
332 		ptr = newstr + pad_len;
333 		(void)memset((void *)newstr, (int)' ', IF_NAMESIZE);
334 		(void)strncpy(ptr, (const char *)&ifp->if_mib.ifmd_name,
335 			      str_len);
336 	}
337 
338 	return;
339 }
340 
341 /*
342  * This function iterates through our list of interfaces, identifying
343  * those that are to be displayed (ifp->display = 1).  For each interf-
344  * rface that we're displaying, we generate an appropriate position for
345  * it on the screen (ifp->row).
346  *
347  * This function is called any time a change is made to an interface's
348  * ``display'' state.
349  */
350 void
351 sort_interface_list(void)
352 {
353 	if_stat_t *ifp;
354 	u_int y;
355 
356 	y = 1;
357 	SLIST_FOREACH(ifp, &curlist, link) {
358 		if (ifp->display)
359 			ifp->row = ++y;
360 	}
361 	TopSection2 = TopSection1 + y + 4;
362 	TopSection3 = TopSection2 + y + 4;
363 }
364 
365 static
366 unsigned int
367 getifnum(void)
368 {
369 	u_int	data    = 0;
370 	size_t	datalen = 0;
371 	static	int name[] = { CTL_NET,
372 			       PF_LINK,
373 			       NETLINK_GENERIC,
374 			       IFMIB_SYSTEM,
375 			       IFMIB_IFCOUNT };
376 
377 	datalen = sizeof(data);
378 	if (sysctl(name, 5, (void *)&data, (size_t *)&datalen, NULL,
379 	    (size_t)0) != 0)
380 		IFSTAT_ERR(1, "sysctl error");
381 	return data;
382 }
383 
384 static void
385 getifmibdata(int row, struct ifmibdata *data)
386 {
387 	size_t	datalen = 0;
388 	static	int name[] = { CTL_NET,
389 			       PF_LINK,
390 			       NETLINK_GENERIC,
391 			       IFMIB_IFDATA,
392 			       0,
393 			       IFDATA_GENERAL };
394 	datalen = sizeof(*data);
395 	name[4] = row;
396 
397 	if ((sysctl(name, 6, (void *)data, (size_t *)&datalen, NULL,
398 	    (size_t)0) != 0) && (errno != ENOENT))
399 		IFSTAT_ERR(2, "sysctl error getting interface data");
400 }
401 
402 static void
403 load_altqs(void)
404 {
405 	struct pfioc_altq pa;
406 	struct pfioc_qstats pq;
407 	if_stat_t *p;
408 	queue_stats_t *q;
409 	qcol_t *qc;
410 	int i;
411 	int n;
412 
413 	bzero(&pa, sizeof(pa));
414 	bzero(&pq, sizeof(pq));
415 
416 	if (ioctl(pf_fd, DIOCGETALTQS, &pa))
417 		return;
418 	n = pa.nr;
419 	for (i = 0; i < n; ++i) {
420 		pa.nr = i;
421 		if (ioctl(pf_fd, DIOCGETALTQ, &pa))
422 			return;
423 		if (pa.altq.qid <= 0)
424 			continue;
425 
426 		SLIST_FOREACH(p, &curlist, link) {
427 			if (strcmp(pa.altq.ifname, p->if_mib.ifmd_name) == 0)
428 				break;
429 		}
430 		if (p == NULL)
431 			continue;
432 		SLIST_FOREACH(q, &p->queues, link) {
433 			if (strcmp(pa.altq.qname, q->pa.altq.qname) == 0)
434 				break;
435 		}
436 		if (q == NULL) {
437 			q = calloc(1, sizeof(*q));
438 			q->pa = pa;
439 			SLIST_INSERT_HEAD(&p->queues, q, link);
440 		} else {
441 			q->pa.ticket = pa.ticket;
442 		}
443 		q->ostats = q->nstats;
444 		q->pq.nr = i;
445 		q->pq.ticket = q->pa.ticket;
446 		q->pq.buf = &q->nstats;
447 		q->pq.nbytes = sizeof(q->nstats);
448 		if (ioctl(pf_fd, DIOCGETQSTATS, &q->pq) < 0) {
449 			SLIST_REMOVE(&p->queues, q, queue_stats, link);
450 			free(q);
451 		}
452 		SLIST_FOREACH(qc, &qcols, link) {
453 			if (strcmp(q->pa.altq.qname, qc->qname) == 0)
454 				break;
455 		}
456 		if (qc == NULL) {
457 			qc = calloc(1, sizeof(*qc));
458 			qc->qname = strdup(q->pa.altq.qname);
459 			qc->col = qccols++;
460 			SLIST_INSERT_HEAD(&qcols, qc, link);
461 		}
462 		q->qc = qc;
463 	}
464 }
465 
466 static
467 void
468 print_altq(if_stat_t *p, queue_stats_t *q)
469 {
470 	uint64_t xmit_pkts;
471 	uint64_t xmit_bytes;
472 	uint64_t drop_pkts;
473 	uint64_t drop_bytes __unused;
474 	uint64_t qlen;
475 
476 	switch(q->pa.altq.scheduler) {
477 	case ALTQT_CBQ:
478 		xmit_pkts = q->nstats.cbq.xmit_cnt.packets;
479 		xmit_bytes = q->nstats.cbq.xmit_cnt.bytes;
480 		drop_pkts = q->nstats.cbq.drop_cnt.packets;
481 		drop_bytes = q->nstats.cbq.drop_cnt.bytes;
482 		xmit_pkts -= q->ostats.cbq.xmit_cnt.packets;
483 		xmit_bytes -= q->ostats.cbq.xmit_cnt.bytes;
484 		drop_pkts -= q->ostats.cbq.drop_cnt.packets;
485 		drop_bytes -= q->ostats.cbq.drop_cnt.bytes;
486 		qlen = 0;
487 		break;
488 	case ALTQT_PRIQ:
489 		xmit_pkts = q->nstats.priq.xmitcnt.packets;
490 		xmit_bytes = q->nstats.priq.xmitcnt.bytes;
491 		drop_pkts = q->nstats.priq.dropcnt.packets;
492 		drop_bytes = q->nstats.priq.dropcnt.bytes;
493 		xmit_pkts -= q->ostats.priq.xmitcnt.packets;
494 		xmit_bytes -= q->ostats.priq.xmitcnt.bytes;
495 		drop_pkts -= q->ostats.priq.dropcnt.packets;
496 		drop_bytes -= q->ostats.priq.dropcnt.bytes;
497 		qlen = q->nstats.priq.qlength;
498 		break;
499 	case ALTQT_HFSC:
500 		xmit_pkts = q->nstats.hfsc.xmit_cnt.packets;
501 		xmit_bytes = q->nstats.hfsc.xmit_cnt.bytes;
502 		drop_pkts = q->nstats.hfsc.drop_cnt.packets;
503 		drop_bytes = q->nstats.hfsc.drop_cnt.bytes;
504 		xmit_pkts -= q->ostats.hfsc.xmit_cnt.packets;
505 		xmit_bytes -= q->ostats.hfsc.xmit_cnt.bytes;
506 		drop_pkts -= q->ostats.hfsc.drop_cnt.packets;
507 		drop_bytes -= q->ostats.hfsc.drop_cnt.bytes;
508 		qlen = q->nstats.hfsc.qlength;
509 		break;
510 	case ALTQT_FAIRQ:
511 		xmit_pkts = q->nstats.fairq.xmit_cnt.packets;
512 		xmit_bytes = q->nstats.fairq.xmit_cnt.bytes;
513 		drop_pkts = q->nstats.fairq.drop_cnt.packets;
514 		drop_bytes = q->nstats.fairq.drop_cnt.bytes;
515 		xmit_pkts -= q->ostats.fairq.xmit_cnt.packets;
516 		xmit_bytes -= q->ostats.fairq.xmit_cnt.bytes;
517 		drop_pkts -= q->ostats.fairq.drop_cnt.packets;
518 		drop_bytes -= q->ostats.fairq.drop_cnt.bytes;
519 		qlen = q->nstats.fairq.qlength;
520 		break;
521 	default:
522 		xmit_pkts = 0;
523 		xmit_bytes = 0;
524 		drop_pkts = 0;
525 		drop_bytes = 0;
526 		qlen = 0;
527 		break;
528 	}
529 	if (xmit_pkts == 0)
530 		mvprintw(TopSection1 + p->row,
531 			 TOPQSTART + q->qc->col * TOPQWIDTH - 1,
532 			 "%10s", "");
533 	else
534 		mvprintw(TopSection1 + p->row,
535 			 TOPQSTART + q->qc->col * TOPQWIDTH - 1,
536 			 "%10jd",  (intmax_t)xmit_pkts);
537 
538 	if (xmit_bytes == 0)
539 		mvprintw(TopSection2 + p->row,
540 			 TOPQSTART + q->qc->col * TOPQWIDTH - 1,
541 			 "%10s", "");
542 	else
543 		mvprintw(TopSection2 + p->row,
544 			 TOPQSTART + q->qc->col * TOPQWIDTH - 1,
545 			 "%10jd",  (intmax_t)xmit_bytes);
546 	if (drop_pkts)
547 		mvprintw(TopSection3 + p->row,
548 			 TOPQSTART + q->qc->col * TOPQWIDTH - 1,
549 			 "%10jd",  (intmax_t)drop_pkts);
550 	else if (qlen)
551 		mvprintw(TopSection3 + p->row,
552 			 TOPQSTART + q->qc->col * TOPQWIDTH - 1,
553 			 "%9jdQ",  (intmax_t)qlen);
554 	else
555 		mvprintw(TopSection3 + p->row,
556 			 TOPQSTART + q->qc->col * TOPQWIDTH - 1,
557 			 "%10s", "");
558 }
559 
560 int
561 cmdaltqs(const char *cmd, char *args)
562 {
563 	int	retval = 0;
564 
565 	retval = ifcmd(cmd, args);
566 	/* ifcmd() returns 1 on success */
567 	if (retval == 1) {
568 		showaltqs();
569 		refresh();
570 	}
571 
572 	return retval;
573 }
574