diff options
author | ericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-14 03:01:09 +0000 |
---|---|---|
committer | ericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-14 03:01:09 +0000 |
commit | 54e1377ea309c5d8ab318ee9691b46249d7441b6 (patch) | |
tree | 8471e44056027dff255ff5b22ba083a9482b41c3 | |
parent | 672eca0fd191acd39c28430d1ed1f327594faff8 (diff) | |
download | chromium_src-54e1377ea309c5d8ab318ee9691b46249d7441b6.zip chromium_src-54e1377ea309c5d8ab318ee9691b46249d7441b6.tar.gz chromium_src-54e1377ea309c5d8ab318ee9691b46249d7441b6.tar.bz2 |
Implement LoadLog, and hook up HostResolverImpl to LoadLog.
The functionality in load_log_util.h is currently unused, but since it motivates the implementation of LoadLog, figured it should be included with this review.
Note that I decided against defining the loggable event types as LoadStates, since I wanted more flexibility in adding crazy stuff, and they really seem too implementation specific to be in LoadStates.
BUG=http://crbug.com/14478
TEST=net_unittests
Review URL: http://codereview.chromium.org/165404
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@23412 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/base/host_resolver_impl.cc | 113 | ||||
-rw-r--r-- | net/base/host_resolver_impl.h | 27 | ||||
-rw-r--r-- | net/base/host_resolver_impl_unittest.cc | 49 | ||||
-rw-r--r-- | net/base/load_log.cc | 37 | ||||
-rw-r--r-- | net/base/load_log.h | 88 | ||||
-rw-r--r-- | net/base/load_log_event_type_list.h | 34 | ||||
-rw-r--r-- | net/base/load_log_unittest.cc | 105 | ||||
-rw-r--r-- | net/base/load_log_unittest.h | 31 | ||||
-rw-r--r-- | net/base/load_log_util.cc | 179 | ||||
-rw-r--r-- | net/base/load_log_util.h | 74 | ||||
-rw-r--r-- | net/base/load_log_util_unittest.cc | 57 | ||||
-rw-r--r-- | net/net.gyp | 7 |
12 files changed, 751 insertions, 50 deletions
diff --git a/net/base/host_resolver_impl.cc b/net/base/host_resolver_impl.cc index 111e8da..20e57e5 100644 --- a/net/base/host_resolver_impl.cc +++ b/net/base/host_resolver_impl.cc @@ -24,6 +24,7 @@ #include "base/worker_pool.h" #include "net/base/address_list.h" #include "net/base/host_resolver_proc.h" +#include "net/base/load_log.h" #include "net/base/net_errors.h" #if defined(OS_WIN) @@ -54,10 +55,18 @@ static int ResolveAddrInfo(HostResolverProc* resolver_proc, class HostResolverImpl::Request { public: - Request(int id, const RequestInfo& info, CompletionCallback* callback, + Request(LoadLog* load_log, + int id, + const RequestInfo& info, + CompletionCallback* callback, AddressList* addresses) - : id_(id), info_(info), job_(NULL), callback_(callback), - addresses_(addresses) {} + : load_log_(load_log), + id_(id), + info_(info), + job_(NULL), + callback_(callback), + addresses_(addresses) { + } // Mark the request as cancelled. void MarkAsCancelled() { @@ -90,6 +99,10 @@ class HostResolverImpl::Request { return job_; } + LoadLog* load_log() const { + return load_log_; + } + int id() const { return id_; } @@ -99,6 +112,8 @@ class HostResolverImpl::Request { } private: + scoped_refptr<LoadLog> load_log_; + // Unique ID for this request. Used by observers to identify requests. int id_; @@ -300,8 +315,8 @@ int HostResolverImpl::Resolve(LoadLog* load_log, // Choose a unique ID number for observers to see. int request_id = next_request_id_++; - // Notify registered observers. - NotifyObserversStartRequest(request_id, info); + // Update the load log and notify registered observers. + OnStartRequest(load_log, request_id, info); // If we have an unexpired cache entry, use it. if (info.allow_cached_response()) { @@ -311,8 +326,8 @@ int HostResolverImpl::Resolve(LoadLog* load_log, addresses->SetFrom(cache_entry->addrlist, info.port()); int error = cache_entry->error; - // Notify registered observers. - NotifyObserversFinishRequest(request_id, info, error); + // Update the load log and notify registered observers. + OnFinishRequest(load_log, request_id, info, error); return error; } @@ -331,15 +346,15 @@ int HostResolverImpl::Resolve(LoadLog* load_log, // Write to cache. cache_.Set(info.hostname(), error, addrlist, base::TimeTicks::Now()); - // Notify registered observers. - NotifyObserversFinishRequest(request_id, info, error); + // Update the load log and notify registered observers. + OnFinishRequest(load_log, request_id, info, error); return error; } // Create a handle for this request, and pass it back to the user if they // asked for it (out_req != NULL). - Request* req = new Request(request_id, info, callback, addresses); + Request* req = new Request(load_log, request_id, info, callback, addresses); if (out_req) *out_req = reinterpret_cast<RequestHandle>(req); @@ -382,7 +397,7 @@ void HostResolverImpl::CancelRequest(RequestHandle req_handle) { DCHECK(req->job()); // NULL out the fields of req, to mark it as cancelled. req->MarkAsCancelled(); - NotifyObserversCancelRequest(req->id(), req->info()); + OnCancelRequest(req->load_log(), req->id(), req->info()); } void HostResolverImpl::AddObserver(Observer* observer) { @@ -449,8 +464,8 @@ void HostResolverImpl::OnJobComplete(Job* job, if (!req->was_cancelled()) { DCHECK_EQ(job, req->job()); - // Notify registered observers. - NotifyObserversFinishRequest(req->id(), req->info(), error); + // Update the load log and notify registered observers. + OnFinishRequest(req->load_log(), req->id(), req->info(), error); req->OnComplete(error, addrlist); @@ -464,30 +479,68 @@ void HostResolverImpl::OnJobComplete(Job* job, cur_completing_job_ = NULL; } -void HostResolverImpl::NotifyObserversStartRequest(int request_id, - const RequestInfo& info) { - for (ObserversList::iterator it = observers_.begin(); - it != observers_.end(); ++it) { - (*it)->OnStartResolution(request_id, info); +void HostResolverImpl::OnStartRequest(LoadLog* load_log, + int request_id, + const RequestInfo& info) { + LoadLog::BeginEvent(load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL); + + // Notify the observers of the start. + if (!observers_.empty()) { + LoadLog::BeginEvent( + load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART); + + for (ObserversList::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + (*it)->OnStartResolution(request_id, info); + } + + LoadLog::EndEvent( + load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART); } } -void HostResolverImpl::NotifyObserversFinishRequest(int request_id, - const RequestInfo& info, - int error) { - bool was_resolved = error == OK; - for (ObserversList::iterator it = observers_.begin(); - it != observers_.end(); ++it) { - (*it)->OnFinishResolutionWithStatus(request_id, was_resolved, info); +void HostResolverImpl::OnFinishRequest(LoadLog* load_log, + int request_id, + const RequestInfo& info, + int error) { + // Notify the observers of the completion. + if (!observers_.empty()) { + LoadLog::BeginEvent( + load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONFINISH); + + bool was_resolved = error == OK; + for (ObserversList::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + (*it)->OnFinishResolutionWithStatus(request_id, was_resolved, info); + } + + LoadLog::EndEvent( + load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONFINISH); } + + LoadLog::EndEvent(load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL); } -void HostResolverImpl::NotifyObserversCancelRequest(int request_id, - const RequestInfo& info) { - for (ObserversList::iterator it = observers_.begin(); - it != observers_.end(); ++it) { - (*it)->OnCancelResolution(request_id, info); +void HostResolverImpl::OnCancelRequest(LoadLog* load_log, + int request_id, + const RequestInfo& info) { + LoadLog::AddEvent(load_log, LoadLog::TYPE_CANCELLED); + + // Notify the observers of the cancellation. + if (!observers_.empty()) { + LoadLog::BeginEvent( + load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONCANCEL); + + for (ObserversList::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + (*it)->OnCancelResolution(request_id, info); + } + + LoadLog::EndEvent( + load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONCANCEL); } + + LoadLog::EndEvent(load_log, LoadLog::TYPE_HOST_RESOLVER_IMPL); } } // namespace net diff --git a/net/base/host_resolver_impl.h b/net/base/host_resolver_impl.h index ff9e4e7..ede3d18 100644 --- a/net/base/host_resolver_impl.h +++ b/net/base/host_resolver_impl.h @@ -94,18 +94,21 @@ class HostResolverImpl : public HostResolver { // Callback for when |job| has completed with |error| and |addrlist|. void OnJobComplete(Job* job, int error, const AddressList& addrlist); - // Notify all observers of the start of a resolve request. - void NotifyObserversStartRequest(int request_id, - const RequestInfo& info); - - // Notify all observers of the completion of a resolve request. - void NotifyObserversFinishRequest(int request_id, - const RequestInfo& info, - int error); - - // Notify all observers of the cancellation of a resolve request. - void NotifyObserversCancelRequest(int request_id, - const RequestInfo& info); + // Called when a request has just been started. + void OnStartRequest(LoadLog* load_log, + int request_id, + const RequestInfo& info); + + // Called when a request has just completed (before its callback is run). + void OnFinishRequest(LoadLog* load_log, + int request_id, + const RequestInfo& info, + int error); + + // Called when a request has been cancelled. + void OnCancelRequest(LoadLog* load_log, + int request_id, + const RequestInfo& info); // Cache of host resolution results. HostCache cache_; diff --git a/net/base/host_resolver_impl_unittest.cc b/net/base/host_resolver_impl_unittest.cc index e33bbf4e..c59e477 100644 --- a/net/base/host_resolver_impl_unittest.cc +++ b/net/base/host_resolver_impl_unittest.cc @@ -18,12 +18,14 @@ #include "base/ref_counted.h" #include "net/base/address_list.h" #include "net/base/completion_callback.h" +#include "net/base/load_log_unittest.h" #include "net/base/mock_host_resolver.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" #include "testing/gtest/include/gtest/gtest.h" using net::HostResolverImpl; +using net::LoadLog; using net::RuleBasedHostResolverProc; using net::WaitingHostResolverProc; @@ -187,9 +189,16 @@ TEST_F(HostResolverImplTest, SynchronousLookup) { new HostResolverImpl(resolver_proc, kMaxCacheEntries, kMaxCacheAgeMs)); net::HostResolver::RequestInfo info("just.testing", kPortnum); - int err = host_resolver->Resolve(NULL, info, &adrlist, NULL, NULL); + scoped_refptr<LoadLog> log(new LoadLog); + int err = host_resolver->Resolve(log, info, &adrlist, NULL, NULL); EXPECT_EQ(net::OK, err); + EXPECT_EQ(2u, log->events().size()); + net::ExpectLogContains(log, 0, LoadLog::TYPE_HOST_RESOLVER_IMPL, + LoadLog::PHASE_BEGIN); + net::ExpectLogContains(log, 1, LoadLog::TYPE_HOST_RESOLVER_IMPL, + LoadLog::PHASE_END); + const struct addrinfo* ainfo = adrlist.head(); EXPECT_EQ(static_cast<addrinfo*>(NULL), ainfo->ai_next); EXPECT_EQ(sizeof(struct sockaddr_in), ainfo->ai_addrlen); @@ -212,14 +221,23 @@ TEST_F(HostResolverImplTest, AsynchronousLookup) { new HostResolverImpl(resolver_proc, kMaxCacheEntries, kMaxCacheAgeMs)); net::HostResolver::RequestInfo info("just.testing", kPortnum); - int err = host_resolver->Resolve(NULL, info, &adrlist, &callback_, NULL); + scoped_refptr<LoadLog> log(new LoadLog); + int err = host_resolver->Resolve(log, info, &adrlist, &callback_, NULL); EXPECT_EQ(net::ERR_IO_PENDING, err); + EXPECT_EQ(1u, log->events().size()); + net::ExpectLogContains( + log, 0, LoadLog::TYPE_HOST_RESOLVER_IMPL, LoadLog::PHASE_BEGIN); + MessageLoop::current()->Run(); ASSERT_TRUE(callback_called_); ASSERT_EQ(net::OK, callback_result_); + EXPECT_EQ(2u, log->events().size()); + net::ExpectLogContains(log, 1, LoadLog::TYPE_HOST_RESOLVER_IMPL, + LoadLog::PHASE_END); + const struct addrinfo* ainfo = adrlist.head(); EXPECT_EQ(static_cast<addrinfo*>(NULL), ainfo->ai_next); EXPECT_EQ(sizeof(struct sockaddr_in), ainfo->ai_addrlen); @@ -234,6 +252,7 @@ TEST_F(HostResolverImplTest, CanceledAsynchronousLookup) { scoped_refptr<WaitingHostResolverProc> resolver_proc = new WaitingHostResolverProc(NULL); + scoped_refptr<LoadLog> log(new LoadLog); { scoped_refptr<net::HostResolver> host_resolver( new HostResolverImpl(resolver_proc, kMaxCacheEntries, kMaxCacheAgeMs)); @@ -241,7 +260,7 @@ TEST_F(HostResolverImplTest, CanceledAsynchronousLookup) { const int kPortnum = 80; net::HostResolver::RequestInfo info("just.testing", kPortnum); - int err = host_resolver->Resolve(NULL, info, &adrlist, &callback_, NULL); + int err = host_resolver->Resolve(log, info, &adrlist, &callback_, NULL); EXPECT_EQ(net::ERR_IO_PENDING, err); // Make sure we will exit the queue even when callback is not called. @@ -253,6 +272,13 @@ TEST_F(HostResolverImplTest, CanceledAsynchronousLookup) { resolver_proc->Signal(); + EXPECT_EQ(3u, log->events().size()); + net::ExpectLogContains(log, 0, LoadLog::TYPE_HOST_RESOLVER_IMPL, + LoadLog::PHASE_BEGIN); + net::ExpectLogContains(log, 1, LoadLog::TYPE_CANCELLED, LoadLog::PHASE_NONE); + net::ExpectLogContains(log, 2, LoadLog::TYPE_HOST_RESOLVER_IMPL, + LoadLog::PHASE_END); + EXPECT_FALSE(callback_called_); } @@ -801,9 +827,24 @@ TEST_F(HostResolverImplTest, Observers) { // Resolve "host1". net::HostResolver::RequestInfo info1("host1", 70); - int rv = host_resolver->Resolve(NULL, info1, &addrlist, NULL, NULL); + scoped_refptr<LoadLog> log(new LoadLog); + int rv = host_resolver->Resolve(log, info1, &addrlist, NULL, NULL); EXPECT_EQ(net::OK, rv); + EXPECT_EQ(6u, log->events().size()); + net::ExpectLogContains(log, 0, + LoadLog::TYPE_HOST_RESOLVER_IMPL, LoadLog::PHASE_BEGIN); + net::ExpectLogContains(log, 1, + LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART, LoadLog::PHASE_BEGIN); + net::ExpectLogContains(log, 2, + LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART, LoadLog::PHASE_END); + net::ExpectLogContains(log, 3, + LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONFINISH, LoadLog::PHASE_BEGIN); + net::ExpectLogContains(log, 4, + LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONFINISH, LoadLog::PHASE_END); + net::ExpectLogContains(log, 5, + LoadLog::TYPE_HOST_RESOLVER_IMPL, LoadLog::PHASE_END); + EXPECT_EQ(1U, observer.start_log.size()); EXPECT_EQ(1U, observer.finish_log.size()); EXPECT_EQ(0U, observer.cancel_log.size()); diff --git a/net/base/load_log.cc b/net/base/load_log.cc new file mode 100644 index 0000000..120a58b --- /dev/null +++ b/net/base/load_log.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/base/load_log.h" + +namespace net { + +LoadLog::LoadLog() { +} + +// static +const char* LoadLog::EventTypeToString(EventType event) { + switch (event) { +#define EVENT_TYPE(label) case TYPE_ ## label: return #label; +#include "net/base/load_log_event_type_list.h" +#undef EVENT_TYPE + } + return NULL; +} + +void LoadLog::Add(base::TimeTicks t, EventType event, EventPhase phase) { + // Minor optimization. TODO(eroman): use StackVector instead. + if (events_.empty()) + events_.reserve(kMaxNumEntries / 2); + + // Enforce a bound of kMaxNumEntries -- when we reach it, make it so the + // final entry in the list is |TYPE_LOG_TRUNCATED|. + + if (events_.size() + 1 == kMaxNumEntries) + events_.push_back(Event(t, TYPE_LOG_TRUNCATED, PHASE_NONE)); + + if (events_.size() < kMaxNumEntries) + events_.push_back(Event(t, event, phase)); +} + +} // namespace net diff --git a/net/base/load_log.h b/net/base/load_log.h index 0efe242..fcb7d6f 100644 --- a/net/base/load_log.h +++ b/net/base/load_log.h @@ -5,7 +5,10 @@ #ifndef NET_BASE_LOAD_LOG_H_ #define NET_BASE_LOAD_LOG_H_ +#include <vector> + #include "base/ref_counted.h" +#include "base/time.h" namespace net { @@ -13,10 +16,87 @@ namespace net { // a request (waiting in queues, resolving hosts, resolving proxy, etc...). class LoadLog : public base::RefCounted<LoadLog> { public: -// TODO(eroman): Add an API similar to: -// void TrackEnterState(LoadState state); -// void TrackLeaveState(LoadState state); -// void Merge(const LoadLog* other); + + enum EventType { +#define EVENT_TYPE(label) TYPE_ ## label, +#include "net/base/load_log_event_type_list.h" +#undef EVENT_TYPE + }; + + // Whether this is the start/end of an event. Or in the case of EventTypes + // that are "instantaneous", kNone. + enum EventPhase { + PHASE_NONE, + PHASE_BEGIN, + PHASE_END, + }; + + // A logged event. Note that "phase" means if this is the start/end of a + // particular event type (in order to record a timestamp for both endpoints). + struct Event { + Event(base::TimeTicks time, + EventType type, + EventPhase phase) + : time(time), type(type), phase(phase) { + } + + base::TimeTicks time; + EventType type; + EventPhase phase; + }; + + // The maximum size of |events_|. + enum { kMaxNumEntries = 25 }; + + // Ordered set of events that were logged. + // TODO(eroman): use a StackVector or array to avoid allocations. + typedef std::vector<Event> EventList; + + // Create a log, which can hold up to |kMaxNumEntries| Events. + // + // If events are dropped because the log has grown too large, the final + // entry will be of type kLogTruncated. + LoadLog(); + + // -------------------------------------------------------------------------- + + // The public interface for adding events to the log are static methods. + // This makes it easier to deal with optionally NULL LoadLog. + + // Adds an instantaneous event to the log. + static void AddEvent(LoadLog* log, EventType event) { + if (log) + log->Add(base::TimeTicks::Now(), event, PHASE_NONE); + } + + // Adds the start of an event to the log. Presumably this event type measures + // a time duration, and will be matched by a call to EndEvent(event). + static void BeginEvent(LoadLog* log, EventType event) { + if (log) + log->Add(base::TimeTicks::Now(), event, PHASE_BEGIN); + } + + // Adds the end of an event to the log. Presumably this event type measures + // a time duration, and we are matching an earlier call to BeginEvent(event). + static void EndEvent(LoadLog* log, EventType event) { + if (log) + log->Add(base::TimeTicks::Now(), event, PHASE_END); + } + + // -------------------------------------------------------------------------- + + // Returns the list of all events in the log. + const EventList& events() const { + return events_; + } + + // Returns a C-String symbolic name for |event|. + static const char* EventTypeToString(EventType event); + + void Add(base::TimeTicks t, EventType event, EventPhase phase); + + private: + EventList events_; }; } // namespace net diff --git a/net/base/load_log_event_type_list.h b/net/base/load_log_event_type_list.h new file mode 100644 index 0000000..e259515 --- /dev/null +++ b/net/base/load_log_event_type_list.h @@ -0,0 +1,34 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// NOTE: No header guards are used, since this file is intended to be expanded +// directly into load_log.h. DO NOT include this file anywhere else. + +// -------------------------------------------------------------------------- +// General pseudo-events +// -------------------------------------------------------------------------- + +// If a log is terminated by a kLogTruncated event, then it grew too large +// and any subsequent events logged to it were discarded. +EVENT_TYPE(LOG_TRUNCATED) + +// Something got cancelled (we determine what is cancelled based on the +// log context around it.) +EVENT_TYPE(CANCELLED) + +// ------------------------------------------------------------------------ +// HostResolverImpl +// ------------------------------------------------------------------------ + +// The start/end of a host resolve (DNS) request. +EVENT_TYPE(HOST_RESOLVER_IMPL) + +// The start/end of HostResolver::Observer::OnStartResolution. +EVENT_TYPE(HOST_RESOLVER_IMPL_OBSERVER_ONSTART) + +// The start/end of HostResolver::Observer::OnFinishResolutionWithStatus +EVENT_TYPE(HOST_RESOLVER_IMPL_OBSERVER_ONFINISH) + +// The start/end of HostResolver::Observer::OnCancelResolution. +EVENT_TYPE(HOST_RESOLVER_IMPL_OBSERVER_ONCANCEL) diff --git a/net/base/load_log_unittest.cc b/net/base/load_log_unittest.cc new file mode 100644 index 0000000..9634dd9 --- /dev/null +++ b/net/base/load_log_unittest.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/base/load_log_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +base::TimeTicks MakeTime(int t) { + base::TimeTicks ticks; // initialized to 0. + ticks += base::TimeDelta::FromMilliseconds(t); + return ticks; +} + +void ExpectLogContains(const LoadLog* log, + size_t i, + LoadLog::EventType expected_event, + LoadLog::EventPhase expected_phase) { + ASSERT_LT(i, log->events().size()); + EXPECT_EQ(expected_event, log->events()[i].type); + EXPECT_EQ(expected_phase, log->events()[i].phase); +} + +void ExpectLogContains(const LoadLog* log, + size_t i, + base::TimeTicks expected_time, + LoadLog::EventType expected_event, + LoadLog::EventPhase expected_phase) { + ASSERT_LT(i, log->events().size()); + EXPECT_TRUE(expected_time == log->events()[i].time); + EXPECT_EQ(expected_event, log->events()[i].type); + EXPECT_EQ(expected_phase, log->events()[i].phase); +} + +namespace { + +TEST(LoadLogTest, Nullable) { + // Make sure that the static methods can be called with NULL (no-op). + // (Should not crash). + LoadLog::BeginEvent(NULL, LoadLog::TYPE_HOST_RESOLVER_IMPL); + LoadLog::AddEvent(NULL, LoadLog::TYPE_HOST_RESOLVER_IMPL); + LoadLog::EndEvent(NULL, LoadLog::TYPE_HOST_RESOLVER_IMPL); +} + +TEST(LoadLogTest, Basic) { + scoped_refptr<LoadLog> log(new LoadLog); + + // Logs start off empty. + EXPECT_EQ(0u, log->events().size()); + + // Add 3 entries. + + log->Add(MakeTime(0), LoadLog::TYPE_HOST_RESOLVER_IMPL, LoadLog::PHASE_BEGIN); + log->Add(MakeTime(2), LoadLog::TYPE_CANCELLED, LoadLog::PHASE_NONE); + log->Add(MakeTime(11), LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART, + LoadLog::PHASE_END); + + EXPECT_EQ(3u, log->events().size()); + + ExpectLogContains(log, 0, MakeTime(0), LoadLog::TYPE_HOST_RESOLVER_IMPL, + LoadLog::PHASE_BEGIN); + + ExpectLogContains(log, 1, MakeTime(2), + LoadLog::TYPE_CANCELLED, LoadLog::PHASE_NONE); + + ExpectLogContains(log, 2, MakeTime(11), + LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART, LoadLog::PHASE_END); +} + +// Test that the log's size is strictly bounded. +TEST(LoadLogTest, Truncation) { + scoped_refptr<LoadLog> log(new LoadLog); + + // Almost max it out. + for (size_t i = 0; i < LoadLog::kMaxNumEntries - 1; ++i) { + log->Add(MakeTime(0), LoadLog::TYPE_CANCELLED, LoadLog::PHASE_NONE); + } + EXPECT_EQ(LoadLog::kMaxNumEntries - 1, + static_cast<int>(log->events().size())); + EXPECT_EQ(LoadLog::TYPE_CANCELLED, + log->events()[LoadLog::kMaxNumEntries - 2].type); + + // Max it out (none of these get appended to the log). + log->Add(MakeTime(0), LoadLog::TYPE_CANCELLED, LoadLog::PHASE_NONE); + log->Add(MakeTime(0), LoadLog::TYPE_CANCELLED, LoadLog::PHASE_NONE); + log->Add(MakeTime(0), LoadLog::TYPE_CANCELLED, LoadLog::PHASE_NONE); + + EXPECT_EQ(LoadLog::kMaxNumEntries, log->events().size()); + + // We terminated with a "truncation" event. + ExpectLogContains(log, LoadLog::kMaxNumEntries - 1, MakeTime(0), + LoadLog::TYPE_LOG_TRUNCATED, LoadLog::PHASE_NONE); +} + +TEST(LoadLogTest, EventTypeToString) { + EXPECT_STREQ("HOST_RESOLVER_IMPL", + LoadLog::EventTypeToString(LoadLog::TYPE_HOST_RESOLVER_IMPL)); + EXPECT_STREQ("HOST_RESOLVER_IMPL_OBSERVER_ONSTART", + LoadLog::EventTypeToString( + LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART)); +} + +} // namespace +} // namespace net diff --git a/net/base/load_log_unittest.h b/net/base/load_log_unittest.h new file mode 100644 index 0000000..f1e386a --- /dev/null +++ b/net/base/load_log_unittest.h @@ -0,0 +1,31 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_BASE_LOAD_LOG_UNITTEST_H_ +#define NET_BASE_LOAD_LOG_UNITTEST_H_ + +#include "net/base/load_log.h" + +namespace net { + +// Create a timestamp with internal value of |t| milliseconds from the epoch. +base::TimeTicks MakeTime(int t); + +// Call gtest's EXPECT_* to verify that |log| contains the specified entry +// at index |i|. +void ExpectLogContains(const LoadLog* log, + size_t i, + base::TimeTicks expected_time, + LoadLog::EventType expected_event, + LoadLog::EventPhase expected_phase); + +// Same as above, but without an expectation for the timestamp. +void ExpectLogContains(const LoadLog* log, + size_t i, + LoadLog::EventType expected_event, + LoadLog::EventPhase expected_phase); + +} // namespace net + +#endif // NET_BASE_LOAD_LOG_UNITTEST_H_ diff --git a/net/base/load_log_util.cc b/net/base/load_log_util.cc new file mode 100644 index 0000000..28304d0 --- /dev/null +++ b/net/base/load_log_util.cc @@ -0,0 +1,179 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/base/load_log_util.h" + +#include "base/string_util.h" + +namespace net { +namespace { + +class FormatHelper { + public: + std::string ToString(const LoadLog* log) { + entries_.clear(); + + // Pass 1: Match the start/end of indentation blocks. Fills |entries_| + // with the results. + PopulateEntries(log); + + // Pass 2: Figure out the maximum width of each column. This allows us + // to right-justify text within each column. + size_t max_time_width, max_indentation, max_type_width, max_dt_width; + GetMaxExtent( + &max_time_width, &max_indentation, &max_type_width, &max_dt_width); + + // Pass 3: Assemble the string. + std::string result; + + const int kSpacesPerIndentation = 2; + + for (size_t i = 0; i < entries_.size(); ++i) { + if (i > 0) + result += "\n"; + + int indentation_spaces = entries_[i].indentation * kSpacesPerIndentation; + std::string event_str = GetEventString(i); + + result += StringPrintf("t=%s: %s%s", + PadStringLeft(GetTimeString(i), max_time_width).c_str(), + PadStringLeft("", indentation_spaces).c_str(), + event_str.c_str()); + + if (entries_[i].event->phase == LoadLog::PHASE_BEGIN) { + // Summarize how long this block lasted. + int padding = ((max_indentation - entries_[i].indentation) * + kSpacesPerIndentation) + (max_type_width - event_str.size()); + result += StringPrintf("%s [dt=%s]", + PadStringLeft("", padding).c_str(), + PadStringLeft(GetBlockDtString(i), max_dt_width).c_str()); + } + } + + return result; + } + + private: + struct Entry { + explicit Entry(const LoadLog::Event* event) + : event(event), indentation(0), block_index(-1) {} + + const LoadLog::Event* event; + size_t indentation; + int block_index; // The index of the matching start / end of block. + }; + + void PopulateEntries(const LoadLog* log) { + int current_indentation = 0; + + for (size_t i = 0; i < log->events().size(); ++i) { + Entry entry(&log->events()[i]); + + entry.indentation = current_indentation; + + if (entry.event->phase == LoadLog::PHASE_BEGIN) { + // Indent everything contained in this block. + current_indentation++; + } + + if (entry.event->phase == LoadLog::PHASE_END) { + int start_index = FindStartOfBlockIndex(entry); + if (start_index != -1) { + // Point the start / end of block at each other. + entry.block_index = start_index; + entries_[start_index].block_index = i; + + // Restore the indentation prior to the block. + // (Could be more than 1 level if close of blocks are missing). + current_indentation = entries_[start_index].indentation; + entry.indentation = current_indentation; + } + } + + entries_.push_back(entry); + } + } + + int FindStartOfBlockIndex(const Entry& entry) { + DCHECK_EQ(LoadLog::PHASE_END, entry.event->phase); + + // Find the matching start of block by scanning backwards. + for (int i = entries_.size() - 1; i >= 0; --i) { + if (entries_[i].event->phase == LoadLog::PHASE_BEGIN && + entries_[i].event->type == entry.event->type) { + return i; + } + } + return -1; // Start not found. + } + + void GetMaxExtent(size_t* max_time_width, + size_t* max_indentation, + size_t* max_type_width, + size_t* max_dt_width) { + *max_time_width = *max_indentation = *max_type_width = *max_dt_width = 0; + for (size_t i = 0; i < entries_.size(); ++i) { + *max_time_width = std::max(*max_time_width, GetTimeString(i).size()); + *max_type_width = std::max(*max_type_width, GetEventString(i).size()); + *max_indentation = std::max(*max_indentation, entries_[i].indentation); + + if (entries_[i].event->phase == LoadLog::PHASE_BEGIN) + *max_dt_width = std::max(*max_dt_width, GetBlockDtString(i).size()); + } + } + + std::string GetBlockDtString(size_t start_index) { + int end_index = entries_[start_index].block_index; + if (end_index == -1) { + // Block is not closed, implicitly close it at EOF. + end_index = entries_.size() - 1; + } + int64 dt_ms = (entries_[end_index].event->time - + entries_[start_index].event->time).InMilliseconds(); + + return Int64ToString(dt_ms); + } + + std::string GetTimeString(size_t index) { + int64 t_ms = (entries_[index].event->time - + base::TimeTicks()).InMilliseconds(); + return Int64ToString(t_ms); + } + + std::string GetEventString(size_t index) { + const LoadLog::Event* event = entries_[index].event; + const char* type_str = LoadLog::EventTypeToString(event->type); + + switch (event->phase) { + case LoadLog::PHASE_BEGIN: + return std::string("+") + type_str; + case LoadLog::PHASE_END: + return std::string("-") + type_str; + case LoadLog::PHASE_NONE: + return std::string(" ") + type_str; + default: + NOTREACHED(); + return std::string(); + } + } + + static std::string PadStringLeft(const std::string& str, size_t width) { + DCHECK_LE(str.size(), width); + std::string padding; + padding.resize(width - str.size(), ' '); + return padding + str; + } + + std::vector<Entry> entries_; +}; + +} // namespace + +// static +std::string LoadLogUtil::PrettyPrintAsEventTree(const LoadLog* log) { + FormatHelper helper; + return helper.ToString(log); +} + +} // namespace net diff --git a/net/base/load_log_util.h b/net/base/load_log_util.h new file mode 100644 index 0000000..363e219 --- /dev/null +++ b/net/base/load_log_util.h @@ -0,0 +1,74 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_BASE_LOAD_LOG_UTIL_H_ +#define NET_BASE_LOAD_LOG_UTIL_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "net/base/load_log.h" + +namespace net { + +// The LoadLogUtil utility class contains methods to analyze and visualize +// LoadLogs. + +class LoadLogUtil { + public: + struct EventDuration { + LoadLog::EventType event; + base::TimeDelta duration; + }; + typedef std::vector<EventDuration> EventDurationList; + + // Builds a pretty printed ASCII tree showing the chronological order + // of events. + // + // The indentation reflects hiearchy, with the duration of each indented + // block noted on the right. The timestamp (tick count in milliseconds) + // is noted in the left column. + // + // This detailed view of what went into servicing a request can be used + // in logs, and is copy-pastable by users, for attaching to bug reports. + // + // Example A: + // + // t=0: +Event1 [dt = 8] + // t=1: +Event2 [dt = 0] + // t=1: EventX + // t=1: -Event2 + // t=4: +Event3 [dt = 2] + // t=6: -Event3 + // t=6: +Event2 [dt = 1] + // t=7: -Event2 + // t=8: EventY + // t=8: -Event1 + // + // Here we can see that: + // - Event1 started at t=0 and ended at t=8; the duration was 8 time units. + // - Event2 took place while Event1 was in progress, and was repeated + // at t=1 and t=6. + // - EventX took place while (the first) Event2 was in progress. + // - Event3 started and ended at the same time, taking 0 time. + // - EventY took place right before Event1 finished, at t=8 + // + // In general the rules are: + // - Log entries added by BeginEvent() are prefixed with a '+' and + // start an indentation block. + // - Log entries added by EndEvent() are prefixed with a '-' and + // finish an indentation block. + // - Log entries added by AddEvent() have no prefix. + // - Time units are given as milliseconds. + // + static std::string PrettyPrintAsEventTree(const LoadLog* log); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(LoadLogUtil); +}; + +} // namespace net + +#endif // NET_BASE_LOAD_LOG_UTIL_H_ diff --git a/net/base/load_log_util_unittest.cc b/net/base/load_log_util_unittest.cc new file mode 100644 index 0000000..fbaa77b --- /dev/null +++ b/net/base/load_log_util_unittest.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/base/load_log_unittest.h" +#include "net/base/load_log_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace { + +TEST(LoadLogUtilTest, Basic) { + scoped_refptr<LoadLog> log(new LoadLog); + + log->Add(MakeTime(1), LoadLog::TYPE_HOST_RESOLVER_IMPL, LoadLog::PHASE_BEGIN); + log->Add(MakeTime(5), LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART, + LoadLog::PHASE_BEGIN); + log->Add(MakeTime(8), LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART, + LoadLog::PHASE_END); + log->Add(MakeTime(12), LoadLog::TYPE_CANCELLED, LoadLog::PHASE_NONE); + log->Add(MakeTime(131), LoadLog::TYPE_HOST_RESOLVER_IMPL, LoadLog::PHASE_END); + + EXPECT_EQ( + "t= 1: +HOST_RESOLVER_IMPL [dt=130]\n" + "t= 5: +HOST_RESOLVER_IMPL_OBSERVER_ONSTART [dt= 3]\n" + "t= 8: -HOST_RESOLVER_IMPL_OBSERVER_ONSTART\n" + "t= 12: CANCELLED\n" + "t=131: -HOST_RESOLVER_IMPL", + LoadLogUtil::PrettyPrintAsEventTree(log)); +} + +TEST(LoadLogUtilTest, UnmatchedOpen) { + scoped_refptr<LoadLog> log(new LoadLog); + + log->Add(MakeTime(3), LoadLog::TYPE_HOST_RESOLVER_IMPL, LoadLog::PHASE_BEGIN); + // Note that there is no matching call to PHASE_END for all of the following. + log->Add(MakeTime(6), LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART, + LoadLog::PHASE_BEGIN); + log->Add(MakeTime(7), LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART, + LoadLog::PHASE_BEGIN); + log->Add(MakeTime(8), LoadLog::TYPE_HOST_RESOLVER_IMPL_OBSERVER_ONSTART, + LoadLog::PHASE_BEGIN); + log->Add(MakeTime(10), LoadLog::TYPE_CANCELLED, LoadLog::PHASE_NONE); + log->Add(MakeTime(16), LoadLog::TYPE_HOST_RESOLVER_IMPL, LoadLog::PHASE_END); + + EXPECT_EQ( + "t= 3: +HOST_RESOLVER_IMPL [dt=13]\n" + "t= 6: +HOST_RESOLVER_IMPL_OBSERVER_ONSTART [dt=10]\n" + "t= 7: +HOST_RESOLVER_IMPL_OBSERVER_ONSTART [dt= 9]\n" + "t= 8: +HOST_RESOLVER_IMPL_OBSERVER_ONSTART [dt= 8]\n" + "t=10: CANCELLED\n" + "t=16: -HOST_RESOLVER_IMPL", + LoadLogUtil::PrettyPrintAsEventTree(log)); +} + +} // namespace +} // namespace net diff --git a/net/net.gyp b/net/net.gyp index 0f81b29..0a377d6 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -83,6 +83,10 @@ 'base/listen_socket.h', 'base/load_flags.h', 'base/load_log.h', + 'base/load_log.cc', + 'base/load_log_event_type_list.h', + 'base/load_log_util.cc', + 'base/load_log_util.h', 'base/load_states.h', 'base/mime_sniffer.cc', 'base/mime_sniffer.h', @@ -454,6 +458,9 @@ 'base/gzip_filter_unittest.cc', 'base/host_cache_unittest.cc', 'base/host_resolver_impl_unittest.cc', + 'base/load_log_unittest.cc', + 'base/load_log_unittest.h', + 'base/load_log_util_unittest.cc', 'base/listen_socket_unittest.cc', 'base/listen_socket_unittest.h', 'base/mime_sniffer_unittest.cc', |