blob: 22d90b4b1b81bf534cf38554e25400352dd7ccea [file] [log] [blame]
/*
* Copyright 2012 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 "net/instaweb/http/public/url_async_fetcher_stats.h"
#include "net/instaweb/http/public/mock_callback.h"
#include "net/instaweb/http/public/mock_url_fetcher.h"
#include "net/instaweb/http/public/request_context.h"
#include "net/instaweb/http/public/wait_url_async_fetcher.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/google_message_handler.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/mem_file_system.h"
#include "pagespeed/kernel/base/mock_timer.h"
#include "pagespeed/kernel/base/null_mutex.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/statistics.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/http/http_names.h"
#include "pagespeed/kernel/http/response_headers.h"
#include "pagespeed/kernel/sharedmem/inprocess_shared_mem.h"
#include "pagespeed/kernel/sharedmem/shared_mem_statistics.h"
#include "pagespeed/kernel/util/platform.h"
namespace net_instaweb {
namespace {
const char kUrl[] = "http://www.example.com/";
// A little helper class that manages all the objects we need to
// set up full-fledged histogram-capable statistics in-process for testing.
class StatsMaker {
public:
StatsMaker()
: threads_(Platform::CreateThreadSystem()),
timer_(threads_->NewMutex(), MockTimer::kApr_5_2010_ms),
fs_(threads_.get(), &timer_),
mem_runtime_(new InProcessSharedMem(threads_.get())),
stats_(new SharedMemStatistics(3000 /* log dump interval */,
100000 /* max log size kb */,
"/stats.log", false /* no log */,
"in_mem", mem_runtime_.get(),
&message_handler_, &fs_, &timer_)) {
UrlAsyncFetcherStats::InitStats("test", stats_.get());
stats_->Init(true, &message_handler_);
}
~StatsMaker() {
stats_->GlobalCleanup(&message_handler_);
}
Statistics* stats() { return stats_.get(); }
protected:
scoped_ptr<ThreadSystem> threads_;
MockTimer timer_;
MemFileSystem fs_;
GoogleMessageHandler message_handler_;
scoped_ptr<InProcessSharedMem> mem_runtime_;
scoped_ptr<SharedMemStatistics> stats_;
private:
DISALLOW_COPY_AND_ASSIGN(StatsMaker);
};
class UrlAsyncFetcherStatsTest : public testing::Test {
protected:
UrlAsyncFetcherStatsTest()
: thread_system_(Platform::CreateThreadSystem()),
timer_(thread_system_->NewMutex(), MockTimer::kApr_5_2010_ms),
wait_fetcher_(&mock_fetcher_, new NullMutex),
stats_fetcher_("test", &wait_fetcher_, &timer_, stats_) {
// We don't want delays unless we're testing timing stuff.
wait_fetcher_.SetPassThroughMode(true);
}
// We use per-fixture (rather than per-test) setup and teardown to
// manage the stats to better model their real-life use and better cover
// ::Initialize.
static void SetUpTestCase() {
testing::Test::SetUpTestCase();
stats_maker_ = new StatsMaker();
stats_ = stats_maker_->stats();
}
static void TearDownTestCase() {
delete stats_maker_;
stats_maker_ = NULL;
stats_ = NULL;
testing::Test::TearDownTestCase();
}
static StatsMaker* stats_maker_;
static Statistics* stats_;
GoogleMessageHandler message_handler_;
scoped_ptr<ThreadSystem> thread_system_;
MockTimer timer_;
MockUrlFetcher mock_fetcher_;
WaitUrlAsyncFetcher wait_fetcher_;
UrlAsyncFetcherStats stats_fetcher_;
private:
DISALLOW_COPY_AND_ASSIGN(UrlAsyncFetcherStatsTest);
};
StatsMaker* UrlAsyncFetcherStatsTest::stats_maker_;
Statistics* UrlAsyncFetcherStatsTest::stats_;
TEST_F(UrlAsyncFetcherStatsTest, BasicOperation) {
ResponseHeaders headers;
headers.set_first_line(1, 1, 200, "OK");
const char kBody[] = "payload!";
mock_fetcher_.SetResponse(kUrl, headers, kBody);
ExpectStringAsyncFetch target(
true, RequestContext::NewTestRequestContext(thread_system_.get()));
stats_fetcher_.Fetch(kUrl, &message_handler_, &target);
EXPECT_STREQ(kBody, target.buffer());
// Make sure we update stats OK.
EXPECT_EQ(1, stats_->GetVariable("test_fetches")->Get());
EXPECT_EQ(STATIC_STRLEN(kBody),
stats_->GetVariable("test_bytes_fetched")->Get());
EXPECT_EQ(target.response_headers()->SizeEstimate(),
stats_->GetVariable("test_approx_header_bytes_fetched")->Get());
ExpectStringAsyncFetch target2(
false, RequestContext::NewTestRequestContext(thread_system_.get()));
mock_fetcher_.set_fail_on_unexpected(false);
stats_fetcher_.Fetch(StrCat(kUrl, "Not"), &message_handler_, &target2);
// 1 more response, but no additional payload bytes.
EXPECT_EQ(2, stats_->GetVariable("test_fetches")->Get());
EXPECT_EQ(STATIC_STRLEN(kBody),
stats_->GetVariable("test_bytes_fetched")->Get());
EXPECT_EQ(target.response_headers()->SizeEstimate() +
target2.response_headers()->SizeEstimate(),
stats_->GetVariable("test_approx_header_bytes_fetched")->Get());
}
TEST_F(UrlAsyncFetcherStatsTest, GzipHandling) {
stats_->Clear();
// Make sure we measure what's transferred, not after gunzip'ing, and that
// we decompress right.
const char kOriginal[] = "Hello, gzip!";
// This was gotten by sniffing a gzip'd transfer of the text above.
const char kCompressed[] =
"\x1f\x8b\x08\x00\x00\x00\x00\x00"
"\x00\x03\xf3\x48\xcd\xc9\xc9\xd7"
"\x51\x48\xaf\xca\x2c\x50\x04\x00"
"\x3e\x3d\x0f\x10\x0c\x00\x00\x00";
// The test isn't usable if this doesn't hold.
ASSERT_NE(STATIC_STRLEN(kCompressed), STATIC_STRLEN(kOriginal));
StringPiece compressed(kCompressed, STATIC_STRLEN(kCompressed));
ResponseHeaders headers;
headers.set_first_line(1, 1, 200, "OK");
headers.Add(HttpAttributes::kContentEncoding, "gzip");
mock_fetcher_.SetResponse(kUrl, headers, compressed);
stats_fetcher_.set_fetch_with_gzip(true);
ExpectStringAsyncFetch target(
true, RequestContext::NewTestRequestContext(thread_system_.get()));
stats_fetcher_.Fetch(kUrl, &message_handler_, &target);
EXPECT_STREQ(kOriginal, target.buffer());
EXPECT_EQ(1, stats_->GetVariable("test_fetches")->Get());
EXPECT_EQ(STATIC_STRLEN(kCompressed),
stats_->GetVariable("test_bytes_fetched")->Get());
}
TEST_F(UrlAsyncFetcherStatsTest, TimeMeasurement) {
// Test the we collect timing measurements properly.
stats_->Clear();
wait_fetcher_.SetPassThroughMode(false);
ResponseHeaders headers;
headers.set_first_line(1, 1, 200, "OK");
const char kBody[] = "payload!";
mock_fetcher_.SetResponse(kUrl, headers, kBody);
ExpectStringAsyncFetch target(
true, RequestContext::NewTestRequestContext(thread_system_.get()));
stats_fetcher_.Fetch(kUrl, &message_handler_, &target);
EXPECT_FALSE(target.done());
Histogram* timings = stats_->GetHistogram("test_fetch_latency_us");
EXPECT_EQ(0, timings->Count());
timer_.AdvanceUs(42);
wait_fetcher_.CallCallbacks();
EXPECT_TRUE(target.done());
EXPECT_EQ(1, timings->Count());
EXPECT_DOUBLE_EQ(42, timings->Average());
// Now do an another fetch, this with a 2 us.
ExpectStringAsyncFetch target2(
true, RequestContext::NewTestRequestContext(thread_system_.get()));
stats_fetcher_.Fetch(kUrl, &message_handler_, &target2);
EXPECT_FALSE(target2.done());
timer_.AdvanceUs(2);
wait_fetcher_.CallCallbacks();
EXPECT_TRUE(target2.done());
EXPECT_EQ(2, timings->Count());
EXPECT_DOUBLE_EQ(22, timings->Average()); // (42 + 2) / 2 = 22
}
} // namespace
} // namespace net_instaweb