/* * Copyright (c) 2021 Daniel R. Carvalho * All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer; * redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution; * neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "base/gtest/cur_tick_fake.hh" #include "base/gtest/logging.hh" #include "base/stats/storage.hh" using namespace gem5; // Instantiate the fake class to have a valid curTick of 0 GTestTickHandler tickHandler; /** Increases the current tick by one. */ void increaseTick() { tickHandler.setCurTick(curTick() + 1); } /** A pair of value and its number of samples, used for sampling. */ struct ValueSamples { statistics::Counter value; statistics::Counter numSamples; ValueSamples(statistics::Counter value, statistics::Counter num_samples) : value(value), numSamples(num_samples) { } }; /** Test setting and getting a value to the storage. */ TEST(StatsStatStorTest, SetValueResult) { statistics::StatStor stor(nullptr); statistics::Counter val; val = 10; stor.set(val); ASSERT_EQ(stor.value(), val); ASSERT_EQ(stor.result(), statistics::Result(val)); val = 1234; stor.set(val); ASSERT_EQ(stor.value(), val); ASSERT_EQ(stor.result(), statistics::Result(val)); } /** Test if prepare does not change the value. */ TEST(StatsStatStorTest, Prepare) { statistics::StatStor stor(nullptr); statistics::Counter val; val = 10; stor.set(val); stor.prepare(nullptr); ASSERT_EQ(stor.value(), val); ASSERT_EQ(stor.result(), statistics::Result(val)); } /** Test whether incrementing and decrementing work as expected. */ TEST(StatsStatStorTest, IncDec) { statistics::StatStor stor(nullptr); statistics::Counter diff_val = 10; statistics::Counter val = 0; stor.inc(diff_val); val += diff_val; ASSERT_EQ(stor.value(), val); stor.inc(diff_val); val += diff_val; ASSERT_EQ(stor.value(), val); stor.dec(diff_val); val -= diff_val; ASSERT_EQ(stor.value(), val); stor.dec(diff_val); val -= diff_val; ASSERT_EQ(stor.value(), val); } /** * Test whether zero is correctly set as the reset value. The test order is * to check if it is initially zero on creation, then it is made non zero, * and finally reset to zero. */ TEST(StatsStatStorTest, ZeroReset) { statistics::StatStor stor(nullptr); statistics::Counter val = 10; ASSERT_TRUE(stor.zero()); stor.reset(nullptr); ASSERT_TRUE(stor.zero()); stor.reset(nullptr); stor.inc(val); ASSERT_FALSE(stor.zero()); } /** Test setting and getting a value to the storage. */ TEST(StatsAvgStorTest, SetValueResult) { statistics::AvgStor stor(nullptr); statistics::Counter val; statistics::Result total = 0; Tick last_reset = 0; Tick last_tick = 0; val = 10; stor.set(val); last_tick = curTick(); ASSERT_EQ(stor.value(), val); ASSERT_EQ(stor.result(), statistics::Result(total + val) / statistics::Result(curTick() - last_reset + 1)); increaseTick(); total += val * (curTick() - last_tick); val = 1234; stor.set(val); last_tick = curTick(); ASSERT_EQ(stor.value(), val); ASSERT_EQ(stor.result(), statistics::Result(total + val) / statistics::Result(curTick() - last_reset + 1)); increaseTick(); } #if TRACING_ON /** * Test whether getting the result in a different tick triggers an assertion. */ TEST(StatsAvgStorDeathTest, Result) { statistics::AvgStor stor(nullptr); increaseTick(); ASSERT_DEATH(stor.result(), ".+"); } #endif /** * Test whether getting the result in a different tick does not trigger an * assertion if storage is prepared. */ TEST(StatsAvgStorTest, Prepare) { statistics::AvgStor stor(nullptr); statistics::Counter val = 10; statistics::Result total = 0; Tick last_reset = 0; Tick last_tick = 0; val = 10; stor.set(val); last_tick = curTick(); ASSERT_EQ(stor.value(), val); ASSERT_EQ(stor.result(), statistics::Result(total + val) / statistics::Result(curTick() - last_reset + 1)); increaseTick(); total += val * (curTick() - last_tick); stor.prepare(nullptr); last_tick = curTick(); ASSERT_EQ(stor.value(), val); ASSERT_EQ(stor.result(), statistics::Result(total + val) / statistics::Result(curTick() - last_reset + 1)); increaseTick(); } /** Test whether incrementing and decrementing work as expected. */ TEST(StatsAvgStorTest, IncDec) { statistics::AvgStor stor(nullptr); statistics::Counter diff_val = 10; statistics::Counter val = 0; stor.set(diff_val); val += diff_val; ASSERT_EQ(stor.value(), val); stor.inc(diff_val); val += diff_val; ASSERT_EQ(stor.value(), val); stor.inc(diff_val); val += diff_val; ASSERT_EQ(stor.value(), val); stor.dec(diff_val); val -= diff_val; ASSERT_EQ(stor.value(), val); stor.dec(diff_val); val -= diff_val; ASSERT_EQ(stor.value(), val); } /** * Test whether zero is correctly set as the reset value. The test order is * to check if it is initially zero on creation, then it is made non zero, * and finally reset to zero. */ TEST(StatsAvgStorTest, ZeroReset) { statistics::AvgStor stor(nullptr); statistics::Counter val = 10; ASSERT_TRUE(stor.zero()); stor.reset(nullptr); ASSERT_TRUE(stor.zero()); // Set current value to val, reset total and increase tick, so that the // next call to set will update the total to be different from zero stor.inc(val); stor.reset(nullptr); increaseTick(); stor.inc(val); ASSERT_FALSE(stor.zero()); } #if TRACING_ON /** Test that an assertion is thrown when bucket size is 0. */ TEST(StatsDistStorDeathTest, BucketSize0) { EXPECT_ANY_THROW(statistics::DistStor::Params params(0, 5, 0)); } #endif /** * Test whether zero is correctly set as the reset value. The test order is * to check if it is initially zero on creation, then it is made non zero, * and finally reset to zero. */ TEST(StatsDistStorTest, ZeroReset) { statistics::DistStor::Params params(0, 99, 10); statistics::DistStor stor(¶ms); statistics::Counter val = 10; statistics::Counter num_samples = 5; ASSERT_TRUE(stor.zero()); stor.reset(¶ms); stor.sample(val, num_samples); ASSERT_FALSE(stor.zero()); stor.reset(¶ms); ASSERT_TRUE(stor.zero()); } /** * Test that the size of this storage is equal to its counters vector's size, * and that after it has been set, nothing can modify it. */ TEST(StatsDistStorTest, Size) { statistics::Counter val = 10; statistics::Counter num_samples = 5; statistics::Counter size = 20; statistics::DistData data; statistics::DistStor::Params params(0, 19, 1); statistics::DistStor stor(¶ms); ASSERT_EQ(stor.size(), size); stor.sample(val, num_samples); ASSERT_EQ(stor.size(), size); stor.prepare(¶ms, data); ASSERT_EQ(stor.size(), size); stor.reset(¶ms); ASSERT_EQ(stor.size(), size); stor.zero(); ASSERT_EQ(stor.size(), size); } /** * Compare both dist datas to see if their contents match. * * @param data The data being tested. * @param expected_data The ground truth. * @param no_log Whether log should not be compared. */ void checkExpectedDistData(const statistics::DistData& data, const statistics::DistData& expected_data, bool no_log = true) { ASSERT_EQ(data.type, expected_data.type); ASSERT_EQ(data.min, expected_data.min); ASSERT_EQ(data.max, expected_data.max); ASSERT_EQ(data.bucket_size, expected_data.bucket_size); ASSERT_EQ(data.min_val, expected_data.min_val); ASSERT_EQ(data.max_val, expected_data.max_val); ASSERT_EQ(data.sum, expected_data.sum); ASSERT_EQ(data.squares, expected_data.squares); if (!no_log) { ASSERT_EQ(data.logs, expected_data.logs); } ASSERT_EQ(data.samples, expected_data.samples); ASSERT_EQ(data.cvec.size(), expected_data.cvec.size()); for (int i = 0; i < expected_data.cvec.size(); i++) { ASSERT_EQ(data.cvec[i], expected_data.cvec[i]); } } /** * Auxiliary function that finishes preparing the DistStor's expected values, * perform the calls to the storage's sample, and compares the expected data. * * @param params The params containing the number of buckets. * @param values The value-num_sample pairs to be sampled. * @param num_values Number of values in the values array. * @param expected_data Expected data after sampling, with the following values * setup to the expected values: bucket_size, min, max_val, and cvec. */ void prepareCheckDistStor(statistics::DistStor::Params& params, ValueSamples* values, int num_values, statistics::DistData& expected_data) { statistics::DistStor stor(¶ms); statistics::Counter val; statistics::DistData data; expected_data.min = params.min; expected_data.max = params.max; expected_data.sum = 0; expected_data.squares = 0; expected_data.logs = 0; expected_data.samples = 0; // Populate storage with more data for (int i = 0; i < num_values; i++) { stor.sample(values[i].value, values[i].numSamples); val = values[i].value * values[i].numSamples; expected_data.sum += val; expected_data.squares += values[i].value * val; expected_data.samples += values[i].numSamples; } stor.prepare(¶ms, data); // DistStor does not use log checkExpectedDistData(data, expected_data, true); } /** Test setting and getting value from storage. */ TEST(StatsDistStorTest, SamplePrepareSingle) { statistics::DistStor::Params params(0, 99, 5); ValueSamples values[] = {{10, 5}}; int num_values = sizeof(values) / sizeof(ValueSamples); // Setup expected data statistics::DistData expected_data; expected_data.type = statistics::Dist; expected_data.bucket_size = params.bucket_size; expected_data.underflow = 0; expected_data.overflow = 0; expected_data.min_val = 10; expected_data.max_val = 10; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); expected_data.cvec[2] = 5; prepareCheckDistStor(params, values, num_values, expected_data); } /** Test setting and getting value from storage with multiple values. */ TEST(StatsDistStorTest, SamplePrepareMultiple) { statistics::DistStor::Params params(0, 99, 5); // There are 20 buckets: [0,5[, [5,10[, [10,15[, ..., [95,100[. // We test that values that pass the maximum bucket value (1234, 12345678, // 100) are added to the overflow counter, and that the ones below the // minimum bucket value (-10, -1) are added to the underflow counter. // The extremes (0 and 99) are added to check if they go to the first and // last buckets. ValueSamples values[] = {{10, 5}, {1234, 2}, {12345678, 99}, {-10, 4}, {17, 17}, {52, 63}, {18, 11}, {0, 1}, {99, 15}, {-1, 200}, {100, 50}}; int num_values = sizeof(values) / sizeof(ValueSamples); // Setup variables that should always match params' values statistics::DistData expected_data; expected_data.type = statistics::Dist; expected_data.min_val = -10; expected_data.max_val = 12345678; expected_data.bucket_size = params.bucket_size; expected_data.underflow = 204; expected_data.overflow = 151; expected_data.sum = 0; expected_data.squares = 0; expected_data.samples = 0; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); expected_data.cvec[0] = 1; expected_data.cvec[2] = 5; expected_data.cvec[3] = 17+11; expected_data.cvec[10] = 63; expected_data.cvec[19] = 15; prepareCheckDistStor(params, values, num_values, expected_data); } /** Test resetting storage. */ TEST(StatsDistStorTest, Reset) { statistics::DistStor::Params params(0, 99, 5); statistics::DistStor stor(¶ms); // Populate storage with random samples ValueSamples values[] = {{10, 5}, {1234, 2}, {12345678, 99}, {-10, 4}, {17, 17}, {52, 63}, {18, 11}, {0, 1}, {99, 15}, {-1, 200}, {100, 50}}; int num_values = sizeof(values) / sizeof(ValueSamples); for (int i = 0; i < num_values; i++) { stor.sample(values[i].value, values[i].numSamples); } // Reset storage, and make sure all data has been cleared stor.reset(¶ms); statistics::DistData data; stor.prepare(¶ms, data); statistics::DistData expected_data; expected_data.type = statistics::Dist; expected_data.bucket_size = params.bucket_size; expected_data.underflow = 0; expected_data.overflow = 0; expected_data.min = params.min; expected_data.max = params.max; expected_data.min_val = 0; expected_data.max_val = 0; expected_data.sum = 0; expected_data.squares = 0; expected_data.samples = 0; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); checkExpectedDistData(data, expected_data, true); } #if TRACING_ON /** Test that an assertion is thrown when not enough buckets are provided. */ TEST(StatsHistStorDeathTest, NotEnoughBuckets0) { EXPECT_ANY_THROW(statistics::HistStor::Params params(0)); } /** Test that an assertion is thrown when not enough buckets are provided. */ TEST(StatsHistStorDeathTest, NotEnoughBuckets1) { EXPECT_ANY_THROW(statistics::HistStor::Params params(1)); } #endif /** * Test whether zero is correctly set as the reset value. The test order is * to check if it is initially zero on creation, then it is made non zero, * and finally reset to zero. */ TEST(StatsHistStorTest, ZeroReset) { statistics::HistStor::Params params(10); statistics::HistStor stor(¶ms); statistics::Counter val = 10; statistics::Counter num_samples = 5; ASSERT_TRUE(stor.zero()); stor.reset(¶ms); stor.sample(val, num_samples); ASSERT_FALSE(stor.zero()); stor.reset(¶ms); ASSERT_TRUE(stor.zero()); } /** * Test that the size of this storage is equal to its counters vector's size, * and that after it has been set, nothing can modify it. */ TEST(StatsHistStorTest, Size) { statistics::Counter val = 10; statistics::Counter num_samples = 5; statistics::DistData data; statistics::size_type sizes[] = {2, 10, 1234}; for (int i = 0; i < (sizeof(sizes) / sizeof(statistics::size_type)); i++) { statistics::HistStor::Params params(sizes[i]); statistics::HistStor stor(¶ms); ASSERT_EQ(stor.size(), sizes[i]); stor.sample(val, num_samples); ASSERT_EQ(stor.size(), sizes[i]); stor.prepare(¶ms, data); ASSERT_EQ(stor.size(), sizes[i]); stor.reset(¶ms); ASSERT_EQ(stor.size(), sizes[i]); stor.zero(); ASSERT_EQ(stor.size(), sizes[i]); } } /** * Auxiliary function that finishes preparing the HistStor's expected values, * perform the calls to the storage's sample, and compares the expected data. * * @param params The params containing the number of buckets. * @param values The value-num_sample pairs to be sampled. * @param num_values Number of values in the values array. * @param expected_data Expected data after sampling, with the following values * setup to the expected values: bucket_size, min, max_val, and cvec. */ void prepareCheckHistStor(statistics::HistStor::Params& params, ValueSamples* values, int num_values, statistics::DistData& expected_data) { statistics::HistStor stor(¶ms); statistics::Counter val; statistics::DistData data; bool no_log = false; expected_data.min_val = expected_data.min; expected_data.max = expected_data.max_val + expected_data.bucket_size - 1; expected_data.sum = 0; expected_data.squares = 0; expected_data.logs = 0; expected_data.samples = 0; // Populate storage with more data for (int i = 0; i < num_values; i++) { stor.sample(values[i].value, values[i].numSamples); val = values[i].value * values[i].numSamples; expected_data.sum += val; expected_data.squares += values[i].value * val; if (values[i].value < 0) { // Negative values don't have log, so mark log check to be skipped no_log = true; } else { expected_data.logs += std::log(values[i].value) * values[i].numSamples; } expected_data.samples += values[i].numSamples; } stor.prepare(¶ms, data); checkExpectedDistData(data, expected_data, no_log); } /** * Test samples that fit in the initial buckets, and therefore do not need * to grow up. */ TEST(StatsHistStorTest, SamplePrepareFit) { statistics::HistStor::Params params(4); // Setup expected data for the hand-carved values given. The final buckets // will be divided at: // Bkt0=[0,1[ , Bkt1=[1,2[, Bkt2=[2,3[, Bkt3=[3,4[ ValueSamples values[] = {{0, 5}, {1, 2}, {2, 99}, {3, 4}}; const int num_values = sizeof(values) / sizeof(ValueSamples); statistics::DistData expected_data; expected_data.type = statistics::Hist; expected_data.bucket_size = 1; expected_data.min = 0; expected_data.max_val = 3; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); expected_data.cvec[0] = 5; expected_data.cvec[1] = 2; expected_data.cvec[2] = 99; expected_data.cvec[3] = 4; prepareCheckHistStor(params, values, num_values, expected_data); } /** * Test samples that do not fit in the initial buckets, and therefore have * to grow up once. */ TEST(StatsHistStorTest, SamplePrepareSingleGrowUp) { statistics::HistStor::Params params(4); // Setup expected data for the hand-carved values given. Since there // are four buckets, and the highest value is 4, the bucket size will // grow to be 2. The final buckets will be divided at: // Bkt0=[0,2[ , Bkt1=[2,4[, Bkt2=[4,6[, Bkt3=[6,8[ ValueSamples values[] = {{0, 5}, {1, 2}, {2, 99}, {4, 4}}; const int num_values = sizeof(values) / sizeof(ValueSamples); statistics::DistData expected_data; expected_data.type = statistics::Hist; expected_data.bucket_size = 2; expected_data.min = 0; expected_data.max_val = 6; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); expected_data.cvec[0] = 5+2; expected_data.cvec[1] = 99; expected_data.cvec[2] = 4; expected_data.cvec[3] = 0; prepareCheckHistStor(params, values, num_values, expected_data); } /** * Test samples that do not fit in the initial buckets, and therefore have * to grow up a few times. */ TEST(StatsHistStorTest, SamplePrepareMultipleGrowUp) { statistics::HistStor::Params params(4); // Setup expected data for the hand-carved values given. Since there // are four buckets, and the highest value is 4, the bucket size will // grow thrice to become 8. The final buckets will be divided at: // Bkt0=[0,8[ , Bkt1=[8,16[, Bkt2=[16,24[, Bkt3=[24,32[ ValueSamples values[] = {{0, 5}, {1, 2}, {2, 99}, {16, 4}}; const int num_values = sizeof(values) / sizeof(ValueSamples); statistics::DistData expected_data; expected_data.type = statistics::Hist; expected_data.bucket_size = 8; expected_data.min = 0; expected_data.max_val = 24; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); expected_data.cvec[0] = 5+2+99; expected_data.cvec[1] = 0; expected_data.cvec[2] = 4; expected_data.cvec[3] = 0; prepareCheckHistStor(params, values, num_values, expected_data); } /** * Test samples that have a negative value, and therefore do not fit in the * initial buckets. Since this involves using negative values, the logs * become irrelevant. */ TEST(StatsHistStorTest, SamplePrepareGrowDownOddBuckets) { statistics::HistStor::Params params(5); // Setup expected data for the hand-carved values given. Since there // is a negative value, the min bucket will change, and the bucket size // will grow to be 2. The final buckets will be divided at: // Bkt0=[-4,-2[ , Bkt1=[-2,-0[, Bkt2=[0,2[, Bkt3=[2,4[, Bkt4=[4,6[ ValueSamples values[] = {{0, 5}, {1, 2}, {2, 99}, {3, 12}, {4, 33}, {-1, 4}}; const int num_values = sizeof(values) / sizeof(ValueSamples); statistics::DistData expected_data; expected_data.type = statistics::Hist; expected_data.bucket_size = 2; expected_data.min = -4; expected_data.max_val = 4; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); expected_data.cvec[0] = 0; expected_data.cvec[1] = 4; expected_data.cvec[2] = 5+2; expected_data.cvec[3] = 99+12; expected_data.cvec[4] = 33; prepareCheckHistStor(params, values, num_values, expected_data); } /** * Test samples that have a negative value, and therefore do not fit in the * initial buckets. Since this involves using negative values, the logs * become irrelevant. */ TEST(StatsHistStorTest, SamplePrepareGrowDownEvenBuckets) { statistics::HistStor::Params params(4); // Setup expected data for the hand-carved values given. Since there // is a negative value, the min bucket will change, and the bucket size // will grow to be 2. The final buckets will be divided at: // Bkt0=[-4,-2[ , Bkt1=[-2,0[, Bkt2=[0,2[, Bkt3=[2,4[ ValueSamples values[] = {{0, 5}, {1, 2}, {2, 99}, {-1, 4}}; const int num_values = sizeof(values) / sizeof(ValueSamples); statistics::DistData expected_data; expected_data.type = statistics::Hist; expected_data.bucket_size = 2; expected_data.min = -4; expected_data.max_val = 2; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); expected_data.cvec[0] = 0; expected_data.cvec[1] = 4; expected_data.cvec[2] = 5+2; expected_data.cvec[3] = 99; prepareCheckHistStor(params, values, num_values, expected_data); } /** * Test samples that have one low negative value, and therefore do not fit * in the initial buckets and have to grow down a few times. Since this * involves using negative values, the logs become irrelevant. */ TEST(StatsHistStorTest, SamplePrepareGrowDownGrowOutOddBuckets) { statistics::HistStor::Params params(5); // Setup expected data for the hand-carved values given. Since there // is a negative value, the min bucket will change, and the bucket size // will grow to be 8. The final buckets will be divided at: // Bkt0=[-16,-8[ , Bkt1=[-8,0[, Bkt2=[0,8[, Bkt3=[8,16[, Bkt4=[16,24[ ValueSamples values[] = {{0, 5}, {1, 2}, {2, 99}, {3, 12}, {4, 33}, {-12, 4}}; const int num_values = sizeof(values) / sizeof(ValueSamples); statistics::DistData expected_data; expected_data.type = statistics::Hist; expected_data.bucket_size = 8; expected_data.min = -16; expected_data.max_val = 16; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); expected_data.cvec[0] = 4; expected_data.cvec[1] = 0; expected_data.cvec[2] = 5+2+99+12+33; expected_data.cvec[3] = 0; expected_data.cvec[4] = 0; prepareCheckHistStor(params, values, num_values, expected_data); } /** * Test samples that have one low negative value, and therefore do not fit * in the initial buckets and have to grow down a few times. Since this * involves using negative values, the logs become irrelevant. */ TEST(StatsHistStorTest, SamplePrepareGrowDownGrowOutEvenBuckets) { statistics::HistStor::Params params(4); // Setup expected data for the hand-carved values given. Since there // is a negative value, the min bucket will change, and the bucket size // will grow to be 8. The final buckets will be divided at: // Bkt0=[-16,-8[ , Bkt1=[-8,0[, Bkt2=[0,8[, Bkt3=[8,16[ ValueSamples values[] = {{0, 5}, {1, 2}, {2, 99}, {3, 12}, {-12, 4}}; const int num_values = sizeof(values) / sizeof(ValueSamples); statistics::DistData expected_data; expected_data.type = statistics::Hist; expected_data.bucket_size = 8; expected_data.min = -16; expected_data.max_val = 8; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); expected_data.cvec[0] = 4; expected_data.cvec[1] = 0; expected_data.cvec[2] = 5+2+99+12; expected_data.cvec[3] = 0; prepareCheckHistStor(params, values, num_values, expected_data); } /** * Test a complex sample set with negative values, and therefore multiple * grows will happen. Since this involves using negative values, the logs * become irrelevant. */ TEST(StatsHistStorTest, SamplePrepareMultipleGrowOddBuckets) { statistics::HistStor::Params params(5); // Setup expected data for the hand-carved values given. This adds quite // a few positive and negative samples, and the bucket size will grow to // be 64. The final buckets will be divided at: // Bkt0=[-128,-64[ , Bkt1=[-64,0[, Bkt2=[0,64[, Bkt3=[64,128[, // Bkt4=[128,192[ ValueSamples values[] = {{0, 5}, {7, 2}, {31, 99}, {-8, 12}, {127, 4}, {-120, 53}, {-50, 1}}; const int num_values = sizeof(values) / sizeof(ValueSamples); statistics::DistData expected_data; expected_data.type = statistics::Hist; expected_data.bucket_size = 64; expected_data.min = -128; expected_data.max_val = 128; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); expected_data.cvec[0] = 53; expected_data.cvec[1] = 12+1; expected_data.cvec[2] = 5+2+99; expected_data.cvec[3] = 4; expected_data.cvec[4] = 0; prepareCheckHistStor(params, values, num_values, expected_data); } /** * Test a complex sample set with negative values, and therefore multiple * grows will happen. Since this involves using negative values, the logs * become irrelevant. */ TEST(StatsHistStorTest, SamplePrepareMultipleGrowEvenBuckets) { statistics::HistStor::Params params(4); // Setup expected data for the hand-carved values given. This adds quite // a few positive and negative samples, and the bucket size will grow to // be 64. The final buckets will be divided at: // Bkt0=[-128,-64[ , Bkt1=[-64,0[, Bkt2=[0,64[, Bkt3=[64,128[ ValueSamples values[] = {{0, 5}, {7, 2}, {31, 99}, {-8, 12}, {127, 4}, {-120, 53}, {-50, 1}}; const int num_values = sizeof(values) / sizeof(ValueSamples); statistics::DistData expected_data; expected_data.type = statistics::Hist; expected_data.bucket_size = 64; expected_data.min = -128; expected_data.max_val = 64; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); expected_data.cvec[0] = 53; expected_data.cvec[1] = 12+1; expected_data.cvec[2] = 5+2+99; expected_data.cvec[3] = 4; prepareCheckHistStor(params, values, num_values, expected_data); } /** Test resetting storage. */ TEST(StatsHistStorTest, Reset) { statistics::HistStor::Params params(4); statistics::HistStor stor(¶ms); // Setup expected data for the hand-carved values given. This adds quite // a few positive and negative samples, and the bucket size will grow to // be 64. The final buckets will be divided at: // Bkt0=[-128,-64[ , Bkt1=[-64,0[, Bkt2=[0,64[, Bkt3=[64,128[ ValueSamples values[] = {{0, 5}, {7, 2}, {31, 99}, {-8, 12}, {127, 4}, {-120, 53}, {-50, 1}}; const int num_values = sizeof(values) / sizeof(ValueSamples); for (int i = 0; i < num_values; i++) { stor.sample(values[i].value, values[i].numSamples); } // Reset storage, and make sure all data has been cleared: // Bkt0=[0,1[ , Bkt1=[1,2[, Bkt2=[2,3[, Bkt3=[3,4[ stor.reset(¶ms); statistics::DistData expected_data; expected_data.type = statistics::Hist; expected_data.bucket_size = 1; expected_data.min = 0; expected_data.max_val = 3; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); prepareCheckHistStor(params, values, 0, expected_data); } #if TRACING_ON /** Test whether adding storages with different sizes triggers an assertion. */ TEST(StatsHistStorDeathTest, AddDifferentSize) { statistics::HistStor::Params params(4); statistics::HistStor stor(¶ms); statistics::HistStor::Params params2(5); statistics::HistStor stor2(¶ms2); ASSERT_DEATH(stor.add(&stor2), ".+"); } /** Test whether adding storages with different min triggers an assertion. */ TEST(StatsHistStorDeathTest, AddDifferentMin) { statistics::HistStor::Params params(4); statistics::HistStor stor(¶ms); stor.sample(-1, 3); // On creation, the storage's min is zero statistics::HistStor::Params params2(4); statistics::HistStor stor2(¶ms2); ASSERT_DEATH(stor.add(&stor2), ".+"); } #endif /** Test merging two histograms. */ TEST(StatsHistStorTest, Add) { statistics::HistStor::Params params(4); // Setup first storage. Buckets are: // Bkt0=[0,16[, Bkt1=[16,32[, Bkt2=[32,48[, Bkt3=[58,64[ statistics::HistStor stor(¶ms); ValueSamples values[] = {{0, 5}, {3, 2}, {20, 37}, {32, 18}}; int num_values = sizeof(values) / sizeof(ValueSamples); for (int i = 0; i < num_values; i++) { stor.sample(values[i].value, values[i].numSamples); } statistics::DistData data; stor.prepare(¶ms, data); // Setup second storage. Buckets are: // Bkt0=[0,32[, Bkt1=[32,64[, Bkt2=[64,96[, Bkt3=[96,128[ statistics::HistStor stor2(¶ms); ValueSamples values2[] = {{10, 10}, {0, 1}, {80, 4}, {17, 100}, {95, 79}}; int num_values2 = sizeof(values2) / sizeof(ValueSamples); for (int i = 0; i < num_values2; i++) { stor2.sample(values2[i].value, values2[i].numSamples); } statistics::DistData data2; stor2.prepare(¶ms, data2); // Perform the merge stor.add(&stor2); statistics::DistData merge_data; stor.prepare(¶ms, merge_data); // Setup expected data. Buckets are: // Bkt0=[0,32[, Bkt1=[32,64[, Bkt2=[64,96[, Bkt3=[96,128[ statistics::DistData expected_data; expected_data.type = statistics::Hist; expected_data.bucket_size = 32; expected_data.min = 0; expected_data.max = 127; expected_data.min_val = 0; expected_data.max_val = 96; expected_data.cvec.clear(); expected_data.cvec.resize(params.buckets); expected_data.cvec[0] = 5+2+37+10+1+100; expected_data.cvec[1] = 18; expected_data.cvec[2] = 4+79; expected_data.cvec[3] = 0; expected_data.sum = data.sum + data2.sum; expected_data.squares = data.squares + data2.squares; expected_data.logs = data.squares + data2.logs; expected_data.samples = data.samples + data2.samples; // Compare results checkExpectedDistData(merge_data, expected_data, false); } /** * Test whether zero is correctly set as the reset value. The test order is * to check if it is initially zero on creation, then it is made non zero, * and finally reset to zero. */ TEST(StatsSampleStorTest, ZeroReset) { statistics::SampleStor stor(nullptr); statistics::Counter val = 10; statistics::Counter num_samples = 5; ASSERT_TRUE(stor.zero()); stor.reset(nullptr); stor.sample(val, num_samples); ASSERT_FALSE(stor.zero()); stor.reset(nullptr); ASSERT_TRUE(stor.zero()); } /** Test setting and getting value from storage. */ TEST(StatsSampleStorTest, SamplePrepare) { statistics::SampleStor stor(nullptr); ValueSamples values[] = {{10, 5}, {1234, 2}, {0xFFFFFFFF, 18}}; int num_values = sizeof(values) / sizeof(ValueSamples); statistics::Counter val; statistics::DistData data; statistics::DistData expected_data; statistics::SampleStor::Params params; // Simple test with one value being sampled stor.sample(values[0].value, values[0].numSamples); stor.prepare(¶ms, data); val = values[0].value * values[0].numSamples; expected_data.type = statistics::Deviation; expected_data.sum = val; expected_data.squares = values[0].value * val; expected_data.samples = values[0].numSamples; ASSERT_EQ(data.type, expected_data.type); ASSERT_EQ(data.sum, expected_data.sum); ASSERT_EQ(data.squares, expected_data.squares); ASSERT_EQ(data.samples, expected_data.samples); // Reset storage, and make sure all data has been cleared expected_data.sum = 0; expected_data.squares = 0; expected_data.samples = 0; stor.reset(nullptr); stor.prepare(¶ms, data); ASSERT_EQ(data.type, expected_data.type); ASSERT_EQ(data.sum, expected_data.sum); ASSERT_EQ(data.squares, expected_data.squares); ASSERT_EQ(data.samples, expected_data.samples); // Populate storage with more data for (int i = 0; i < num_values; i++) { stor.sample(values[i].value, values[i].numSamples); val = values[i].value * values[i].numSamples; expected_data.sum += val; expected_data.squares += values[i].value * val; expected_data.samples += values[i].numSamples; } stor.prepare(¶ms, data); ASSERT_EQ(data.type, expected_data.type); ASSERT_EQ(data.sum, expected_data.sum); ASSERT_EQ(data.squares, expected_data.squares); ASSERT_EQ(data.samples, expected_data.samples); } /** The size is always 1, no matter which functions have been called. */ TEST(StatsSampleStorTest, Size) { statistics::SampleStor stor(nullptr); statistics::Counter val = 10; statistics::Counter num_samples = 5; statistics::DistData data; statistics::SampleStor::Params params; ASSERT_EQ(stor.size(), 1); stor.sample(val, num_samples); ASSERT_EQ(stor.size(), 1); stor.prepare(¶ms, data); ASSERT_EQ(stor.size(), 1); stor.reset(nullptr); ASSERT_EQ(stor.size(), 1); stor.zero(); ASSERT_EQ(stor.size(), 1); } /** * Test whether zero is correctly set as the reset value. The test order is * to check if it is initially zero on creation, then it is made non zero, * and finally reset to zero. */ TEST(StatsAvgSampleStorTest, ZeroReset) { statistics::AvgSampleStor stor(nullptr); statistics::Counter val = 10; statistics::Counter num_samples = 5; ASSERT_TRUE(stor.zero()); stor.reset(nullptr); stor.sample(val, num_samples); ASSERT_FALSE(stor.zero()); stor.reset(nullptr); ASSERT_TRUE(stor.zero()); } /** Test setting and getting value from storage. */ TEST(StatsAvgSampleStorTest, SamplePrepare) { statistics::AvgSampleStor stor(nullptr); ValueSamples values[] = {{10, 5}, {1234, 2}, {0xFFFFFFFF, 18}}; int num_values = sizeof(values) / sizeof(ValueSamples); statistics::Counter val; statistics::DistData data; statistics::DistData expected_data; statistics::AvgSampleStor::Params params; // Simple test with one value being sampled stor.sample(values[0].value, values[0].numSamples); stor.prepare(¶ms, data); val = values[0].value * values[0].numSamples; expected_data.type = statistics::Deviation; expected_data.sum = val; expected_data.squares = values[0].value * val; ASSERT_EQ(data.type, expected_data.type); ASSERT_EQ(data.sum, expected_data.sum); ASSERT_EQ(data.squares, expected_data.squares); ASSERT_EQ(data.samples, curTick()); increaseTick(); // Reset storage, and make sure all data has been cleared expected_data.sum = 0; expected_data.squares = 0; stor.reset(nullptr); stor.prepare(¶ms, data); ASSERT_EQ(data.type, expected_data.type); ASSERT_EQ(data.sum, expected_data.sum); ASSERT_EQ(data.squares, expected_data.squares); ASSERT_EQ(data.samples, curTick()); increaseTick(); // Populate storage with more data for (int i = 0; i < num_values; i++) { stor.sample(values[i].value, values[i].numSamples); val = values[i].value * values[i].numSamples; expected_data.sum += val; expected_data.squares += values[i].value * val; } stor.prepare(¶ms, data); ASSERT_EQ(data.type, expected_data.type); ASSERT_EQ(data.sum, expected_data.sum); ASSERT_EQ(data.squares, expected_data.squares); ASSERT_EQ(data.samples, curTick()); } /** The size is always 1, no matter which functions have been called. */ TEST(StatsAvgSampleStorTest, Size) { statistics::AvgSampleStor stor(nullptr); statistics::Counter val = 10; statistics::Counter num_samples = 5; statistics::DistData data; statistics::AvgSampleStor::Params params; ASSERT_EQ(stor.size(), 1); stor.sample(val, num_samples); ASSERT_EQ(stor.size(), 1); stor.prepare(¶ms, data); ASSERT_EQ(stor.size(), 1); stor.reset(nullptr); ASSERT_EQ(stor.size(), 1); stor.zero(); ASSERT_EQ(stor.size(), 1); } /** * Test whether zero is correctly set as the reset value. The test order is * to check if it is initially zero on creation, then it is made non zero, * and finally reset to zero. */ TEST(StatsSparseHistStorTest, ZeroReset) { statistics::SparseHistStor stor(nullptr); statistics::Counter val = 10; statistics::Counter num_samples = 5; ASSERT_TRUE(stor.zero()); stor.reset(nullptr); stor.sample(val, num_samples); ASSERT_FALSE(stor.zero()); stor.reset(nullptr); ASSERT_TRUE(stor.zero()); } /** Test setting and getting value from storage. */ TEST(StatsSparseHistStorTest, SamplePrepare) { statistics::SparseHistStor stor(nullptr); ValueSamples values[] = {{10, 5}, {1234, 2}, {0xFFFFFFFF, 18}}; int num_values = sizeof(values) / sizeof(ValueSamples); statistics::Counter total_samples; statistics::SparseHistData data; // Simple test with one value being sampled stor.sample(values[0].value, values[0].numSamples); stor.prepare(nullptr, data); ASSERT_EQ(stor.size(), 1); ASSERT_EQ(data.cmap.size(), 1); ASSERT_EQ(data.cmap[values[0].value], values[0].numSamples); ASSERT_EQ(data.samples, values[0].numSamples); // Reset storage, and make sure all data has been cleared stor.reset(nullptr); stor.prepare(nullptr, data); ASSERT_EQ(stor.size(), 0); ASSERT_EQ(data.cmap.size(), 0); ASSERT_EQ(data.samples, 0); // Populate storage with more data for (int i = 0; i < num_values; i++) { stor.sample(values[i].value, values[i].numSamples); } stor.prepare(nullptr, data); total_samples = 0; ASSERT_EQ(stor.size(), num_values); ASSERT_EQ(data.cmap.size(), num_values); for (int i = 0; i < num_values; i++) { ASSERT_EQ(data.cmap[values[i].value], values[i].numSamples); total_samples += values[i].numSamples; } ASSERT_EQ(data.samples, total_samples); }