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) { |