/* $OpenBSD: pfctl_qstats.c,v 1.30 2004/04/27 21:47:32 kjc Exp $ */ /* * Copyright (c) Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl.h" #include "pfctl_parser.h" union class_stats { class_stats_t cbq_stats; struct priq_classstats priq_stats; struct hfsc_classstats hfsc_stats; }; #define AVGN_MAX 8 #define STAT_INTERVAL 5 struct queue_stats { union class_stats data; int avgn; double avg_bytes; double avg_packets; u_int64_t prev_bytes; u_int64_t prev_packets; }; struct pf_altq_node { struct pf_altq altq; struct pf_altq_node *next; struct pf_altq_node *children; struct queue_stats qstats; }; int pfctl_update_qstats(int, struct pf_altq_node **); void pfctl_insert_altq_node(struct pf_altq_node **, const struct pf_altq, const struct queue_stats); struct pf_altq_node *pfctl_find_altq_node(struct pf_altq_node *, const char *, const char *); void pfctl_print_altq_node(int, const struct pf_altq_node *, unsigned, int); void print_cbqstats(struct queue_stats); void print_priqstats(struct queue_stats); void print_hfscstats(struct queue_stats); void pfctl_free_altq_node(struct pf_altq_node *); void pfctl_print_altq_nodestat(int, const struct pf_altq_node *); void update_avg(struct pf_altq_node *); int pfctl_show_altq(int dev, const char *iface, int opts, int verbose2) { struct pf_altq_node *root = NULL, *node; int nodes, dotitle = (opts & PF_OPT_SHOWALL); #ifdef __FreeBSD__ if (!altqsupport) return (-1); #endif if ((nodes = pfctl_update_qstats(dev, &root)) < 0) return (-1); if (nodes == 0) printf("No queue in use\n"); for (node = root; node != NULL; node = node->next) { if (iface != NULL && strcmp(node->altq.ifname, iface)) continue; if (dotitle) { pfctl_print_title("ALTQ:"); dotitle = 0; } pfctl_print_altq_node(dev, node, 0, opts); } while (verbose2 && nodes > 0) { printf("\n"); fflush(stdout); sleep(STAT_INTERVAL); if ((nodes = pfctl_update_qstats(dev, &root)) == -1) return (-1); for (node = root; node != NULL; node = node->next) { if (iface != NULL && strcmp(node->altq.ifname, iface)) continue; #ifdef __FreeBSD__ if (node->altq.local_flags & PFALTQ_FLAG_IF_REMOVED) continue; #endif pfctl_print_altq_node(dev, node, 0, opts); } } pfctl_free_altq_node(root); return (0); } int pfctl_update_qstats(int dev, struct pf_altq_node **root) { struct pf_altq_node *node; struct pfioc_altq pa; struct pfioc_qstats pq; u_int32_t mnr, nr; struct queue_stats qstats; static u_int32_t last_ticket; memset(&pa, 0, sizeof(pa)); memset(&pq, 0, sizeof(pq)); memset(&qstats, 0, sizeof(qstats)); if (ioctl(dev, DIOCGETALTQS, &pa)) { warn("DIOCGETALTQS"); return (-1); } /* if a new set is found, start over */ if (pa.ticket != last_ticket && *root != NULL) { pfctl_free_altq_node(*root); *root = NULL; } last_ticket = pa.ticket; mnr = pa.nr; for (nr = 0; nr < mnr; ++nr) { pa.nr = nr; if (ioctl(dev, DIOCGETALTQ, &pa)) { warn("DIOCGETALTQ"); return (-1); } #ifdef __FreeBSD__ if (pa.altq.qid > 0 && !(pa.altq.local_flags & PFALTQ_FLAG_IF_REMOVED)) { #else if (pa.altq.qid > 0) { #endif pq.nr = nr; pq.ticket = pa.ticket; pq.buf = &qstats.data; pq.nbytes = sizeof(qstats.data); if (ioctl(dev, DIOCGETQSTATS, &pq)) { warn("DIOCGETQSTATS"); return (-1); } if ((node = pfctl_find_altq_node(*root, pa.altq.qname, pa.altq.ifname)) != NULL) { memcpy(&node->qstats.data, &qstats.data, sizeof(qstats.data)); update_avg(node); } else { pfctl_insert_altq_node(root, pa.altq, qstats); } } #ifdef __FreeBSD__ else if (pa.altq.local_flags & PFALTQ_FLAG_IF_REMOVED) { memset(&qstats.data, 0, sizeof(qstats.data)); if ((node = pfctl_find_altq_node(*root, pa.altq.qname, pa.altq.ifname)) != NULL) { memcpy(&node->qstats.data, &qstats.data, sizeof(qstats.data)); update_avg(node); } else { pfctl_insert_altq_node(root, pa.altq, qstats); } } #endif } return (mnr); } void pfctl_insert_altq_node(struct pf_altq_node **root, const struct pf_altq altq, const struct queue_stats qstats) { struct pf_altq_node *node; node = calloc(1, sizeof(struct pf_altq_node)); if (node == NULL) err(1, "pfctl_insert_altq_node: calloc"); memcpy(&node->altq, &altq, sizeof(struct pf_altq)); memcpy(&node->qstats, &qstats, sizeof(qstats)); node->next = node->children = NULL; if (*root == NULL) *root = node; else if (!altq.parent[0]) { struct pf_altq_node *prev = *root; while (prev->next != NULL) prev = prev->next; prev->next = node; } else { struct pf_altq_node *parent; parent = pfctl_find_altq_node(*root, altq.parent, altq.ifname); if (parent == NULL) errx(1, "parent %s not found", altq.parent); if (parent->children == NULL) parent->children = node; else { struct pf_altq_node *prev = parent->children; while (prev->next != NULL) prev = prev->next; prev->next = node; } } update_avg(node); } struct pf_altq_node * pfctl_find_altq_node(struct pf_altq_node *root, const char *qname, const char *ifname) { struct pf_altq_node *node, *child; for (node = root; node != NULL; node = node->next) { if (!strcmp(node->altq.qname, qname) && !(strcmp(node->altq.ifname, ifname))) return (node); if (node->children != NULL) { child = pfctl_find_altq_node(node->children, qname, ifname); if (child != NULL) return (child); } } return (NULL); } void pfctl_print_altq_node(int dev, const struct pf_altq_node *node, unsigned int level, int opts) { const struct pf_altq_node *child; if (node == NULL) return; print_altq(&node->altq, level, NULL, NULL); if (node->children != NULL) { printf("{"); for (child = node->children; child != NULL; child = child->next) { printf("%s", child->altq.qname); if (child->next != NULL) printf(", "); } printf("}"); } printf("\n"); if (opts & PF_OPT_VERBOSE) pfctl_print_altq_nodestat(dev, node); if (opts & PF_OPT_DEBUG) printf(" [ qid=%u ifname=%s ifbandwidth=%s ]\n", node->altq.qid, node->altq.ifname, rate2str((double)(node->altq.ifbandwidth))); for (child = node->children; child != NULL; child = child->next) pfctl_print_altq_node(dev, child, level + 1, opts); } void pfctl_print_altq_nodestat(int dev, const struct pf_altq_node *a) { if (a->altq.qid == 0) return; #ifdef __FreeBSD__ if (a->altq.local_flags & PFALTQ_FLAG_IF_REMOVED) return; #endif switch (a->altq.scheduler) { case ALTQT_CBQ: print_cbqstats(a->qstats); break; case ALTQT_PRIQ: print_priqstats(a->qstats); break; case ALTQT_HFSC: print_hfscstats(a->qstats); break; } } void print_cbqstats(struct queue_stats cur) { printf(" [ pkts: %10llu bytes: %10llu " "dropped pkts: %6llu bytes: %6llu ]\n", (unsigned long long)cur.data.cbq_stats.xmit_cnt.packets, (unsigned long long)cur.data.cbq_stats.xmit_cnt.bytes, (unsigned long long)cur.data.cbq_stats.drop_cnt.packets, (unsigned long long)cur.data.cbq_stats.drop_cnt.bytes); printf(" [ qlength: %3d/%3d borrows: %6u suspends: %6u ]\n", cur.data.cbq_stats.qcnt, cur.data.cbq_stats.qmax, cur.data.cbq_stats.borrows, cur.data.cbq_stats.delays); if (cur.avgn < 2) return; printf(" [ measured: %7.1f packets/s, %s/s ]\n", cur.avg_packets / STAT_INTERVAL, rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); } void print_priqstats(struct queue_stats cur) { printf(" [ pkts: %10llu bytes: %10llu " "dropped pkts: %6llu bytes: %6llu ]\n", (unsigned long long)cur.data.priq_stats.xmitcnt.packets, (unsigned long long)cur.data.priq_stats.xmitcnt.bytes, (unsigned long long)cur.data.priq_stats.dropcnt.packets, (unsigned long long)cur.data.priq_stats.dropcnt.bytes); printf(" [ qlength: %3d/%3d ]\n", cur.data.priq_stats.qlength, cur.data.priq_stats.qlimit); if (cur.avgn < 2) return; printf(" [ measured: %7.1f packets/s, %s/s ]\n", cur.avg_packets / STAT_INTERVAL, rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); } void print_hfscstats(struct queue_stats cur) { printf(" [ pkts: %10llu bytes: %10llu " "dropped pkts: %6llu bytes: %6llu ]\n", (unsigned long long)cur.data.hfsc_stats.xmit_cnt.packets, (unsigned long long)cur.data.hfsc_stats.xmit_cnt.bytes, (unsigned long long)cur.data.hfsc_stats.drop_cnt.packets, (unsigned long long)cur.data.hfsc_stats.drop_cnt.bytes); printf(" [ qlength: %3d/%3d ]\n", cur.data.hfsc_stats.qlength, cur.data.hfsc_stats.qlimit); if (cur.avgn < 2) return; printf(" [ measured: %7.1f packets/s, %s/s ]\n", cur.avg_packets / STAT_INTERVAL, rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); } void pfctl_free_altq_node(struct pf_altq_node *node) { while (node != NULL) { struct pf_altq_node *prev; if (node->children != NULL) pfctl_free_altq_node(node->children); prev = node; node = node->next; free(prev); } } void update_avg(struct pf_altq_node *a) { struct queue_stats *qs; u_int64_t b, p; int n; if (a->altq.qid == 0) return; qs = &a->qstats; n = qs->avgn; switch (a->altq.scheduler) { case ALTQT_CBQ: b = qs->data.cbq_stats.xmit_cnt.bytes; p = qs->data.cbq_stats.xmit_cnt.packets; break; case ALTQT_PRIQ: b = qs->data.priq_stats.xmitcnt.bytes; p = qs->data.priq_stats.xmitcnt.packets; break; case ALTQT_HFSC: b = qs->data.hfsc_stats.xmit_cnt.bytes; p = qs->data.hfsc_stats.xmit_cnt.packets; break; default: b = 0; p = 0; break; } if (n == 0) { qs->prev_bytes = b; qs->prev_packets = p; qs->avgn++; return; } if (b >= qs->prev_bytes) qs->avg_bytes = ((qs->avg_bytes * (n - 1)) + (b - qs->prev_bytes)) / n; if (p >= qs->prev_packets) qs->avg_packets = ((qs->avg_packets * (n - 1)) + (p - qs->prev_packets)) / n; qs->prev_bytes = b; qs->prev_packets = p; if (n < AVGN_MAX) qs->avgn++; }