Commit 55ed27ac authored by Geoff Simmons's avatar Geoff Simmons

Backport for Varnish 5.1.x.

parent d868dc37
......@@ -30,7 +30,6 @@ import selector [from "path"] ;
new <obj> = selector.set([BOOL case_sensitive])
VOID <obj>.add(STRING [, STRING string] [, STRING regex]
[, BACKEND backend])
VOID <obj>.create_stats()
# Matching
BOOL <obj>.match(STRING)
......@@ -381,42 +380,6 @@ Example::
regex="^/quux/([^/]+)/");
}
.. _func_set.create_stats:
set.create_stats
----------------
::
VOID set.create_stats(PRIV_VCL)
Creates statistics counters for this object that are displayed by
tools such as ``varnishstat(1)``. See `STATISTICS`_ below for details.
It should be called in ``vcl_init`` after all strings have been added
to the set. No statistics are created for a set object if
``.create_stats()`` is not invoked.
Unlike the matching operations, the time needed for this method
increases as the number of strings in the set increases, since it
traverses the entire internal data structure. For large sets, the time
needed for a VCL load can become noticeably longer. If that is a
problem, consider using this method during development and testing,
and removing it for production deployments (since the stats values are
always the same for sets with the same strings).
``.create_stats()`` fails and invokes VCL failure if it is called in
any VCL subroutine besides ``vcl_init``.
Example::
sub vcl_init {
new myset = selector.set();
myset.add("foo");
myset.add("bar");
myset.add("baz");
myset.create_stats();
}
.. _func_set.match:
set.match
......@@ -793,53 +756,6 @@ Example::
std.log("Using VMOD selector version: " + selector.version());
STATISTICS
==========
When ``.create_stats()`` is invoked for a set object, statistics are
created that can be viewed with a tool like varnishstat(1).
*NOTE*: except for ``elements``, the stats refer to properties of a
set object's internal data structure, and hence depend on the internal
implementation. The implementation may be changed in any new version
of the VMOD, and hence the stats may change. If you install a new
version, check the new version of the present document to see if there
are new or different statistics.
The stats have the following naming schema::
SELECTOR.<vcl>.<object>.<stat>
... where ``<vcl>`` is the VCL instance name, ``<object>`` is the
object name, and ``<stat>`` is the statistic. So the ``elements`` stat
of the ``myset`` object in the VCL instance ``boot`` is named::
SELECTOR.boot.myset.elements
The VMOD currently provides the following statistics:
* ``elements``: the number of elements in the set (added via
``.add()``)
* ``nodes``: the total number of nodes in the internal data structure
* ``leaves``: the number of leaf nodes in the internal data structure.
There may be fewer leaf nodes than elements of the set, if the set
contains common prefixes.
* ``dmin``: the minimum depth of a terminating node in the internal
data structure; that is, a node at which a matching string may be
found (not necessarily a leaf node, if the set has common prefixes)
* ``dmax``: the maximum depth of a terminating node in the internal
data structure
* ``davg``: the average depth of a terminating node in the internal
data structure, rounded to the nearest integer
The values of the stats are constant; they do not change during the
lifetime of the VCL instance.
ERRORS
======
......@@ -867,7 +783,7 @@ a VCL subroutine:
REQUIREMENTS
============
The VMOD requires Varnish versions 5.2.x. See the VMOD source
The VMOD requires Varnish versions 5.1.x. See the VMOD source
repository for versions that are compatible with other Varnish
versions.
......@@ -916,12 +832,9 @@ SEE ALSO
* varnishd(1)
* vcl(7)
* varnishstat(1)
* varnish-cli(7)
* VMOD source repository: https://code.uplex.de/uplex-varnish/libvmod-selector
* `VMOD re2`_: https://code.uplex.de/uplex-varnish/libvmod-re2
COPYRIGHT
=========
......
......@@ -37,7 +37,7 @@ AM_CONDITIONAL(HAVE_RST2MAN, [test "x$RST2MAN" != "xno"])
m4_ifndef([VARNISH_PREREQ], AC_MSG_ERROR([Need varnish.m4 -- see README.rst]))
VARNISH_PREREQ([5.2.0 5.2.1])
VARNISH_PREREQ([1.0.0], [5.1.3])
VARNISH_VMODS([selector])
VMOD_TESTS="$(cd $srcdir/src && echo tests/*.vtc)"
......@@ -91,24 +91,6 @@ if test "x$enable_debugging" != "xno"; then
[])
fi
# Varnish since 5.2.0 depends on AC_DEFINE(STATIC_ASSERT)
save_LIBS="${LIBS}"
LIBS=""
AC_CACHE_CHECK([for _Static_assert],
[ac_cv_static_assert],
[AC_RUN_IFELSE(
[AC_LANG_PROGRAM([[
_Static_assert(1 == sizeof(char), "didn't work");
]],[[
]])],
[ac_cv_static_assert=yes],
[ac_cv_static_assert=no])
])
if test "$ac_cv_static_assert" = yes; then
AC_DEFINE([STATIC_ASSERT], [1], [Define if _Static_assert is availabel])
fi
LIBS="${save_LIBS}"
AC_CONFIG_FILES([
Makefile
src/Makefile
......
......@@ -12,15 +12,10 @@ libvmod_selector_la_SOURCES = \
nodist_libvmod_selector_la_SOURCES = \
vcc_if.c \
vcc_if.h \
VSC_selector.c \
VSC_selector.h
vcc_if.h
dist_man_MANS = vmod_selector.3
VSC_selector.c VSC_selector.h: selector.vsc
$(PYTHON) $(VSCTOOL) -ch $(srcdir)/selector.vsc
vmod_selector.c patricia.c: patricia.h
vmod_selector.lo: $(nodist_libvmod_selector_la_SOURCES)
......
This diff is collapsed.
This diff is collapsed.
......@@ -1250,201 +1250,6 @@ client c1 {
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: Eej5xeNies"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: ta2UBaem2k"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: caepez0AiT"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: Buc0ieCohm"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: wie1EHee1o"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: ha6Olo8foo"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: Veengae7Wo"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: ui7OhY8oro"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: aiqui8eTho"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: Wiequeeve1"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: ohMeim7sai"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: Equie2ohri"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: ga5Aepikie"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: Jaibie6che"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
txreq -hdr "Word: reide9aeGh"
rxresp
expect resp.status == 200
expect resp.http.Match == "false"
expect resp.http.N == "0"
expect resp.http.Which == "0"
expect resp.http.Which-Unique == resp.http.Which
expect resp.http.Which-Exact == "0"
expect resp.http.Which-First == "0"
expect resp.http.Which-Last == "0"
expect resp.http.Which-Shortest == "0"
expect resp.http.Which-Longest == "0"
} -run
varnish v1 -vcl {
......
# looks like -*- vcl -*-
varnishtest ".create_stats() method"
varnish v1 -vcl {
import ${vmod_selector};
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new s = selector.set();
s.add("foo");
s.add("bar");
s.add("baz");
s.add("quux");
s.create_stats();
}
} -start
varnish v1 -vsc SELECTOR.*
varnish v1 -expect SELECTOR.vcl1.s.elements == 4
varnish v1 -expect SELECTOR.vcl1.s.nodes > 0
varnish v1 -expect SELECTOR.vcl1.s.leaves <= 4
varnish v1 -expect SELECTOR.vcl1.s.dmin > 0
varnish v1 -expect SELECTOR.vcl1.s.dmax > 0
varnish v1 -expect SELECTOR.vcl1.s.davg > 0
varnish v1 -vcl {
import ${vmod_selector};
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new p = selector.set();
p.add("foo");
p.add("foobar");
p.add("foobarbaz");
p.add("foobarbazquux");
p.create_stats();
# No .create_stats() call.
new n = selector.set();
n.add("foo");
n.add("bar");
n.add("baz");
n.add("quux");
# Calling .create_stats() on an empty set is
# pointless, but not an error.
new e = selector.set();
e.create_stats();
}
}
# Stats for vc1.s and vcl2.p appear, but not for vcl2.n.
varnish v1 -vsc SELECTOR.*
varnish v1 -expect SELECTOR.vcl2.p.elements == 4
varnish v1 -expect SELECTOR.vcl2.p.nodes > 0
varnish v1 -expect SELECTOR.vcl2.p.leaves <= 4
varnish v1 -expect SELECTOR.vcl2.p.dmin > 0
varnish v1 -expect SELECTOR.vcl2.p.dmax > 0
varnish v1 -expect SELECTOR.vcl2.p.davg > 0
varnish v1 -expect SELECTOR.vcl2.e.elements == 0
varnish v1 -expect SELECTOR.vcl2.e.nodes == 0
varnish v1 -expect SELECTOR.vcl2.e.leaves == 0
varnish v1 -expect SELECTOR.vcl2.e.dmin == 0
varnish v1 -expect SELECTOR.vcl2.e.dmax == 0
varnish v1 -expect SELECTOR.vcl2.e.davg == 0
# cold/warm states do *not* affect stats visibility in Varnish < 6.0.0
varnish v1 -cliok "vcl.state vcl1 cold"
varnish v1 -vsc SELECTOR.*
varnish v1 -cliok "vcl.state vcl1 warm"
varnish v1 -vsc SELECTOR.*
varnish v1 -expect SELECTOR.vcl1.s.elements == 4
varnish v1 -expect SELECTOR.vcl1.s.nodes > 0
varnish v1 -expect SELECTOR.vcl1.s.leaves <= 4
varnish v1 -expect SELECTOR.vcl1.s.dmin > 0
varnish v1 -expect SELECTOR.vcl1.s.dmax > 0
varnish v1 -expect SELECTOR.vcl1.s.davg > 0
# The same 100 words from /usr/share/dict/words as in match.vtc
varnish v1 -vcl {
import ${vmod_selector};
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new words = selector.set();
words.add("trustee's");
words.add("Marc");
words.add("remover's");
words.add("brutishly");
words.add("Blythe");
words.add("tastier");
words.add("backed");
words.add("rain");
words.add("banality");
words.add("unstrung");
words.add("barnyards");
words.add("paperweight");
words.add("Kazan's");
words.add("fanfares");
words.add("Donny's");
words.add("faze");
words.add("redefinition");
words.add("Schulz");
words.add("Lanai's");
words.add("bastions");
words.add("kicker's");
words.add("Denny");
words.add("disgraced");
words.add("downswings");
words.add("pullback's");
words.add("Gregorio's");
words.add("spillways");
words.add("puller");
words.add("basilica's");
words.add("serviced");
words.add("insistently");
words.add("Frisian's");
words.add("question");
words.add("mien");
words.add("rockier");
words.add("indivisible");
words.add("megahertzes");
words.add("Oldfield's");
words.add("accusatory");
words.add("Mabel");
words.add("magnetize");
words.add("Philly");
words.add("Katheryn's");
words.add("policewoman's");
words.add("ashcan");
words.add("deviousness's");
words.add("suspends");
words.add("furnishings");
words.add("compiler's");
words.add("Claudio");
words.add("zestfully");
words.add("laughter's");
words.add("Manuel");
words.add("palatal's");
words.add("eminent");
words.add("strongboxes");
words.add("pinafores");
words.add("Glendale");
words.add("dethronement");
words.add("chlorinate");
words.add("Souths");
words.add("tilting");
words.add("trenched");
words.add("run");
words.add("initialized");
words.add("breakfast");
words.add("winning's");
words.add("mediates");
words.add("triads");
words.add("verdict");
words.add("Irish");
words.add("Jeremy");
words.add("handouts");
words.add("Billie's");
words.add("romanticist's");
words.add("descanting");
words.add("bidders");
words.add("play");
words.add("navigability's");
words.add("leapfrogging");
words.add("Libby");
words.add("smelter's");
words.add("hermit's");
words.add("Tabatha's");
words.add("churlish");
words.add("spuriousness's");
words.add("Salish's");
words.add("Curry");
words.add("hula");
words.add("ruse's");
words.add("bureaucratic");
words.add("Moseley");
words.add("confluence");
words.add("inseams");
words.add("producers");
words.add("cozier");
words.add("augur's");
words.add("electrode's");
words.add("disposition");
words.add("Rena's");
words.create_stats();
}
}
varnish v1 -expect SELECTOR.vcl3.words.elements == 100
varnish v1 -expect SELECTOR.vcl3.words.nodes > 0
varnish v1 -expect SELECTOR.vcl3.words.leaves <= 100
varnish v1 -expect SELECTOR.vcl3.words.dmin > 0
varnish v1 -expect SELECTOR.vcl3.words.dmax > 0
varnish v1 -expect SELECTOR.vcl3.words.davg > 0
varnish v1 -cliok "vcl.state vcl1 cold"
varnish v1 -cli "vcl.discard vcl1"
# No stats for vcl1 appear after discard.
varnish v1 -vsc SELECTOR.vc1.*
# The same for vcl2 after discard.
varnish v1 -cli "vcl.discard vcl2"
varnish v1 -vsc SELECTOR.vc2.*
varnish v1 -vcl {
import ${vmod_selector};
backend b { .host = "${bad_ip}"; }
sub vcl_init {
# No .create_stats() call.
new n = selector.set();
n.add("foo");
n.add("bar");
n.add("baz");
n.add("quux");
}
sub vcl_recv {
n.create_stats();
return(synth(200));
}
}
client c1 {
txreq
rxresp
expect resp.status == 503
expect resp.reason == "VCL failed"
} -run
logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
expect 0 * Begin req
expect * = VCL_Error {^vmod selector failure: n\.create_stats\(\) may only be called in vcl_init$}
expect * = End
} -run
......@@ -44,7 +44,6 @@
#include "vcc_if.h"
#include "patricia.h"
#include "VSC_selector.h"
#define VFAIL(ctx, fmt, ...) \
VRT_fail((ctx), "vmod selector failure: " fmt, __VA_ARGS__)
......@@ -90,58 +89,17 @@ struct vmod_selector_set {
VCL_BOOL case_sensitive;
};
struct vsc_entry {
unsigned magic;
#define VMOD_SELECTOR_VSC_MAGIC 0x4b99b64a
VSLIST_ENTRY(vsc_entry) list;
struct VSC_selector *vsc;
};
VSLIST_HEAD(vsc_head, vsc_entry);
/* Event function */
int
event(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)
{
struct vsc_head *vsc_head;
struct vsc_entry *vsc_entry;
ASSERT_CLI();
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
AN(priv);
(void) ctx;
(void) priv;
if (priv->priv == NULL) {
vsc_head = malloc(sizeof(*vsc_head));
AN(vsc_head);
priv->priv = vsc_head;
VSLIST_INIT(vsc_head);
}
else
vsc_head = priv->priv;
switch(e) {
case VCL_EVENT_LOAD:
if (!PT_Inited())
PT_Init();
break;
case VCL_EVENT_DISCARD:
while (!VSLIST_EMPTY(vsc_head)) {
vsc_entry = VSLIST_FIRST(vsc_head);
CHECK_OBJ_NOTNULL(vsc_entry, VMOD_SELECTOR_VSC_MAGIC);
VSC_selector_Destroy(&vsc_entry->vsc);
VSLIST_REMOVE_HEAD(vsc_head, list);
FREE_OBJ(vsc_entry);
}
free(vsc_head);
break;
case VCL_EVENT_WARM:
case VCL_EVENT_COLD:
break;
default:
WRONG("illegal event enum");
}
return 0;
if (e == VCL_EVENT_LOAD && !PT_Inited())
PT_Init();
return (0);
}
/* Object regex */
......@@ -463,7 +421,7 @@ vmod_set_hasprefix(VRT_CTX, struct vmod_selector_set *set, VCL_STRING subject)
static struct match_data *
get_existing_match_data(VRT_CTX,
const struct vmod_selector_set * const restrict set,
struct vmod_selector_set * const restrict set,
const char * const restrict method)
{
struct vmod_priv *task;
......@@ -577,7 +535,7 @@ vmod_set_which(VRT_CTX, struct vmod_selector_set *set, VCL_ENUM selects)
}
static unsigned
get_idx(VRT_CTX, VCL_INT n, const struct vmod_selector_set * const restrict set,
get_idx(VRT_CTX, VCL_INT n, struct vmod_selector_set * const restrict set,
const char * const restrict method, VCL_ENUM const restrict selects)
{
struct match_data *match;
......@@ -669,9 +627,8 @@ vmod_set_string(VRT_CTX, struct vmod_selector_set * set, VCL_INT n,
}
static vre_t *
get_re(VRT_CTX, const struct vmod_selector_set * const restrict set,
VCL_INT n, VCL_ENUM const restrict selects,
const char * const restrict method)
get_re(VRT_CTX, struct vmod_selector_set * const restrict set, VCL_INT n,
VCL_ENUM const restrict selects, const char * const restrict method)
{
unsigned idx;
vre_t *re;
......@@ -732,57 +689,6 @@ vmod_set_debug(VRT_CTX, struct vmod_selector_set *set)
return output;
}
VCL_VOID
vmod_set_create_stats(VRT_CTX, struct vmod_selector_set *set,
struct vmod_priv *priv)
{
struct pt_stats stats = { .magic = PT_STATS_MAGIC };
struct VSC_selector *vsc;
struct vsc_head *vsc_head;
struct vsc_entry *vsc_entry;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(set, VMOD_SELECTOR_SET_MAGIC);
if ((ctx->method & VCL_MET_INIT) == 0) {
VFAIL(ctx, "%s.create_stats() may only be called in vcl_init",
set->vcl_name);
return;
}
AN(priv);
AN(priv->priv);
vsc_head = priv->priv;
if (set->nmembers == 0)
memset(&stats, 0, sizeof(stats));
else {
char **members = set->members;
if (!set->case_sensitive)
members = set->lomembers;
AN(members);
PT_Stats(set->origo, members, &stats);
assert(stats.terms == set->nmembers);
assert(stats.leaves <= stats.terms);
assert(stats.terms <= stats.nodes);
assert(stats.dmin <= stats.dmax);
assert(stats.dmin <= stats.davg);
assert(stats.davg <= stats.dmax);
}
vsc = VSC_selector_New("%s.%s", VCL_Name(ctx->vcl), set->vcl_name);
vsc->elements = set->nmembers;
vsc->nodes = stats.nodes;
vsc->leaves = stats.leaves;
vsc->dmin = stats.dmin;
vsc->dmax = stats.dmax;
vsc->davg = (uint64_t)(stats.davg + 0.5);