blob: ccd200ae53256b951f8fc162200fd7e309ef6e1a [file] [log] [blame]
// Copyright 2011 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: morlovich@google.com (Maksim Orlovich)
#include "pagespeed/kernel/sharedmem/shared_mem_statistics_test_base.h"
#include "pagespeed/kernel/base/function.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/mock_message_handler.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/statistics.h"
#include "pagespeed/kernel/base/statistics_template.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_writer.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/sharedmem/shared_mem_test_base.h"
#include "pagespeed/kernel/util/platform.h"
namespace net_instaweb {
namespace {
const char kPrefix[] = "/prefix/";
const char kVar1[] = "v1";
const char kVar2[] = "num_flushes";
const char kHist1[] = "H1";
const char kHist2[] = "Html Time us Histogram";
// We cannot init the logger unless all stats are initialized.
const char kStatsLogFile[] = "";
} // namespace
const int64 SharedMemStatisticsTestBase::kLogIntervalMs = 3 * Timer::kSecondMs;
// Set this small for TestLogfileTrimming.
const int64 SharedMemStatisticsTestBase::kMaxLogfileSizeKb = 10;
SharedMemStatisticsTestBase::SharedMemStatisticsTestBase(
SharedMemTestEnv* test_env)
: thread_system_(Platform::CreateThreadSystem()),
handler_(thread_system_->NewMutex()),
test_env_(test_env),
shmem_runtime_(test_env->CreateSharedMemRuntime()) {
}
SharedMemStatisticsTestBase::SharedMemStatisticsTestBase()
: thread_system_(Platform::CreateThreadSystem()),
handler_(thread_system_->NewMutex()) {
}
void SharedMemStatisticsTestBase::SetUp() {
timer_.reset(
new MockTimer(thread_system_->NewMutex(), MockTimer::kApr_5_2010_ms));
file_system_.reset(new MemFileSystem(thread_system_.get(), timer_.get()));
stats_.reset(new SharedMemStatistics(
kLogIntervalMs, kMaxLogfileSizeKb, kStatsLogFile, false /* no logging */,
kPrefix, shmem_runtime_.get(), &handler_, file_system_.get(),
timer_.get()));
}
void SharedMemStatisticsTestBase::TearDown() {
stats_->GlobalCleanup(&handler_);
EXPECT_EQ(0, handler_.SeriousMessages());
}
bool SharedMemStatisticsTestBase::CreateChild(TestMethod method) {
Function* callback =
new MemberFunction0<SharedMemStatisticsTestBase>(method, this);
return test_env_->CreateChild(callback);
}
bool SharedMemStatisticsTestBase::AddVars(SharedMemStatistics* stats) {
UpDownCounter* v1 = stats->AddUpDownCounter(kVar1);
UpDownCounter* v2 = stats->AddUpDownCounter(kVar2);
return ((v1 != NULL) && (v2 != NULL));
}
bool SharedMemStatisticsTestBase::AddHistograms(SharedMemStatistics* stats) {
Histogram* hist1 = stats->AddHistogram(kHist1);
Histogram* hist2 = stats->AddHistogram(kHist2);
return ((hist1 != NULL) && (hist2 != NULL));
}
SharedMemStatistics* SharedMemStatisticsTestBase::ChildInit() {
scoped_ptr<SharedMemStatistics> stats(new SharedMemStatistics(
kLogIntervalMs, kMaxLogfileSizeKb, kStatsLogFile, false /* no logging */,
kPrefix, shmem_runtime_.get(), &handler_, file_system_.get(),
timer_.get()));
if (!AddVars(stats.get()) || !AddHistograms(stats.get())) {
test_env_->ChildFailed();
return NULL;
}
stats->Init(false, &handler_);
return stats.release();
}
void SharedMemStatisticsTestBase::ParentInit() {
EXPECT_TRUE(AddVars(stats_.get()));
EXPECT_TRUE(AddHistograms(stats_.get()));
stats_->Init(true, &handler_);
}
void SharedMemStatisticsTestBase::TestCreate() {
// Basic initialization/reading/cleanup test
ParentInit();
UpDownCounter* v1 = stats_->GetUpDownCounter(kVar1);
UpDownCounter* v2 = stats_->GetUpDownCounter(kVar2);
EXPECT_EQ(0, v1->Get());
EXPECT_EQ(0, v2->Get());
Histogram* hist1 = stats_->GetHistogram(kHist1);
Histogram* hist2 = stats_->GetHistogram(kHist2);
EXPECT_EQ(0, hist1->Maximum());
EXPECT_EQ(0, hist2->Maximum());
ASSERT_TRUE(CreateChild(&SharedMemStatisticsTestBase::TestCreateChild));
test_env_->WaitForChildren();
}
void SharedMemStatisticsTestBase::TestCreateChild() {
scoped_ptr<SharedMemStatistics> stats(ChildInit());
UpDownCounter* v1 = stats->GetUpDownCounter(kVar1);
Histogram* hist1 = stats->GetHistogram(kHist1);
stats->Init(false, &handler_);
UpDownCounter* v2 = stats->GetUpDownCounter(kVar2);
Histogram* hist2 = stats->GetHistogram(kHist2);
// We create one var & hist before SHM attach, one after for test coverage.
if (v1->Get() != 0 || hist1->Count() != 0) {
test_env_->ChildFailed();
}
if (v2->Get() != 0 || hist2->Count() != 0) {
test_env_->ChildFailed();
}
}
void SharedMemStatisticsTestBase::TestSet() {
// -> Set works as well, propagates right
ParentInit();
UpDownCounter* v1 = stats_->GetUpDownCounter(kVar1);
UpDownCounter* v2 = stats_->GetUpDownCounter(kVar2);
EXPECT_EQ(0, v1->Get());
EXPECT_EQ(0, v2->Get());
v1->Set(3);
v2->Set(17);
EXPECT_EQ(3, v1->Get());
EXPECT_EQ(17, v2->Get());
ASSERT_TRUE(CreateChild(&SharedMemStatisticsTestBase::TestSetChild));
test_env_->WaitForChildren();
EXPECT_EQ(3*3, v1->Get());
EXPECT_EQ(17*17, v2->Get());
}
void SharedMemStatisticsTestBase::TestSetChild() {
scoped_ptr<SharedMemStatistics> stats(ChildInit());
UpDownCounter* v1 = stats->GetUpDownCounter(kVar1);
stats->Init(false, &handler_);
UpDownCounter* v2 = stats->GetUpDownCounter(kVar2);
v1->Set(v1->Get() * v1->Get());
v2->Set(v2->Get() * v2->Get());
}
void SharedMemStatisticsTestBase::TestClear() {
// We can clear things from the kid
ParentInit();
UpDownCounter* v1 = stats_->GetUpDownCounter(kVar1);
UpDownCounter* v2 = stats_->GetUpDownCounter(kVar2);
EXPECT_EQ(0, v1->Get());
EXPECT_EQ(0, v2->Get());
v1->Set(3);
v2->Set(17);
EXPECT_EQ(3, v1->Get());
EXPECT_EQ(17, v2->Get());
Histogram* hist1 = stats_->GetHistogram(kHist1);
Histogram* hist2 = stats_->GetHistogram(kHist2);
EXPECT_EQ(0, hist1->Count());
EXPECT_EQ(0, hist2->Count());
hist1->Add(1);
hist2->Add(2);
hist2->Add(4);
EXPECT_EQ(1, hist1->Count());
EXPECT_EQ(2, hist2->Count());
EXPECT_EQ(1, hist1->Maximum());
EXPECT_EQ(2, hist2->Minimum());
ASSERT_TRUE(CreateChild(&SharedMemStatisticsTestBase::TestClearChild));
test_env_->WaitForChildren();
EXPECT_EQ(0, v1->Get());
EXPECT_EQ(0, v2->Get());
EXPECT_EQ(0, hist1->Count());
EXPECT_EQ(0, hist2->Count());
EXPECT_EQ(0, hist1->Maximum());
EXPECT_EQ(0, hist2->Minimum());
}
void SharedMemStatisticsTestBase::TestClearChild() {
scoped_ptr<SharedMemStatistics> stats(ChildInit());
// Double check the child process gets the data in Histogram before clears it.
Histogram* hist1 = stats->GetHistogram(kHist1);
Histogram* hist2 = stats->GetHistogram(kHist2);
EXPECT_EQ(1, hist1->Count());
EXPECT_EQ(2, hist2->Count());
EXPECT_EQ(1, hist1->Maximum());
EXPECT_EQ(2, hist2->Minimum());
stats->Init(false, &handler_);
stats->Clear();
}
void SharedMemStatisticsTestBase::TestAdd() {
ParentInit();
UpDownCounter* v1 = stats_->GetUpDownCounter(kVar1);
UpDownCounter* v2 = stats_->GetUpDownCounter(kVar2);
Histogram* hist1 = stats_->GetHistogram(kHist1);
Histogram* hist2 = stats_->GetHistogram(kHist2);
EXPECT_EQ(0, v1->Get());
EXPECT_EQ(0, v2->Get());
EXPECT_EQ(0, hist1->Count());
EXPECT_EQ(0, hist2->Count());
v1->Set(3);
v2->Set(17);
EXPECT_EQ(3, v1->Get());
EXPECT_EQ(17, v2->Get());
// We will add 10x 1 to v1, and 10x 2 to v2.
// Add 10x (1,2) to hist1, and 10x (3,4) to hist2.
for (int i = 0; i < 10; ++i) {
ASSERT_TRUE(CreateChild(&SharedMemStatisticsTestBase::TestAddChild));
}
test_env_->WaitForChildren();
EXPECT_EQ(3 + 10 * 1, v1->Get());
EXPECT_EQ(17 + 10 * 2, v2->Get());
EXPECT_EQ(20, hist1->Count());
EXPECT_EQ(1, hist1->Minimum());
EXPECT_EQ(2, hist1->Maximum());
EXPECT_EQ(20, hist2->Count());
EXPECT_EQ(3, hist2->Minimum());
EXPECT_EQ(4, hist2->Maximum());
}
void SharedMemStatisticsTestBase::TestSetReturningPrevious() {
ParentInit();
UpDownCounter* v1 = stats_->GetUpDownCounter(kVar1);
EXPECT_EQ(0, v1->Get());
EXPECT_EQ(0, v1->SetReturningPreviousValue(5));
EXPECT_EQ(5, v1->SetReturningPreviousValue(-3));
EXPECT_EQ(-3, v1->SetReturningPreviousValue(10));
EXPECT_EQ(10, v1->Get());
}
void SharedMemStatisticsTestBase::TestAddChild() {
scoped_ptr<SharedMemStatistics> stats(ChildInit());
stats->Init(false, &handler_);
UpDownCounter* v1 = stats->GetUpDownCounter(kVar1);
UpDownCounter* v2 = stats->GetUpDownCounter(kVar2);
Histogram* hist1 = stats->GetHistogram(kHist1);
Histogram* hist2 = stats->GetHistogram(kHist2);
v1->Add(1);
v2->Add(2);
hist1->Add(1);
hist1->Add(2);
hist2->Add(3);
hist2->Add(4);
}
// This function tests the Histogram options with multi-processes.
void SharedMemStatisticsTestBase::TestHistogram() {
ParentInit();
Histogram* hist1 = stats_->GetHistogram(kHist1);
hist1->SetMaxValue(200);
// Test Avg, Min, Max, Median, Percentile, STD, Count.
// Add 0 to 14 to hist1.
for (int i = 0; i <= 14; ++i) {
hist1->Add(i);
}
EXPECT_EQ(15, hist1->Count());
EXPECT_EQ(0, hist1->Minimum());
EXPECT_EQ(14, hist1->Maximum());
EXPECT_EQ(7, hist1->Average());
EXPECT_NEAR(4.32049, hist1->StandardDeviation(), 0.1);
// Note Median() invokes Percentile(50), so it's estimated.
EXPECT_NEAR(7, hist1->Median(), 1);
// The return of Percentile() is an estimated value. It's more accurate when
// the histogram has more numbers.
EXPECT_NEAR(3, hist1->Percentile(20), 1);
// Test EnableNegativeBuckets();
hist1->EnableNegativeBuckets();
hist1->SetMaxValue(100);
// Child process adds 1, 2 to the histogram.
ASSERT_TRUE(CreateChild(&SharedMemStatisticsTestBase::TestAddChild));
test_env_->WaitForChildren();
EXPECT_EQ(2, hist1->Count());
EXPECT_EQ(1, hist1->Minimum());
EXPECT_EQ(2, hist1->Maximum());
hist1->Add(-50);
EXPECT_EQ(-50, hist1->Minimum());
// Test overflow.
// The value range of histogram is [min_value, max_value) or
// (-max_value, max_value) if enabled negative buckets.
// First test when histogram does not have negative buckets.
hist1->Clear();
hist1->SetMaxValue(100);
hist1->Add(1);
hist1->Add(5);
EXPECT_EQ(0, hist1->BucketCount(hist1->NumBuckets() - 1));
hist1->Add(100);
// 10 is the max_value, so 100 should be added to the histogram, but into the
// last bucket.
EXPECT_EQ(1, hist1->BucketCount(hist1->NumBuckets() - 1));
EXPECT_EQ(3, hist1->Count());
EXPECT_EQ(1, hist1->Minimum());
EXPECT_EQ(100, hist1->Maximum());
// Test when negative buckets are enabled.
// -101 and 101 are just outside limits, so they should have been stuck into
// the extreme buckets.
hist1->Clear();
hist1->SetMaxValue(100);
hist1->EnableNegativeBuckets();
EXPECT_EQ(0, hist1->BucketCount(0));
hist1->Add(-101);
EXPECT_EQ(1, hist1->BucketCount(0));
hist1->Add(-5);
hist1->Add(0);
hist1->Add(5);
EXPECT_EQ(0, hist1->BucketCount(hist1->NumBuckets() - 1));
hist1->Add(101);
EXPECT_EQ(1, hist1->BucketCount(hist1->NumBuckets() - 1));
EXPECT_EQ(5, hist1->Count());
EXPECT_EQ(-101, hist1->Minimum());
EXPECT_EQ(101, hist1->Maximum());
}
bool SharedMemStatisticsTestBase::Contains(const StringPiece& html,
const StringPiece& pattern) {
return (html.find(pattern) != GoogleString::npos);
}
// This function tests the Histogram graph is written to html.
void SharedMemStatisticsTestBase::TestHistogramRender() {
// Test header.
// A basic sanity test showing that even there's no data in histograms,
// the script, histogram title, histogram table header are written to html.
// The message written to html should look like:
// <td>H1 (click to view)</td> ...
// Raw Histogram Data ...
// <script> ... </script>
// ParentInit() adds two histograms: H1 and Html Time us Histogram.
ParentInit();
GoogleString html;
StringWriter writer(&html);
stats_->RenderHistograms(&writer, &handler_);
EXPECT_TRUE(Contains(html, "No histogram data yet. Refresh once there is"))
<< "zero state message";
EXPECT_FALSE(Contains(html, "setHistogram"));
// Test basic graph.
Histogram* h1 = stats_->GetHistogram(kHist1);
// Default max_buckets is 500, with max_value = 2500, bucket width is 5.
h1->SetMaxValue(2500);
h1->Add(1);
h1->Add(2);
h1->Add(10);
h1->Add(20);
h1->Add(100);
h1->Add(200);
h1->Add(1000);
h1->Add(2000);
// The table of histogram graph should look like:
// [0,5) 2 25.0% 25.0% ||||||
// [10,15) 1 12.5% 37.5% |||
// ...
// Check if the above number appears.
GoogleString html_graph;
StringWriter writer_graph(&html_graph);
stats_->RenderHistograms(&writer_graph, &handler_);
EXPECT_FALSE(Contains(html_graph, "inf"));
EXPECT_TRUE(Contains(html_graph, "5)</td>"));
EXPECT_TRUE(Contains(html_graph, "25.0%"));
EXPECT_TRUE(Contains(html_graph, "15)</td>"));
EXPECT_TRUE(Contains(html_graph, "12.5%"));
EXPECT_TRUE(Contains(html_graph, "37.5%"));
EXPECT_TRUE(Contains(html_graph, "setHistogram"));
// Now add something out-of-range, that should also add a negative infinity
// bucket
h1->Add(-10);
html_graph.clear();
stats_->RenderHistograms(&writer_graph, &handler_);
EXPECT_TRUE(Contains(html_graph, "-&infin;,</td>"));
}
void SharedMemStatisticsTestBase::TestHistogramNoExtraClear() {
// Make sure we don't lose histogram data when a child process
// redundantly applies the same settings.
ParentInit();
Histogram* h1 = stats_->GetHistogram(kHist1);
h1->EnableNegativeBuckets();
h1->SetMaxValue(100.0);
h1->Add(42);
EXPECT_EQ(1, h1->Count());
ASSERT_TRUE(CreateChild(
&SharedMemStatisticsTestBase::TestHistogramNoExtraClearChild));
test_env_->WaitForChildren();
EXPECT_EQ(1, h1->Count());
}
void SharedMemStatisticsTestBase::TestHistogramNoExtraClearChild() {
scoped_ptr<SharedMemStatistics> stats(ChildInit());
Histogram* h1 = stats->GetHistogram(kHist1);
// This would previously lose the data.
h1->EnableNegativeBuckets();
h1->SetMaxValue(100.0);
}
void SharedMemStatisticsTestBase::TestHistogramExtremeBuckets() {
ParentInit();
Histogram* h1 = stats_->GetHistogram(kHist1);
h1->SetMaxValue(100.0);
h1->Add(0);
// The median will be approximated, but it really ought to be
// in the [0, End of first bucket] range.
EXPECT_LE(0.0, h1->Median());
EXPECT_LE(h1->Median(), h1->BucketLimit(0));
}
void SharedMemStatisticsTestBase::TestTimedVariableEmulation() {
// Simple test of timed variable emulation. Not using ParentInit
// here since we want to add some custom things.
UpDownCounter* a = stats_->AddUpDownCounter("A");
TimedVariable* b = stats_->AddTimedVariable("B", "some group");
stats_->Init(true, &handler_);
b->IncBy(42);
EXPECT_EQ(0, a->Get());
EXPECT_EQ(42, b->Get(TimedVariable::START));
}
} // namespace net_instaweb