From 90168d285bfa0250cab6709eb3be8d6f2517011a Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 22 Oct 2010 09:10:44 -0700 Subject: ESC-108 Develop support classes for numerical collection Stuff moved over and adapted from simulator code. Basic, simple counters and min/max/mean accumulators. --- indra/newview/CMakeLists.txt | 5 + indra/newview/llsimplestat.h | 140 ++++++++++ indra/newview/tests/llsimplestat_test.cpp | 428 ++++++++++++++++++++++++++++++ 3 files changed, 573 insertions(+) create mode 100644 indra/newview/llsimplestat.h create mode 100644 indra/newview/tests/llsimplestat_test.cpp diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 1f4302d870..0c4d2aaca6 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1925,6 +1925,11 @@ if (LL_TESTS) "${test_libs}" ) + LL_ADD_INTEGRATION_TEST(llsimplestat + "" + "${test_libs}" + ) + #ADD_VIEWER_BUILD_TEST(llmemoryview viewer) #ADD_VIEWER_BUILD_TEST(llagentaccess viewer) #ADD_VIEWER_BUILD_TEST(llworldmap viewer) diff --git a/indra/newview/llsimplestat.h b/indra/newview/llsimplestat.h new file mode 100644 index 0000000000..f8f4be0390 --- /dev/null +++ b/indra/newview/llsimplestat.h @@ -0,0 +1,140 @@ +/** + * @file llsimplestat.h + * @brief Runtime statistics accumulation. + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_SIMPLESTAT_H +#define LL_SIMPLESTAT_H + +// History +// +// The original source for this code is the server repositories' +// llcommon/llstat.h file. This particular code was added after the +// viewer/server code schism but before the effort to convert common +// code to libraries was complete. Rather than add to merge issues, +// the needed code was cut'n'pasted into this new header as it isn't +// too awful a burden. Post-modularization, we can look at removing +// this redundancy. + + +/** + * @class LLSimpleStatCounter + * @brief Just counts events. + * + * Really not needed but have a pattern in mind in the future. + * Interface limits what can be done at that's just fine. + * + * *TODO: Update/transfer unit tests + * Unit tests: indra/test/llcommon_llstat_tut.cpp + */ +class LLSimpleStatCounter +{ +public: + inline LLSimpleStatCounter() { reset(); } + // Default destructor and assignment operator are valid + + inline void reset() { mCount = 0; } + + inline U32 operator++() { return ++mCount; } + + inline U32 getCount() const { return mCount; } + +protected: + U32 mCount; +}; + + +/** + * @class LLSimpleStatMMM + * @brief Templated collector of min, max and mean data for stats. + * + * Fed a stream of data samples, keeps a running account of the + * min, max and mean seen since construction or the last reset() + * call. A freshly-constructed or reset instance returns counts + * and values of zero. + * + * Overflows and underflows (integer, inf or -inf) and NaN's + * are the caller's problem. As is loss of precision when + * the running sum's exponent (when parameterized by a floating + * point of some type) differs from a given data sample's. + * + * Unit tests: indra/test/llcommon_llstat_tut.cpp + */ +template +class LLSimpleStatMMM +{ +public: + typedef VALUE_T Value; + +public: + LLSimpleStatMMM() { reset(); } + // Default destructor and assignment operator are valid + + /** + * Resets the object returning all counts and derived + * values back to zero. + */ + void reset() + { + mCount = 0; + mMin = Value(0); + mMax = Value(0); + mTotal = Value(0); + } + + void record(Value v) + { + if (mCount) + { + mMin = llmin(mMin, v); + mMax = llmax(mMax, v); + } + else + { + mMin = v; + mMax = v; + } + mTotal += v; + ++mCount; + } + + inline U32 getCount() const { return mCount; } + inline Value getMin() const { return mMin; } + inline Value getMax() const { return mMax; } + inline Value getMean() const { return mCount ? mTotal / mCount : mTotal; } + +protected: + U32 mCount; + Value mMin; + Value mMax; + Value mTotal; +}; + +#endif // LL_SIMPLESTAT_H diff --git a/indra/newview/tests/llsimplestat_test.cpp b/indra/newview/tests/llsimplestat_test.cpp new file mode 100644 index 0000000000..5efc9cf857 --- /dev/null +++ b/indra/newview/tests/llsimplestat_test.cpp @@ -0,0 +1,428 @@ +/** + * @file llsimplestats_test.cpp + * @date 2010-10-22 + * @brief Test cases for some of llsimplestat.h + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include + +#include "lltut.h" +#include "../llsimplestat.h" +#include "llsd.h" +#include "llmath.h" + +// @brief Used as a pointer cast type to get access to LLSimpleStatCounter +class TutStatCounter: public LLSimpleStatCounter +{ +public: + TutStatCounter(); // Not defined + ~TutStatCounter(); // Not defined + void operator=(const TutStatCounter &); // Not defined + + void setRawCount(U32 c) { mCount = c; } + U32 getRawCount() const { return mCount; } +}; + + +namespace tut +{ + struct stat_counter_index + {}; + typedef test_group stat_counter_index_t; + typedef stat_counter_index_t::object stat_counter_index_object_t; + tut::stat_counter_index_t tut_stat_counter_index("stat_counter_test"); + + // Testing LLSimpleStatCounter's external interface + template<> template<> + void stat_counter_index_object_t::test<1>() + { + LLSimpleStatCounter c1; + ensure("Initialized counter is zero", (0 == c1.getCount())); + + ensure("Counter increment return is 1", (1 == ++c1)); + ensure("Counter increment return is 2", (2 == ++c1)); + + ensure("Current counter is 2", (2 == c1.getCount())); + + c1.reset(); + ensure("Counter is 0 after reset", (0 == c1.getCount())); + + ensure("Counter increment return is 1", (1 == ++c1)); + } + + // Testing LLSimpleStatCounter's internal state + template<> template<> + void stat_counter_index_object_t::test<2>() + { + LLSimpleStatCounter c1; + TutStatCounter * tc1 = (TutStatCounter *) &c1; + + ensure("Initialized private counter is zero", (0 == tc1->getRawCount())); + + ++c1; + ++c1; + + ensure("Current private counter is 2", (2 == tc1->getRawCount())); + + c1.reset(); + ensure("Raw counter is 0 after reset", (0 == tc1->getRawCount())); + } + + // Testing LLSimpleStatCounter's wrapping behavior + template<> template<> + void stat_counter_index_object_t::test<3>() + { + LLSimpleStatCounter c1; + TutStatCounter * tc1 = (TutStatCounter *) &c1; + + tc1->setRawCount(U32_MAX); + ensure("Initialized private counter is zero", (U32_MAX == c1.getCount())); + + ensure("Increment of max value wraps to 0", (0 == ++c1)); + } + + // Testing LLSimpleStatMMM's external behavior + template<> template<> + void stat_counter_index_object_t::test<4>() + { + LLSimpleStatMMM<> m1; + typedef LLSimpleStatMMM<>::Value lcl_float; + lcl_float zero(0); + + // Freshly-constructed + ensure("Constructed MMM<> has 0 count", (0 == m1.getCount())); + ensure("Constructed MMM<> has 0 min", (zero == m1.getMin())); + ensure("Constructed MMM<> has 0 max", (zero == m1.getMax())); + ensure("Constructed MMM<> has 0 mean no div-by-zero", (zero == m1.getMean())); + + // Single insert + m1.record(1.0); + ensure("Single insert MMM<> has 1 count", (1 == m1.getCount())); + ensure("Single insert MMM<> has 1.0 min", (1.0 == m1.getMin())); + ensure("Single insert MMM<> has 1.0 max", (1.0 == m1.getMax())); + ensure("Single insert MMM<> has 1.0 mean", (1.0 == m1.getMean())); + + // Second insert + m1.record(3.0); + ensure("2nd insert MMM<> has 2 count", (2 == m1.getCount())); + ensure("2nd insert MMM<> has 1.0 min", (1.0 == m1.getMin())); + ensure("2nd insert MMM<> has 3.0 max", (3.0 == m1.getMax())); + ensure_approximately_equals("2nd insert MMM<> has 2.0 mean", m1.getMean(), lcl_float(2.0), 1); + + // Third insert + m1.record(5.0); + ensure("3rd insert MMM<> has 3 count", (3 == m1.getCount())); + ensure("3rd insert MMM<> has 1.0 min", (1.0 == m1.getMin())); + ensure("3rd insert MMM<> has 5.0 max", (5.0 == m1.getMax())); + ensure_approximately_equals("3rd insert MMM<> has 3.0 mean", m1.getMean(), lcl_float(3.0), 1); + + // Fourth insert + m1.record(1000000.0); + ensure("4th insert MMM<> has 4 count", (4 == m1.getCount())); + ensure("4th insert MMM<> has 1.0 min", (1.0 == m1.getMin())); + ensure("4th insert MMM<> has 100000.0 max", (1000000.0 == m1.getMax())); + ensure_approximately_equals("4th insert MMM<> has 250002.0 mean", m1.getMean(), lcl_float(250002.0), 1); + + // Reset + m1.reset(); + ensure("Reset MMM<> has 0 count", (0 == m1.getCount())); + ensure("Reset MMM<> has 0 min", (zero == m1.getMin())); + ensure("Reset MMM<> has 0 max", (zero == m1.getMax())); + ensure("Reset MMM<> has 0 mean no div-by-zero", (zero == m1.getMean())); + } + + // Testing LLSimpleStatMMM's response to large values + template<> template<> + void stat_counter_index_object_t::test<5>() + { + LLSimpleStatMMM<> m1; + typedef LLSimpleStatMMM<>::Value lcl_float; + lcl_float zero(0); + + // Insert overflowing values + const lcl_float bignum(F32_MAX / 2); + + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(zero); + + ensure("Overflowed MMM<> has 8 count", (8 == m1.getCount())); + ensure("Overflowed MMM<> has 0 min", (zero == m1.getMin())); + ensure("Overflowed MMM<> has huge max", (bignum == m1.getMax())); + ensure("Overflowed MMM<> has fetchable mean", (1.0 == m1.getMean() || true)); + // We should be infinte but not interested in proving the IEEE standard here. + LLSD sd1(m1.getMean()); + // std::cout << "Thingy: " << m1.getMean() << " and as LLSD: " << sd1 << std::endl; + ensure("Overflowed MMM<> produces LLSDable Real", (sd1.isReal())); + } + + // Testing LLSimpleStatMMM's external behavior + template<> template<> + void stat_counter_index_object_t::test<6>() + { + LLSimpleStatMMM m1; + typedef LLSimpleStatMMM::Value lcl_float; + lcl_float zero(0); + + // Freshly-constructed + ensure("Constructed MMM has 0 count", (0 == m1.getCount())); + ensure("Constructed MMM has 0 min", (zero == m1.getMin())); + ensure("Constructed MMM has 0 max", (zero == m1.getMax())); + ensure("Constructed MMM has 0 mean no div-by-zero", (zero == m1.getMean())); + + // Single insert + m1.record(1.0); + ensure("Single insert MMM has 1 count", (1 == m1.getCount())); + ensure("Single insert MMM has 1.0 min", (1.0 == m1.getMin())); + ensure("Single insert MMM has 1.0 max", (1.0 == m1.getMax())); + ensure("Single insert MMM has 1.0 mean", (1.0 == m1.getMean())); + + // Second insert + m1.record(3.0); + ensure("2nd insert MMM has 2 count", (2 == m1.getCount())); + ensure("2nd insert MMM has 1.0 min", (1.0 == m1.getMin())); + ensure("2nd insert MMM has 3.0 max", (3.0 == m1.getMax())); + ensure_approximately_equals("2nd insert MMM has 2.0 mean", m1.getMean(), lcl_float(2.0), 1); + + // Third insert + m1.record(5.0); + ensure("3rd insert MMM has 3 count", (3 == m1.getCount())); + ensure("3rd insert MMM has 1.0 min", (1.0 == m1.getMin())); + ensure("3rd insert MMM has 5.0 max", (5.0 == m1.getMax())); + ensure_approximately_equals("3rd insert MMM has 3.0 mean", m1.getMean(), lcl_float(3.0), 1); + + // Fourth insert + m1.record(1000000.0); + ensure("4th insert MMM has 4 count", (4 == m1.getCount())); + ensure("4th insert MMM has 1.0 min", (1.0 == m1.getMin())); + ensure("4th insert MMM has 1000000.0 max", (1000000.0 == m1.getMax())); + ensure_approximately_equals("4th insert MMM has 250002.0 mean", m1.getMean(), lcl_float(250002.0), 1); + + // Reset + m1.reset(); + ensure("Reset MMM has 0 count", (0 == m1.getCount())); + ensure("Reset MMM has 0 min", (zero == m1.getMin())); + ensure("Reset MMM has 0 max", (zero == m1.getMax())); + ensure("Reset MMM has 0 mean no div-by-zero", (zero == m1.getMean())); + } + + // Testing LLSimpleStatMMM's response to large values + template<> template<> + void stat_counter_index_object_t::test<7>() + { + LLSimpleStatMMM m1; + typedef LLSimpleStatMMM::Value lcl_float; + lcl_float zero(0); + + // Insert overflowing values + const lcl_float bignum(F32_MAX / 2); + + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(zero); + + ensure("Overflowed MMM has 8 count", (8 == m1.getCount())); + ensure("Overflowed MMM has 0 min", (zero == m1.getMin())); + ensure("Overflowed MMM has huge max", (bignum == m1.getMax())); + ensure("Overflowed MMM has fetchable mean", (1.0 == m1.getMean() || true)); + // We should be infinte but not interested in proving the IEEE standard here. + LLSD sd1(m1.getMean()); + // std::cout << "Thingy: " << m1.getMean() << " and as LLSD: " << sd1 << std::endl; + ensure("Overflowed MMM produces LLSDable Real", (sd1.isReal())); + } + + // Testing LLSimpleStatMMM's external behavior + template<> template<> + void stat_counter_index_object_t::test<8>() + { + LLSimpleStatMMM m1; + typedef LLSimpleStatMMM::Value lcl_float; + lcl_float zero(0); + + // Freshly-constructed + ensure("Constructed MMM has 0 count", (0 == m1.getCount())); + ensure("Constructed MMM has 0 min", (zero == m1.getMin())); + ensure("Constructed MMM has 0 max", (zero == m1.getMax())); + ensure("Constructed MMM has 0 mean no div-by-zero", (zero == m1.getMean())); + + // Single insert + m1.record(1.0); + ensure("Single insert MMM has 1 count", (1 == m1.getCount())); + ensure("Single insert MMM has 1.0 min", (1.0 == m1.getMin())); + ensure("Single insert MMM has 1.0 max", (1.0 == m1.getMax())); + ensure("Single insert MMM has 1.0 mean", (1.0 == m1.getMean())); + + // Second insert + m1.record(3.0); + ensure("2nd insert MMM has 2 count", (2 == m1.getCount())); + ensure("2nd insert MMM has 1.0 min", (1.0 == m1.getMin())); + ensure("2nd insert MMM has 3.0 max", (3.0 == m1.getMax())); + ensure_approximately_equals("2nd insert MMM has 2.0 mean", m1.getMean(), lcl_float(2.0), 1); + + // Third insert + m1.record(5.0); + ensure("3rd insert MMM has 3 count", (3 == m1.getCount())); + ensure("3rd insert MMM has 1.0 min", (1.0 == m1.getMin())); + ensure("3rd insert MMM has 5.0 max", (5.0 == m1.getMax())); + ensure_approximately_equals("3rd insert MMM has 3.0 mean", m1.getMean(), lcl_float(3.0), 1); + + // Fourth insert + m1.record(1000000.0); + ensure("4th insert MMM has 4 count", (4 == m1.getCount())); + ensure("4th insert MMM has 1.0 min", (1.0 == m1.getMin())); + ensure("4th insert MMM has 1000000.0 max", (1000000.0 == m1.getMax())); + ensure_approximately_equals("4th insert MMM has 250002.0 mean", m1.getMean(), lcl_float(250002.0), 1); + + // Reset + m1.reset(); + ensure("Reset MMM has 0 count", (0 == m1.getCount())); + ensure("Reset MMM has 0 min", (zero == m1.getMin())); + ensure("Reset MMM has 0 max", (zero == m1.getMax())); + ensure("Reset MMM has 0 mean no div-by-zero", (zero == m1.getMean())); + } + + // Testing LLSimpleStatMMM's response to large values + template<> template<> + void stat_counter_index_object_t::test<9>() + { + LLSimpleStatMMM m1; + typedef LLSimpleStatMMM::Value lcl_float; + lcl_float zero(0); + + // Insert overflowing values + const lcl_float bignum(F64_MAX / 2); + + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(zero); + + ensure("Overflowed MMM has 8 count", (8 == m1.getCount())); + ensure("Overflowed MMM has 0 min", (zero == m1.getMin())); + ensure("Overflowed MMM has huge max", (bignum == m1.getMax())); + ensure("Overflowed MMM has fetchable mean", (1.0 == m1.getMean() || true)); + // We should be infinte but not interested in proving the IEEE standard here. + LLSD sd1(m1.getMean()); + // std::cout << "Thingy: " << m1.getMean() << " and as LLSD: " << sd1 << std::endl; + ensure("Overflowed MMM produces LLSDable Real", (sd1.isReal())); + } + + // Testing LLSimpleStatMMM's external behavior + template<> template<> + void stat_counter_index_object_t::test<10>() + { + LLSimpleStatMMM m1; + typedef LLSimpleStatMMM::Value lcl_int; + lcl_int zero(0); + + // Freshly-constructed + ensure("Constructed MMM has 0 count", (0 == m1.getCount())); + ensure("Constructed MMM has 0 min", (zero == m1.getMin())); + ensure("Constructed MMM has 0 max", (zero == m1.getMax())); + ensure("Constructed MMM has 0 mean no div-by-zero", (zero == m1.getMean())); + + // Single insert + m1.record(1); + ensure("Single insert MMM has 1 count", (1 == m1.getCount())); + ensure("Single insert MMM has 1 min", (1 == m1.getMin())); + ensure("Single insert MMM has 1 max", (1 == m1.getMax())); + ensure("Single insert MMM has 1 mean", (1 == m1.getMean())); + + // Second insert + m1.record(3); + ensure("2nd insert MMM has 2 count", (2 == m1.getCount())); + ensure("2nd insert MMM has 1 min", (1 == m1.getMin())); + ensure("2nd insert MMM has 3 max", (3 == m1.getMax())); + ensure("2nd insert MMM has 2 mean", (2 == m1.getMean())); + + // Third insert + m1.record(5); + ensure("3rd insert MMM has 3 count", (3 == m1.getCount())); + ensure("3rd insert MMM has 1 min", (1 == m1.getMin())); + ensure("3rd insert MMM has 5 max", (5 == m1.getMax())); + ensure("3rd insert MMM has 3 mean", (3 == m1.getMean())); + + // Fourth insert + m1.record(U64L(1000000000000)); + ensure("4th insert MMM has 4 count", (4 == m1.getCount())); + ensure("4th insert MMM has 1 min", (1 == m1.getMin())); + ensure("4th insert MMM has 1000000000000ULL max", (U64L(1000000000000) == m1.getMax())); + ensure("4th insert MMM has 250000000002ULL mean", (U64L( 250000000002) == m1.getMean())); + + // Reset + m1.reset(); + ensure("Reset MMM has 0 count", (0 == m1.getCount())); + ensure("Reset MMM has 0 min", (zero == m1.getMin())); + ensure("Reset MMM has 0 max", (zero == m1.getMax())); + ensure("Reset MMM has 0 mean no div-by-zero", (zero == m1.getMean())); + } + + // Testing LLSimpleStatMMM's response to large values + template<> template<> + void stat_counter_index_object_t::test<11>() + { + LLSimpleStatMMM m1; + typedef LLSimpleStatMMM::Value lcl_int; + lcl_int zero(0); + + // Insert overflowing values + const lcl_int bignum(U64L(0xffffffffffffffff) / 2); + + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(zero); + + ensure("Overflowed MMM has 8 count", (8 == m1.getCount())); + ensure("Overflowed MMM has 0 min", (zero == m1.getMin())); + ensure("Overflowed MMM has huge max", (bignum == m1.getMax())); + ensure("Overflowed MMM has fetchable mean", (zero == m1.getMean() || true)); + } +} -- cgit v1.2.3