2005-05-03 16:55:20 +00:00
|
|
|
/* $OpenBSD: pfctl_qstats.c,v 1.30 2004/04/27 21:47:32 kjc Exp $ */
|
2004-02-28 16:52:45 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2004-03-16 17:24:06 +00:00
|
|
|
#include <sys/cdefs.h>
|
|
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
|
2004-02-28 16:52:45 +00:00
|
|
|
#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 *,
|
2011-06-28 11:57:25 +00:00
|
|
|
unsigned, int);
|
2004-02-28 16:52:45 +00:00
|
|
|
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
|
2004-06-16 23:39:33 +00:00
|
|
|
pfctl_show_altq(int dev, const char *iface, int opts, int verbose2)
|
2004-02-28 16:52:45 +00:00
|
|
|
{
|
|
|
|
struct pf_altq_node *root = NULL, *node;
|
2004-06-16 23:39:33 +00:00
|
|
|
int nodes, dotitle = (opts & PF_OPT_SHOWALL);
|
2004-02-28 16:52:45 +00:00
|
|
|
|
2004-03-15 13:41:17 +00:00
|
|
|
#ifdef __FreeBSD__
|
2004-02-28 17:32:53 +00:00
|
|
|
if (!altqsupport)
|
|
|
|
return (-1);
|
|
|
|
#endif
|
2004-06-16 23:39:33 +00:00
|
|
|
|
|
|
|
if ((nodes = pfctl_update_qstats(dev, &root)) < 0)
|
2004-02-28 16:52:45 +00:00
|
|
|
return (-1);
|
|
|
|
|
2005-05-03 16:55:20 +00:00
|
|
|
if (nodes == 0)
|
|
|
|
printf("No queue in use\n");
|
2004-06-16 23:39:33 +00:00
|
|
|
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;
|
|
|
|
}
|
2004-02-28 16:52:45 +00:00
|
|
|
pfctl_print_altq_node(dev, node, 0, opts);
|
2004-06-16 23:39:33 +00:00
|
|
|
}
|
2004-02-28 16:52:45 +00:00
|
|
|
|
2005-05-03 16:55:20 +00:00
|
|
|
while (verbose2 && nodes > 0) {
|
2004-02-28 16:52:45 +00:00
|
|
|
printf("\n");
|
|
|
|
fflush(stdout);
|
|
|
|
sleep(STAT_INTERVAL);
|
2005-05-03 16:55:20 +00:00
|
|
|
if ((nodes = pfctl_update_qstats(dev, &root)) == -1)
|
2004-02-28 16:52:45 +00:00
|
|
|
return (-1);
|
2004-06-16 23:39:33 +00:00
|
|
|
for (node = root; node != NULL; node = node->next) {
|
|
|
|
if (iface != NULL && strcmp(node->altq.ifname, iface))
|
|
|
|
continue;
|
2008-03-29 00:24:36 +00:00
|
|
|
#ifdef __FreeBSD__
|
|
|
|
if (node->altq.local_flags & PFALTQ_FLAG_IF_REMOVED)
|
|
|
|
continue;
|
|
|
|
#endif
|
2004-02-28 16:52:45 +00:00
|
|
|
pfctl_print_altq_node(dev, node, 0, opts);
|
2004-06-16 23:39:33 +00:00
|
|
|
}
|
2004-02-28 16:52:45 +00:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
2008-03-29 00:24:36 +00:00
|
|
|
#ifdef __FreeBSD__
|
|
|
|
if (pa.altq.qid > 0 &&
|
|
|
|
!(pa.altq.local_flags & PFALTQ_FLAG_IF_REMOVED)) {
|
|
|
|
#else
|
2004-02-28 16:52:45 +00:00
|
|
|
if (pa.altq.qid > 0) {
|
2008-03-29 00:24:36 +00:00
|
|
|
#endif
|
2004-02-28 16:52:45 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2008-03-29 00:24:36 +00:00
|
|
|
#ifdef __FreeBSD__
|
2011-06-28 11:57:25 +00:00
|
|
|
else if (pa.altq.local_flags & PFALTQ_FLAG_IF_REMOVED) {
|
|
|
|
memset(&qstats.data, 0, sizeof(qstats.data));
|
2008-03-29 00:24:36 +00:00
|
|
|
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);
|
2011-06-28 11:57:25 +00:00
|
|
|
}
|
2008-03-29 00:24:36 +00:00
|
|
|
}
|
|
|
|
#endif
|
2004-02-28 16:52:45 +00:00
|
|
|
}
|
2004-06-16 23:39:33 +00:00
|
|
|
return (mnr);
|
2004-02-28 16:52:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2008-12-10 20:59:26 +00:00
|
|
|
pfctl_print_altq_node(int dev, const struct pf_altq_node *node,
|
|
|
|
unsigned int level, int opts)
|
2004-02-28 16:52:45 +00:00
|
|
|
{
|
|
|
|
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)
|
2004-06-16 23:39:33 +00:00
|
|
|
printf(" [ qid=%u ifname=%s ifbandwidth=%s ]\n",
|
|
|
|
node->altq.qid, node->altq.ifname,
|
|
|
|
rate2str((double)(node->altq.ifbandwidth)));
|
2004-02-28 16:52:45 +00:00
|
|
|
|
|
|
|
for (child = node->children; child != NULL;
|
|
|
|
child = child->next)
|
2004-06-16 23:39:33 +00:00
|
|
|
pfctl_print_altq_node(dev, child, level + 1, opts);
|
2004-02-28 16:52:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
pfctl_print_altq_nodestat(int dev, const struct pf_altq_node *a)
|
|
|
|
{
|
|
|
|
if (a->altq.qid == 0)
|
|
|
|
return;
|
|
|
|
|
2008-03-29 00:24:36 +00:00
|
|
|
#ifdef __FreeBSD__
|
|
|
|
if (a->altq.local_flags & PFALTQ_FLAG_IF_REMOVED)
|
|
|
|
return;
|
|
|
|
#endif
|
2004-02-28 16:52:45 +00:00
|
|
|
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)
|
|
|
|
{
|
2004-03-15 13:41:17 +00:00
|
|
|
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);
|
2004-02-28 16:52:45 +00:00
|
|
|
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)
|
|
|
|
{
|
2004-03-15 13:41:17 +00:00
|
|
|
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);
|
2004-02-28 16:52:45 +00:00
|
|
|
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)
|
|
|
|
{
|
2004-03-15 13:41:17 +00:00
|
|
|
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);
|
2004-02-28 16:52:45 +00:00
|
|
|
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++;
|
|
|
|
}
|