Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(699)

Unified Diff: base/tracked_objects.cc

Issue 8429009: Enable tracking of objects by default (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 9 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « base/tracked_objects.h ('k') | base/tracked_objects_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: base/tracked_objects.cc
===================================================================
--- base/tracked_objects.cc (revision 107970)
+++ base/tracked_objects.cc (working copy)
@@ -11,57 +11,54 @@
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
using base::TimeDelta;
namespace tracked_objects {
-
-#if defined(TRACK_ALL_TASK_OBJECTS)
+namespace {
+// Flag to compile out almost all of the task tracking code.
+#if defined(NDEBUG) && defined(OS_MAC)
+// Avoid problems with base_unittest crashes in Mac for now.
+static const bool kTrackAllTaskObjects = false;
+#else
static const bool kTrackAllTaskObjects = true;
-#else
-static const bool kTrackAllTaskObjects = false;
#endif
-// Can we count on thread termination to call for thread cleanup? If not, then
-// we can't risk putting references to ThreadData in TLS, as it will leak on
-// worker thread termination.
-static const bool kWorkerThreadCleanupSupported = true;
+// When ThreadData is first initialized, should we start in an ACTIVE state to
+// record all of the startup-time tasks, or should we start up DEACTIVATED, so
+// that we only record after parsing the command line flag --enable-tracking.
+// Note that the flag may force either state, so this really controls only the
+// period of time up until that flag is parsed. If there is no flag seen, then
+// this state may prevail for much or all of the process lifetime.
+static const ThreadData::Status kInitialStartupState = ThreadData::ACTIVE;
+} // anonymous namespace.
-// A TLS slot which points to the ThreadData instance for the current thread. We
-// do a fake initialization here (zeroing out data), and then the real in-place
-// construction happens when we call tls_index_.Initialize().
-// static
-base::ThreadLocalStorage::Slot ThreadData::tls_index_(base::LINKER_INITIALIZED);
-
-// A global state variable to prevent repeated initialization during tests.
-// static
-AutoTracking::State AutoTracking::state_ = AutoTracking::kNeverBeenRun;
-
-// A locked protected counter to assign sequence number to threads.
-// static
-int ThreadData::thread_number_counter_ = 0;
-
//------------------------------------------------------------------------------
// Death data tallies durations when a death takes place.
-void DeathData::RecordDeath(const TimeDelta& queue_duration,
- const TimeDelta& run_duration) {
+void DeathData::RecordDeath(const Duration& queue_duration,
+ const Duration& run_duration) {
++count_;
queue_duration_ += queue_duration;
run_duration_ += run_duration;
}
int DeathData::AverageMsRunDuration() const {
- if (run_duration_ == base::TimeDelta())
+ if (run_duration_ == Duration() || !count_)
return 0;
- return static_cast<int>(run_duration_.InMilliseconds() / count_);
+ // Add half of denominator to achieve rounding.
+ return static_cast<int>(run_duration_.InMilliseconds() + count_ / 2) /
+ count_;
}
int DeathData::AverageMsQueueDuration() const {
- if (queue_duration_ == base::TimeDelta())
+ if (queue_duration_ == Duration() || !count_)
return 0;
- return static_cast<int>(queue_duration_.InMilliseconds() / count_);
+ // Add half of denominator to achieve rounding.
+ return (static_cast<int>(queue_duration_.InMilliseconds() + count_ / 2) /
+ count_);
}
void DeathData::AddDeathData(const DeathData& other) {
@@ -75,11 +72,14 @@
return;
base::StringAppendF(output, "%s:%d, ",
(count_ == 1) ? "Life" : "Lives", count_);
- base::StringAppendF(output, "Run:%"PRId64"ms(%dms/life) ",
- run_duration_.InMilliseconds(),
+ // Be careful to leave static_casts intact, as the type returned by
+ // InMilliseconds() may not always be an int, even if it can generally fit
+ // into an int.
+ base::StringAppendF(output, "Run:%dms(%dms/life) ",
+ static_cast<int>(run_duration_.InMilliseconds()),
AverageMsRunDuration());
- base::StringAppendF(output, "Queue:%"PRId64"ms(%dms/life) ",
- queue_duration_.InMilliseconds(),
+ base::StringAppendF(output, "Queue:%dms(%dms/life) ",
+ static_cast<int>(queue_duration_.InMilliseconds()),
AverageMsQueueDuration());
}
@@ -95,8 +95,8 @@
void DeathData::Clear() {
count_ = 0;
- queue_duration_ = TimeDelta();
- run_duration_ = TimeDelta();
+ queue_duration_ = Duration();
+ run_duration_ = Duration();
}
//------------------------------------------------------------------------------
@@ -113,51 +113,76 @@
//------------------------------------------------------------------------------
// ThreadData maintains the central data for all births and deaths.
+// TODO(jar): We should pull all these static vars together, into a struct, and
+// optimize layout so that we benefit from locality of reference during accesses
+// to them.
+
+// A TLS slot which points to the ThreadData instance for the current thread. We
+// do a fake initialization here (zeroing out data), and then the real in-place
+// construction happens when we call tls_index_.Initialize().
// static
+base::ThreadLocalStorage::Slot ThreadData::tls_index_(base::LINKER_INITIALIZED);
+
+// A lock-protected counter to assign sequence number to threads.
+// static
+int ThreadData::thread_number_counter_ = 0;
+
+// static
+int ThreadData::incarnation_counter_ = 0;
+
+// static
ThreadData* ThreadData::all_thread_data_list_head_ = NULL;
// static
ThreadData::ThreadDataPool* ThreadData::unregistered_thread_data_pool_ = NULL;
// static
-base::Lock ThreadData::list_lock_;
+base::Lock* ThreadData::list_lock_;
// static
ThreadData::Status ThreadData::status_ = ThreadData::UNINITIALIZED;
ThreadData::ThreadData(const std::string& suggested_name)
- : next_(NULL),
+ : incarnation_count_for_pool_(-1),
+ next_(NULL),
is_a_worker_thread_(false) {
DCHECK_GE(suggested_name.size(), 0u);
thread_name_ = suggested_name;
- PushToHeadOfList();
+ PushToHeadOfList(); // Which sets real incarnation_count_for_pool_.
}
-ThreadData::ThreadData() : next_(NULL), is_a_worker_thread_(true) {
+ThreadData::ThreadData()
+ : incarnation_count_for_pool_(-1),
+ next_(NULL),
+ is_a_worker_thread_(true) {
int thread_number;
{
- base::AutoLock lock(list_lock_);
+ base::AutoLock lock(*list_lock_);
thread_number = ++thread_number_counter_;
}
base::StringAppendF(&thread_name_, "WorkerThread-%d", thread_number);
- PushToHeadOfList();
+ PushToHeadOfList(); // Which sets real incarnation_count_for_pool_.
}
ThreadData::~ThreadData() {}
void ThreadData::PushToHeadOfList() {
DCHECK(!next_);
- base::AutoLock lock(list_lock_);
+ base::AutoLock lock(*list_lock_);
+ incarnation_count_for_pool_ = incarnation_counter_;
next_ = all_thread_data_list_head_;
all_thread_data_list_head_ = this;
}
// static
void ThreadData::InitializeThreadContext(const std::string& suggested_name) {
- if (!tls_index_.initialized())
- return; // For unittests only.
- DCHECK_EQ(tls_index_.Get(), reinterpret_cast<void*>(NULL));
- ThreadData* current_thread_data = new ThreadData(suggested_name);
+ if (!Initialize()) // Always initialize if needed.
+ return;
+ ThreadData* current_thread_data =
+ reinterpret_cast<ThreadData*>(tls_index_.Get());
+ if (current_thread_data)
+ return; // Browser tests instigate this.
+ current_thread_data = new ThreadData(suggested_name);
tls_index_.Set(current_thread_data);
}
@@ -172,7 +197,7 @@
// We must be a worker thread, since we didn't pre-register.
ThreadData* worker_thread_data = NULL;
{
- base::AutoLock lock(list_lock_);
+ base::AutoLock lock(*list_lock_);
if (!unregistered_thread_data_pool_->empty()) {
worker_thread_data =
const_cast<ThreadData*>(unregistered_thread_data_pool_->top());
@@ -192,24 +217,29 @@
void ThreadData::OnThreadTermination(void* thread_data) {
if (!kTrackAllTaskObjects)
return; // Not compiled in.
- DCHECK(tls_index_.initialized());
if (!thread_data)
return;
reinterpret_cast<ThreadData*>(thread_data)->OnThreadTerminationCleanup();
- DCHECK_EQ(tls_index_.Get(), reinterpret_cast<ThreadData*>(NULL));
}
void ThreadData::OnThreadTerminationCleanup() const {
- tls_index_.Set(NULL);
if (!is_a_worker_thread_)
return;
- base::AutoLock lock(list_lock_);
- unregistered_thread_data_pool_->push(this);
+ base::AutoLock lock(*list_lock_);
+ if (incarnation_counter_ != incarnation_count_for_pool_)
+ return; // ThreadData was constructed in an earlier unit test.
+
+ // Handle case where we are in unit tests, and have become UNINITIALIZED.
+ // In that case, the pool might be NULL. We really should detect this via the
+ // incarnation_counter_, but this call is rarely made, so we can afford to
+ // code defensively.
+ if (unregistered_thread_data_pool_)
+ unregistered_thread_data_pool_->push(this);
}
// static
void ThreadData::WriteHTML(const std::string& query, std::string* output) {
- if (!ThreadData::IsActive())
+ if (status_ == UNINITIALIZED)
return; // Not yet initialized.
DataCollector collected_data; // Gather data.
@@ -316,13 +346,12 @@
}
// static
-base::Value* ThreadData::ToValue(int process_type) {
+base::DictionaryValue* ThreadData::ToValue() {
DataCollector collected_data; // Gather data.
collected_data.AddListOfLivingObjects(); // Add births that are still alive.
base::ListValue* list = collected_data.ToValue();
base::DictionaryValue* dictionary = new base::DictionaryValue();
dictionary->Set("list", list);
- dictionary->SetInteger("process", process_type);
return dictionary;
}
@@ -342,8 +371,8 @@
}
void ThreadData::TallyADeath(const Births& birth,
- const TimeDelta& queue_duration,
- const TimeDelta& run_duration) {
+ const Duration& queue_duration,
+ const Duration& run_duration) {
DeathMap::iterator it = death_map_.find(&birth);
DeathData* death_data;
if (it != death_map_.end()) {
@@ -360,7 +389,7 @@
if (!kTrackAllTaskObjects)
return NULL; // Not compiled in.
- if (!IsActive())
+ if (!tracking_status())
return NULL;
ThreadData* current_thread_data = Get();
if (!current_thread_data)
@@ -369,37 +398,89 @@
}
// static
-void ThreadData::TallyADeathIfActive(const Births* birth,
- const base::TimeTicks& time_posted,
- const base::TimeTicks& delayed_start_time,
- const base::TimeTicks& start_of_run,
- const base::TimeTicks& end_of_run) {
+void ThreadData::TallyRunOnNamedThreadIfTracking(
+ const base::TrackingInfo& completed_task,
+ const TrackedTime& start_of_run,
+ const TrackedTime& end_of_run) {
if (!kTrackAllTaskObjects)
return; // Not compiled in.
- if (!IsActive() || !birth)
+ // Even if we have been DEACTIVATED, we will process any pending births so
+ // that our data structures (which counted the outstanding births) remain
+ // consistent.
+ const Births* birth = completed_task.birth_tally;
+ if (!birth)
return;
-
ThreadData* current_thread_data = Get();
if (!current_thread_data)
return;
// To avoid conflating our stats with the delay duration in a PostDelayedTask,
// we identify such tasks, and replace their post_time with the time they
- // were sechudled (requested?) to emerge from the delayed task queue. This
+ // were scheduled (requested?) to emerge from the delayed task queue. This
// means that queueing delay for such tasks will show how long they went
// unserviced, after they *could* be serviced. This is the same stat as we
// have for non-delayed tasks, and we consistently call it queueing delay.
- base::TimeTicks effective_post_time =
- (delayed_start_time.is_null()) ? time_posted : delayed_start_time;
- base::TimeDelta queue_duration = start_of_run - effective_post_time;
- base::TimeDelta run_duration = end_of_run - start_of_run;
+ TrackedTime effective_post_time = completed_task.delayed_run_time.is_null()
+ ? tracked_objects::TrackedTime(completed_task.time_posted)
+ : tracked_objects::TrackedTime(completed_task.delayed_run_time);
+
+ // Watch out for a race where status_ is changing, and hence one or both
+ // of start_of_run or end_of_run is zero. IN that case, we didn't bother to
+ // get a time value since we "weren't tracking" and we were trying to be
+ // efficient by not calling for a genuine time value. For simplicity, we'll
+ // use a default zero duration when we can't calculate a true value.
+ Duration queue_duration;
+ Duration run_duration;
+ if (!start_of_run.is_null()) {
+ queue_duration = start_of_run - effective_post_time;
+ if (!end_of_run.is_null())
+ run_duration = end_of_run - start_of_run;
+ }
current_thread_data->TallyADeath(*birth, queue_duration, run_duration);
}
// static
+void ThreadData::TallyRunOnWorkerThreadIfTracking(
+ const Births* birth,
+ const TrackedTime& time_posted,
+ const TrackedTime& start_of_run,
+ const TrackedTime& end_of_run) {
+ if (!kTrackAllTaskObjects)
+ return; // Not compiled in.
+
+ // Even if we have been DEACTIVATED, we will process any pending births so
+ // that our data structures (which counted the outstanding births) remain
+ // consistent.
+ if (!birth)
+ return;
+
+ // TODO(jar): Support the option to coalesce all worker-thread activity under
+ // one ThreadData instance that uses locks to protect *all* access. This will
+ // reduce memory (making it provably bounded), but run incrementally slower
+ // (since we'll use locks on TallyBirth and TallyDeath). The good news is
+ // that the locks on TallyDeath will be *after* the worker thread has run, and
+ // hence nothing will be waiting for the completion (... besides some other
+ // thread that might like to run). Also, the worker threads tasks are
+ // generally longer, and hence the cost of the lock may perchance be amortized
+ // over the long task's lifetime.
+ ThreadData* current_thread_data = Get();
+ if (!current_thread_data)
+ return;
+
+ Duration queue_duration;
+ Duration run_duration;
+ if (!start_of_run.is_null()) {
+ queue_duration = start_of_run - time_posted;
+ if (!end_of_run.is_null())
+ run_duration = end_of_run - start_of_run;
+ }
+ current_thread_data->TallyADeath(*birth, queue_duration, run_duration);
+}
+
+// static
ThreadData* ThreadData::first() {
- base::AutoLock lock(list_lock_);
+ base::AutoLock lock(*list_lock_);
return all_thread_data_list_head_;
}
@@ -439,71 +520,86 @@
it->second->Clear();
}
-// static
-bool ThreadData::StartTracking(bool status) {
+bool ThreadData::Initialize() {
if (!kTrackAllTaskObjects)
return false; // Not compiled in.
-
- // Do a bit of class initialization.
- if (!unregistered_thread_data_pool_) {
- ThreadDataPool* initial_pool = new ThreadDataPool;
- {
- base::AutoLock lock(list_lock_);
- if (!unregistered_thread_data_pool_) {
- unregistered_thread_data_pool_ = initial_pool;
- initial_pool = NULL;
- }
- }
- delete initial_pool; // In case it was not used.
- }
-
- // Perform the "real" initialization now, and leave it intact through
+ if (status_ != UNINITIALIZED)
+ return true;
+ // Initialize all leaking constants that are difficult to toggle in and out
+ // of existance.
+ // First call must be made when single threaded at startup.
+ // Perform the "real" TLS initialization now, and leave it intact through
// process termination.
- if (!tls_index_.initialized())
+ if (!tls_index_.initialized()) // Testing may have initialized this.
tls_index_.Initialize(&ThreadData::OnThreadTermination);
DCHECK(tls_index_.initialized());
+ ThreadDataPool* pool = new ThreadDataPool;
+ // TODO(jar): A linker initialized spin lock would be much safer than this
+ // allocation, which relies on being called while single threaded.
+ if (!list_lock_) // In case testing deleted this.
+ list_lock_ = new base::Lock;
+ status_ = kInitialStartupState;
- if (!status) {
- base::AutoLock lock(list_lock_);
- DCHECK(status_ == ACTIVE || status_ == SHUTDOWN);
- status_ = SHUTDOWN;
- return true;
- }
- base::AutoLock lock(list_lock_);
- DCHECK_EQ(UNINITIALIZED, status_);
- status_ = ACTIVE;
+ base::AutoLock lock(*list_lock_);
+ DCHECK_EQ(unregistered_thread_data_pool_,
+ reinterpret_cast<ThreadDataPool*>(NULL));
+ unregistered_thread_data_pool_ = pool;
+ ++incarnation_counter_;
return true;
}
// static
-bool ThreadData::IsActive() {
+bool ThreadData::InitializeAndSetTrackingStatus(bool status) {
+ if (!Initialize()) // No-op if already initialized.
+ return false; // Not compiled in.
+
+ status_ = status ? ACTIVE : DEACTIVATED;
+ return true;
+}
+
+// static
+bool ThreadData::tracking_status() {
return status_ == ACTIVE;
}
// static
-base::TimeTicks ThreadData::Now() {
- if (kTrackAllTaskObjects && status_ == ACTIVE)
- return base::TimeTicks::Now();
- return base::TimeTicks(); // Super fast when disabled, or not compiled in.
+TrackedTime ThreadData::Now() {
+ if (kTrackAllTaskObjects && tracking_status())
+ return TrackedTime::Now();
+ return TrackedTime(); // Super fast when disabled, or not compiled.
}
// static
-void ThreadData::ShutdownSingleThreadedCleanup() {
+void ThreadData::ShutdownSingleThreadedCleanup(bool leak) {
// This is only called from test code, where we need to cleanup so that
// additional tests can be run.
// We must be single threaded... but be careful anyway.
- if (!StartTracking(false))
+ if (!InitializeAndSetTrackingStatus(false))
return;
ThreadData* thread_data_list;
ThreadDataPool* final_pool;
{
- base::AutoLock lock(list_lock_);
+ base::AutoLock lock(*list_lock_);
thread_data_list = all_thread_data_list_head_;
all_thread_data_list_head_ = NULL;
final_pool = unregistered_thread_data_pool_;
unregistered_thread_data_pool_ = NULL;
+ ++incarnation_counter_;
}
+ // Put most global static back in pristine shape.
+ thread_number_counter_ = 0;
+ tls_index_.Set(NULL);
+ status_ = UNINITIALIZED;
+
+ // To avoid any chance of racing in unit tests, which is the only place we
+ // call this function, we may sometimes leak all the data structures we
+ // recovered, as they may still be in use on threads from prior tests!
+ if (leak)
+ return;
+
+ // When we want to cleanup (on a single thread), here is what we do.
+
if (final_pool) {
// The thread_data_list contains *all* the instances, and we'll use it to
// delete them. This pool has pointers to some instances, and we just
@@ -525,10 +621,6 @@
next_thread_data->death_map_.clear();
delete next_thread_data; // Includes all Death Records.
}
- // Put most global static back in pristine shape.
- thread_number_counter_ = 0;
- tls_index_.Set(NULL);
- status_ = UNINITIALIZED;
}
//------------------------------------------------------------------------------
@@ -574,16 +666,12 @@
return dictionary;
}
-void Snapshot::Add(const Snapshot& other) {
- death_data_.AddDeathData(other.death_data_);
-}
-
//------------------------------------------------------------------------------
// DataCollector
DataCollector::DataCollector() {
- if (!ThreadData::IsActive())
- return;
+ if (!kTrackAllTaskObjects)
+ return; // Not compiled in.
// Get an unchanging copy of a ThreadData list.
ThreadData* my_list = ThreadData::first();
@@ -740,6 +828,35 @@
selector_ = NIL;
}
+// static
+Comparator::Selector Comparator::FindSelector(const std::string& keyword) {
+ // Sorting and aggretation keywords, which specify how to sort the data, or
+ // can specify a required match from the specified field in the record.
+ if (0 == keyword.compare("count"))
+ return COUNT;
+ if (0 == keyword.compare("totalduration"))
+ return TOTAL_RUN_DURATION;
+ if (0 == keyword.compare("duration"))
+ return AVERAGE_RUN_DURATION;
+ if (0 == keyword.compare("totalqueueduration"))
+ return TOTAL_QUEUE_DURATION;
+ if (0 == keyword.compare("averagequeueduration"))
+ return AVERAGE_QUEUE_DURATION;
+ if (0 == keyword.compare("birth"))
+ return BIRTH_THREAD;
+ if (0 == keyword.compare("death"))
+ return DEATH_THREAD;
+ if (0 == keyword.compare("file"))
+ return BIRTH_FILE;
+ if (0 == keyword.compare("function"))
+ return BIRTH_FUNCTION;
+ if (0 == keyword.compare("line"))
+ return BIRTH_LINE;
+ if (0 == keyword.compare("reset"))
+ return RESET_ALL_DATA;
+ return UNKNOWN_KEYWORD;
+}
+
bool Comparator::operator()(const Snapshot& left,
const Snapshot& right) const {
switch (selector_) {
@@ -956,28 +1073,6 @@
}
void Comparator::ParseKeyphrase(const std::string& key_phrase) {
- typedef std::map<const std::string, Selector> KeyMap;
- static KeyMap key_map;
- static bool initialized = false;
- if (!initialized) {
- initialized = true;
- // Sorting and aggretation keywords, which specify how to sort the data, or
- // can specify a required match from the specified field in the record.
- key_map["count"] = COUNT;
- key_map["totalduration"] = TOTAL_RUN_DURATION;
- key_map["duration"] = AVERAGE_RUN_DURATION;
- key_map["totalqueueduration"] = TOTAL_QUEUE_DURATION;
- key_map["averagequeueduration"] = AVERAGE_QUEUE_DURATION;
- key_map["birth"] = BIRTH_THREAD;
- key_map["death"] = DEATH_THREAD;
- key_map["file"] = BIRTH_FILE;
- key_map["function"] = BIRTH_FUNCTION;
- key_map["line"] = BIRTH_LINE;
-
- // Immediate commands that do not involve setting sort order.
- key_map["reset"] = RESET_ALL_DATA;
- }
-
std::string required;
// Watch for: "sort_key=value" as we parse.
size_t equal_offset = key_phrase.find('=', 0);
@@ -987,13 +1082,14 @@
}
std::string keyword(key_phrase.substr(0, equal_offset));
keyword = StringToLowerASCII(keyword);
- KeyMap::iterator it = key_map.find(keyword);
- if (key_map.end() == it)
- return; // Unknown keyword.
- if (it->second == RESET_ALL_DATA)
+ Selector selector = FindSelector(keyword);
+ if (selector == UNKNOWN_KEYWORD)
+ return;
+ if (selector == RESET_ALL_DATA) {
ThreadData::ResetAllThreadData();
- else
- SetTiebreaker(key_map[keyword], required);
+ return;
+ }
+ SetTiebreaker(selector, required);
}
bool Comparator::ParseQuery(const std::string& query) {
« no previous file with comments | « base/tracked_objects.h ('k') | base/tracked_objects_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698