/*	$NetBSD: pfctl_qstats.c,v 1.5 2006/10/12 19:59:08 peter Exp $	*/
/*	$OpenBSD: pfctl_qstats.c,v 1.30 2004/04/27 21:47:32 kjc Exp $ */

/*
 * Copyright (c) Henning Brauer <henning@openbsd.org>
 *
 * 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 <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

#include <net/if.h>
#include <netinet/in.h>
#include <net/pfvar.h>
#include <arpa/inet.h>

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <altq/altq.h>
#include <altq/altq_cbq.h>
#include <altq/altq_priq.h>
#include <altq/altq_hfsc.h>

#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);


	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;
			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);
		}
		if (pa.altq.qid > 0) {
			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);
			}
		}
	}
	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 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;

	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++;
}
