1/*	$OpenBSD: pfctl_qstats.c,v 1.30 2004/04/27 21:47:32 kjc Exp $ */
2
3/*
4 * Copyright (c) Henning Brauer <henning@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/cdefs.h>
20#define PFIOC_USE_LATEST
21
22#include <sys/types.h>
23#include <sys/ioctl.h>
24#include <sys/socket.h>
25
26#include <net/if.h>
27#include <netinet/in.h>
28#include <net/pfvar.h>
29#include <arpa/inet.h>
30
31#include <err.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <unistd.h>
36
37#include <net/altq/altq.h>
38#include <net/altq/altq_cbq.h>
39#include <net/altq/altq_codel.h>
40#include <net/altq/altq_priq.h>
41#include <net/altq/altq_hfsc.h>
42#include <net/altq/altq_fairq.h>
43
44#include "pfctl.h"
45#include "pfctl_parser.h"
46
47union class_stats {
48	class_stats_t		cbq_stats;
49	struct priq_classstats	priq_stats;
50	struct hfsc_classstats	hfsc_stats;
51	struct fairq_classstats fairq_stats;
52	struct codel_ifstats	codel_stats;
53};
54
55#define AVGN_MAX	8
56#define STAT_INTERVAL	5
57
58struct queue_stats {
59	union class_stats	 data;
60	int			 avgn;
61	double			 avg_bytes;
62	double			 avg_packets;
63	u_int64_t		 prev_bytes;
64	u_int64_t		 prev_packets;
65};
66
67struct pf_altq_node {
68	struct pf_altq		 altq;
69	struct pf_altq_node	*next;
70	struct pf_altq_node	*children;
71	struct queue_stats	 qstats;
72};
73
74int			 pfctl_update_qstats(int, struct pf_altq_node **);
75void			 pfctl_insert_altq_node(struct pf_altq_node **,
76			    const struct pf_altq, const struct queue_stats);
77struct pf_altq_node	*pfctl_find_altq_node(struct pf_altq_node *,
78			    const char *, const char *);
79void			 pfctl_print_altq_node(int, const struct pf_altq_node *,
80			    unsigned, int);
81void			 print_cbqstats(struct queue_stats);
82void			 print_codelstats(struct queue_stats);
83void			 print_priqstats(struct queue_stats);
84void			 print_hfscstats(struct queue_stats);
85void			 print_fairqstats(struct queue_stats);
86void			 pfctl_free_altq_node(struct pf_altq_node *);
87void			 pfctl_print_altq_nodestat(int,
88			    const struct pf_altq_node *);
89
90void			 update_avg(struct pf_altq_node *);
91
92int
93pfctl_show_altq(int dev, const char *iface, int opts, int verbose2)
94{
95	struct pf_altq_node	*root = NULL, *node;
96	int			 nodes, dotitle = (opts & PF_OPT_SHOWALL);
97
98#ifdef __FreeBSD__
99	if (!altqsupport)
100		return (-1);
101#endif
102
103	if ((nodes = pfctl_update_qstats(dev, &root)) < 0)
104		return (-1);
105
106	if (nodes == 0)
107		printf("No queue in use\n");
108	for (node = root; node != NULL; node = node->next) {
109		if (iface != NULL && strcmp(node->altq.ifname, iface))
110			continue;
111		if (dotitle) {
112			pfctl_print_title("ALTQ:");
113			dotitle = 0;
114		}
115		pfctl_print_altq_node(dev, node, 0, opts);
116	}
117
118	while (verbose2 && nodes > 0) {
119		printf("\n");
120		fflush(stdout);
121		sleep(STAT_INTERVAL);
122		if ((nodes = pfctl_update_qstats(dev, &root)) == -1)
123			return (-1);
124		for (node = root; node != NULL; node = node->next) {
125			if (iface != NULL && strcmp(node->altq.ifname, iface))
126				continue;
127#ifdef __FreeBSD__
128			if (node->altq.local_flags & PFALTQ_FLAG_IF_REMOVED)
129				continue;
130#endif
131			pfctl_print_altq_node(dev, node, 0, opts);
132		}
133	}
134	pfctl_free_altq_node(root);
135	return (0);
136}
137
138int
139pfctl_update_qstats(int dev, struct pf_altq_node **root)
140{
141	struct pf_altq_node	*node;
142	struct pfioc_altq	 pa;
143	struct pfioc_qstats	 pq;
144	u_int32_t		 mnr, nr;
145	struct queue_stats	 qstats;
146	static	u_int32_t	 last_ticket;
147
148	memset(&pa, 0, sizeof(pa));
149	memset(&pq, 0, sizeof(pq));
150	memset(&qstats, 0, sizeof(qstats));
151	pa.version = PFIOC_ALTQ_VERSION;
152	if (ioctl(dev, DIOCGETALTQS, &pa)) {
153		warn("DIOCGETALTQS");
154		return (-1);
155	}
156
157	/* if a new set is found, start over */
158	if (pa.ticket != last_ticket && *root != NULL) {
159		pfctl_free_altq_node(*root);
160		*root = NULL;
161	}
162	last_ticket = pa.ticket;
163
164	mnr = pa.nr;
165	for (nr = 0; nr < mnr; ++nr) {
166		pa.nr = nr;
167		if (ioctl(dev, DIOCGETALTQ, &pa)) {
168			warn("DIOCGETALTQ");
169			return (-1);
170		}
171#ifdef __FreeBSD__
172		if ((pa.altq.qid > 0 || pa.altq.scheduler == ALTQT_CODEL) &&
173		    !(pa.altq.local_flags & PFALTQ_FLAG_IF_REMOVED)) {
174#else
175		if (pa.altq.qid > 0) {
176#endif
177			pq.nr = nr;
178			pq.ticket = pa.ticket;
179			pq.buf = &qstats.data;
180			pq.nbytes = sizeof(qstats.data);
181			pq.version = altq_stats_version(pa.altq.scheduler);
182			if (ioctl(dev, DIOCGETQSTATS, &pq)) {
183				warn("DIOCGETQSTATS");
184				return (-1);
185			}
186			if ((node = pfctl_find_altq_node(*root, pa.altq.qname,
187			    pa.altq.ifname)) != NULL) {
188				memcpy(&node->qstats.data, &qstats.data,
189				    sizeof(qstats.data));
190				update_avg(node);
191			} else {
192				pfctl_insert_altq_node(root, pa.altq, qstats);
193			}
194		}
195#ifdef __FreeBSD__
196		else if (pa.altq.local_flags & PFALTQ_FLAG_IF_REMOVED) {
197			memset(&qstats.data, 0, sizeof(qstats.data));
198			if ((node = pfctl_find_altq_node(*root, pa.altq.qname,
199			    pa.altq.ifname)) != NULL) {
200				memcpy(&node->qstats.data, &qstats.data,
201				    sizeof(qstats.data));
202				update_avg(node);
203			} else {
204				pfctl_insert_altq_node(root, pa.altq, qstats);
205			}
206		}
207#endif
208	}
209	return (mnr);
210}
211
212void
213pfctl_insert_altq_node(struct pf_altq_node **root,
214    const struct pf_altq altq, const struct queue_stats qstats)
215{
216	struct pf_altq_node	*node;
217
218	node = calloc(1, sizeof(struct pf_altq_node));
219	if (node == NULL)
220		err(1, "pfctl_insert_altq_node: calloc");
221	memcpy(&node->altq, &altq, sizeof(struct pf_altq));
222	memcpy(&node->qstats, &qstats, sizeof(qstats));
223	node->next = node->children = NULL;
224
225	if (*root == NULL)
226		*root = node;
227	else if (!altq.parent[0]) {
228		struct pf_altq_node	*prev = *root;
229
230		while (prev->next != NULL)
231			prev = prev->next;
232		prev->next = node;
233	} else {
234		struct pf_altq_node	*parent;
235
236		parent = pfctl_find_altq_node(*root, altq.parent, altq.ifname);
237		if (parent == NULL)
238			errx(1, "parent %s not found", altq.parent);
239		if (parent->children == NULL)
240			parent->children = node;
241		else {
242			struct pf_altq_node *prev = parent->children;
243
244			while (prev->next != NULL)
245				prev = prev->next;
246			prev->next = node;
247		}
248	}
249	update_avg(node);
250}
251
252struct pf_altq_node *
253pfctl_find_altq_node(struct pf_altq_node *root, const char *qname,
254    const char *ifname)
255{
256	struct pf_altq_node	*node, *child;
257
258	for (node = root; node != NULL; node = node->next) {
259		if (!strcmp(node->altq.qname, qname)
260		    && !(strcmp(node->altq.ifname, ifname)))
261			return (node);
262		if (node->children != NULL) {
263			child = pfctl_find_altq_node(node->children, qname,
264			    ifname);
265			if (child != NULL)
266				return (child);
267		}
268	}
269	return (NULL);
270}
271
272void
273pfctl_print_altq_node(int dev, const struct pf_altq_node *node,
274    unsigned int level, int opts)
275{
276	const struct pf_altq_node	*child;
277
278	if (node == NULL)
279		return;
280
281	print_altq(&node->altq, level, NULL, NULL);
282
283	if (node->children != NULL) {
284		printf("{");
285		for (child = node->children; child != NULL;
286		    child = child->next) {
287			printf("%s", child->altq.qname);
288			if (child->next != NULL)
289				printf(", ");
290		}
291		printf("}");
292	}
293	printf("\n");
294
295	if (opts & PF_OPT_VERBOSE)
296		pfctl_print_altq_nodestat(dev, node);
297
298	if (opts & PF_OPT_DEBUG)
299		printf("  [ qid=%u ifname=%s ifbandwidth=%s ]\n",
300		    node->altq.qid, node->altq.ifname,
301		    rate2str((double)(node->altq.ifbandwidth)));
302
303	for (child = node->children; child != NULL;
304	    child = child->next)
305		pfctl_print_altq_node(dev, child, level + 1, opts);
306}
307
308void
309pfctl_print_altq_nodestat(int dev, const struct pf_altq_node *a)
310{
311	if (a->altq.qid == 0 && a->altq.scheduler != ALTQT_CODEL)
312		return;
313
314#ifdef __FreeBSD__
315	if (a->altq.local_flags & PFALTQ_FLAG_IF_REMOVED)
316		return;
317#endif
318	switch (a->altq.scheduler) {
319	case ALTQT_CBQ:
320		print_cbqstats(a->qstats);
321		break;
322	case ALTQT_PRIQ:
323		print_priqstats(a->qstats);
324		break;
325	case ALTQT_HFSC:
326		print_hfscstats(a->qstats);
327		break;
328	case ALTQT_FAIRQ:
329		print_fairqstats(a->qstats);
330		break;
331	case ALTQT_CODEL:
332		print_codelstats(a->qstats);
333		break;
334	}
335}
336
337void
338print_cbqstats(struct queue_stats cur)
339{
340	printf("  [ pkts: %10llu  bytes: %10llu  "
341	    "dropped pkts: %6llu bytes: %6llu ]\n",
342	    (unsigned long long)cur.data.cbq_stats.xmit_cnt.packets,
343	    (unsigned long long)cur.data.cbq_stats.xmit_cnt.bytes,
344	    (unsigned long long)cur.data.cbq_stats.drop_cnt.packets,
345	    (unsigned long long)cur.data.cbq_stats.drop_cnt.bytes);
346	printf("  [ qlength: %3d/%3d  borrows: %6u  suspends: %6u ]\n",
347	    cur.data.cbq_stats.qcnt, cur.data.cbq_stats.qmax,
348	    cur.data.cbq_stats.borrows, cur.data.cbq_stats.delays);
349
350	if (cur.avgn < 2)
351		return;
352
353	printf("  [ measured: %7.1f packets/s, %s/s ]\n",
354	    cur.avg_packets / STAT_INTERVAL,
355	    rate2str((8 * cur.avg_bytes) / STAT_INTERVAL));
356}
357
358void
359print_codelstats(struct queue_stats cur)
360{
361	printf("  [ pkts: %10llu  bytes: %10llu  "
362	    "dropped pkts: %6llu bytes: %6llu ]\n",
363	    (unsigned long long)cur.data.codel_stats.cl_xmitcnt.packets,
364	    (unsigned long long)cur.data.codel_stats.cl_xmitcnt.bytes,
365	    (unsigned long long)cur.data.codel_stats.cl_dropcnt.packets +
366	    cur.data.codel_stats.stats.drop_cnt.packets,
367	    (unsigned long long)cur.data.codel_stats.cl_dropcnt.bytes +
368	    cur.data.codel_stats.stats.drop_cnt.bytes);
369	printf("  [ qlength: %3d/%3d ]\n",
370	    cur.data.codel_stats.qlength, cur.data.codel_stats.qlimit);
371
372	if (cur.avgn < 2)
373		return;
374
375	printf("  [ measured: %7.1f packets/s, %s/s ]\n",
376	    cur.avg_packets / STAT_INTERVAL,
377	    rate2str((8 * cur.avg_bytes) / STAT_INTERVAL));
378}
379
380void
381print_priqstats(struct queue_stats cur)
382{
383	printf("  [ pkts: %10llu  bytes: %10llu  "
384	    "dropped pkts: %6llu bytes: %6llu ]\n",
385	    (unsigned long long)cur.data.priq_stats.xmitcnt.packets,
386	    (unsigned long long)cur.data.priq_stats.xmitcnt.bytes,
387	    (unsigned long long)cur.data.priq_stats.dropcnt.packets,
388	    (unsigned long long)cur.data.priq_stats.dropcnt.bytes);
389	printf("  [ qlength: %3d/%3d ]\n",
390	    cur.data.priq_stats.qlength, cur.data.priq_stats.qlimit);
391
392	if (cur.avgn < 2)
393		return;
394
395	printf("  [ measured: %7.1f packets/s, %s/s ]\n",
396	    cur.avg_packets / STAT_INTERVAL,
397	    rate2str((8 * cur.avg_bytes) / STAT_INTERVAL));
398}
399
400void
401print_hfscstats(struct queue_stats cur)
402{
403	printf("  [ pkts: %10llu  bytes: %10llu  "
404	    "dropped pkts: %6llu bytes: %6llu ]\n",
405	    (unsigned long long)cur.data.hfsc_stats.xmit_cnt.packets,
406	    (unsigned long long)cur.data.hfsc_stats.xmit_cnt.bytes,
407	    (unsigned long long)cur.data.hfsc_stats.drop_cnt.packets,
408	    (unsigned long long)cur.data.hfsc_stats.drop_cnt.bytes);
409	printf("  [ qlength: %3d/%3d ]\n",
410	    cur.data.hfsc_stats.qlength, cur.data.hfsc_stats.qlimit);
411
412	if (cur.avgn < 2)
413		return;
414
415	printf("  [ measured: %7.1f packets/s, %s/s ]\n",
416	    cur.avg_packets / STAT_INTERVAL,
417	    rate2str((8 * cur.avg_bytes) / STAT_INTERVAL));
418}
419
420void
421print_fairqstats(struct queue_stats cur)
422{
423	printf("  [ pkts: %10llu  bytes: %10llu  "
424	    "dropped pkts: %6llu bytes: %6llu ]\n",
425	    (unsigned long long)cur.data.fairq_stats.xmit_cnt.packets,
426	    (unsigned long long)cur.data.fairq_stats.xmit_cnt.bytes,
427	    (unsigned long long)cur.data.fairq_stats.drop_cnt.packets,
428	    (unsigned long long)cur.data.fairq_stats.drop_cnt.bytes);
429	printf("  [ qlength: %3d/%3d ]\n",
430	    cur.data.fairq_stats.qlength, cur.data.fairq_stats.qlimit);
431
432	if (cur.avgn < 2)
433		return;
434
435	printf("  [ measured: %7.1f packets/s, %s/s ]\n",
436	    cur.avg_packets / STAT_INTERVAL,
437	    rate2str((8 * cur.avg_bytes) / STAT_INTERVAL));
438}
439
440void
441pfctl_free_altq_node(struct pf_altq_node *node)
442{
443	while (node != NULL) {
444		struct pf_altq_node	*prev;
445
446		if (node->children != NULL)
447			pfctl_free_altq_node(node->children);
448		prev = node;
449		node = node->next;
450		free(prev);
451	}
452}
453
454void
455update_avg(struct pf_altq_node *a)
456{
457	struct queue_stats	*qs;
458	u_int64_t		 b, p;
459	int			 n;
460
461	if (a->altq.qid == 0 && a->altq.scheduler != ALTQT_CODEL)
462		return;
463
464	qs = &a->qstats;
465	n = qs->avgn;
466
467	switch (a->altq.scheduler) {
468	case ALTQT_CBQ:
469		b = qs->data.cbq_stats.xmit_cnt.bytes;
470		p = qs->data.cbq_stats.xmit_cnt.packets;
471		break;
472	case ALTQT_PRIQ:
473		b = qs->data.priq_stats.xmitcnt.bytes;
474		p = qs->data.priq_stats.xmitcnt.packets;
475		break;
476	case ALTQT_HFSC:
477		b = qs->data.hfsc_stats.xmit_cnt.bytes;
478		p = qs->data.hfsc_stats.xmit_cnt.packets;
479		break;
480	case ALTQT_FAIRQ:
481		b = qs->data.fairq_stats.xmit_cnt.bytes;
482		p = qs->data.fairq_stats.xmit_cnt.packets;
483		break;
484	case ALTQT_CODEL:
485		b = qs->data.codel_stats.cl_xmitcnt.bytes;
486		p = qs->data.codel_stats.cl_xmitcnt.packets;
487		break;
488	default:
489		b = 0;
490		p = 0;
491		break;
492	}
493
494	if (n == 0) {
495		qs->prev_bytes = b;
496		qs->prev_packets = p;
497		qs->avgn++;
498		return;
499	}
500
501	if (b >= qs->prev_bytes)
502		qs->avg_bytes = ((qs->avg_bytes * (n - 1)) +
503		    (b - qs->prev_bytes)) / n;
504
505	if (p >= qs->prev_packets)
506		qs->avg_packets = ((qs->avg_packets * (n - 1)) +
507		    (p - qs->prev_packets)) / n;
508
509	qs->prev_bytes = b;
510	qs->prev_packets = p;
511	if (n < AVGN_MAX)
512		qs->avgn++;
513}
514