| Index: base/tracked_objects.cc
|
| ===================================================================
|
| --- base/tracked_objects.cc (revision 107798)
|
| +++ base/tracked_objects.cc (working copy)
|
| @@ -16,43 +16,52 @@
|
|
|
| namespace tracked_objects {
|
|
|
| -namespace {
|
| -// Flag to compile out almost all of the task tracking code.
|
| +
|
| +#if defined(TRACK_ALL_TASK_OBJECTS)
|
| static const bool kTrackAllTaskObjects = true;
|
| +#else
|
| +static const bool kTrackAllTaskObjects = false;
|
| +#endif
|
|
|
| -// 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.
|
| +// 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;
|
|
|
| +// 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 Duration& queue_duration,
|
| - const Duration& run_duration) {
|
| +void DeathData::RecordDeath(const TimeDelta& queue_duration,
|
| + const TimeDelta& run_duration) {
|
| ++count_;
|
| queue_duration_ += queue_duration;
|
| run_duration_ += run_duration;
|
| }
|
|
|
| int DeathData::AverageMsRunDuration() const {
|
| - if (run_duration_ == Duration() || !count_)
|
| + if (run_duration_ == base::TimeDelta())
|
| return 0;
|
| - // Add half of denominator to achieve rounding.
|
| - return static_cast<int>(run_duration_.InMilliseconds() + count_ / 2) /
|
| - count_;
|
| + return static_cast<int>(run_duration_.InMilliseconds() / count_);
|
| }
|
|
|
| int DeathData::AverageMsQueueDuration() const {
|
| - if (queue_duration_ == Duration() || !count_)
|
| + if (queue_duration_ == base::TimeDelta())
|
| return 0;
|
| - // Add half of denominator to achieve rounding.
|
| - return (static_cast<int>(queue_duration_.InMilliseconds() + count_ / 2) /
|
| - count_);
|
| + return static_cast<int>(queue_duration_.InMilliseconds() / count_);
|
| }
|
|
|
| void DeathData::AddDeathData(const DeathData& other) {
|
| @@ -66,14 +75,11 @@
|
| return;
|
| base::StringAppendF(output, "%s:%d, ",
|
| (count_ == 1) ? "Life" : "Lives", count_);
|
| - // 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()),
|
| + base::StringAppendF(output, "Run:%"PRId64"ms(%dms/life) ",
|
| + run_duration_.InMilliseconds(),
|
| AverageMsRunDuration());
|
| - base::StringAppendF(output, "Queue:%dms(%dms/life) ",
|
| - static_cast<int>(queue_duration_.InMilliseconds()),
|
| + base::StringAppendF(output, "Queue:%"PRId64"ms(%dms/life) ",
|
| + queue_duration_.InMilliseconds(),
|
| AverageMsQueueDuration());
|
| }
|
|
|
| @@ -89,8 +95,8 @@
|
|
|
| void DeathData::Clear() {
|
| count_ = 0;
|
| - queue_duration_ = Duration();
|
| - run_duration_ = Duration();
|
| + queue_duration_ = TimeDelta();
|
| + run_duration_ = TimeDelta();
|
| }
|
|
|
| //------------------------------------------------------------------------------
|
| @@ -107,28 +113,14 @@
|
| //------------------------------------------------------------------------------
|
| // 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
|
| 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;
|
| @@ -144,7 +136,7 @@
|
| ThreadData::ThreadData() : 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);
|
| @@ -155,15 +147,15 @@
|
|
|
| void ThreadData::PushToHeadOfList() {
|
| DCHECK(!next_);
|
| - base::AutoLock lock(*list_lock_);
|
| + base::AutoLock lock(list_lock_);
|
| next_ = all_thread_data_list_head_;
|
| all_thread_data_list_head_ = this;
|
| }
|
|
|
| // static
|
| void ThreadData::InitializeThreadContext(const std::string& suggested_name) {
|
| - if (!Initialize()) // Always initialize if needed.
|
| - return;
|
| + 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);
|
| tls_index_.Set(current_thread_data);
|
| @@ -180,7 +172,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());
|
| @@ -211,13 +203,13 @@
|
| tls_index_.Set(NULL);
|
| if (!is_a_worker_thread_)
|
| return;
|
| - base::AutoLock lock(*list_lock_);
|
| + base::AutoLock lock(list_lock_);
|
| unregistered_thread_data_pool_->push(this);
|
| }
|
|
|
| // static
|
| void ThreadData::WriteHTML(const std::string& query, std::string* output) {
|
| - if (!ThreadData::tracking_status())
|
| + if (!ThreadData::IsActive())
|
| return; // Not yet initialized.
|
|
|
| DataCollector collected_data; // Gather data.
|
| @@ -324,12 +316,13 @@
|
| }
|
|
|
| // static
|
| -base::DictionaryValue* ThreadData::ToValue() {
|
| +base::Value* ThreadData::ToValue(int process_type) {
|
| 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;
|
| }
|
|
|
| @@ -349,8 +342,8 @@
|
| }
|
|
|
| void ThreadData::TallyADeath(const Births& birth,
|
| - const Duration& queue_duration,
|
| - const Duration& run_duration) {
|
| + const TimeDelta& queue_duration,
|
| + const TimeDelta& run_duration) {
|
| DeathMap::iterator it = death_map_.find(&birth);
|
| DeathData* death_data;
|
| if (it != death_map_.end()) {
|
| @@ -367,7 +360,7 @@
|
| if (!kTrackAllTaskObjects)
|
| return NULL; // Not compiled in.
|
|
|
| - if (!tracking_status())
|
| + if (!IsActive())
|
| return NULL;
|
| ThreadData* current_thread_data = Get();
|
| if (!current_thread_data)
|
| @@ -376,74 +369,37 @@
|
| }
|
|
|
| // static
|
| -void ThreadData::TallyRunOnNamedThreadIfTracking(
|
| - const MessageLoop::TrackingInfo& completed_task,
|
| - const TrackedTime& start_of_run,
|
| - const TrackedTime& end_of_run) {
|
| +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) {
|
| 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.
|
| - const Births* birth = completed_task.birth_tally;
|
| - if (!birth)
|
| + if (!IsActive() || !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 scheduled (requested?) to emerge from the delayed task queue. This
|
| + // were sechudled (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.
|
| - 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);
|
| -
|
| - Duration queue_duration = start_of_run - effective_post_time;
|
| - Duration run_duration = end_of_run - start_of_run;
|
| + 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;
|
| 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 = start_of_run - time_posted;
|
| - Duration 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_;
|
| }
|
|
|
| @@ -483,47 +439,52 @@
|
| it->second->Clear();
|
| }
|
|
|
| -bool ThreadData::Initialize() {
|
| +// static
|
| +bool ThreadData::StartTracking(bool status) {
|
| if (!kTrackAllTaskObjects)
|
| return false; // Not compiled in.
|
| - 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
|
| +
|
| + // 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
|
| // process termination.
|
| - if (!tls_index_.initialized()) // Testing may have initialized this.
|
| + if (!tls_index_.initialized())
|
| tls_index_.Initialize(&ThreadData::OnThreadTermination);
|
| DCHECK(tls_index_.initialized());
|
| - unregistered_thread_data_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;
|
| - return true;
|
| -}
|
|
|
| -// static
|
| -bool ThreadData::InitializeAndSetTrackingStatus(bool status) {
|
| - if (!Initialize()) // No-op if already initialized.
|
| - return false; // Not compiled in.
|
| -
|
| - status_ = status ? ACTIVE : DEACTIVATED;
|
| + 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;
|
| return true;
|
| }
|
|
|
| // static
|
| -bool ThreadData::tracking_status() {
|
| +bool ThreadData::IsActive() {
|
| return status_ == ACTIVE;
|
| }
|
|
|
| // static
|
| -TrackedTime ThreadData::Now() {
|
| - if (!kTrackAllTaskObjects || status_ != ACTIVE)
|
| - return TrackedTime(); // Super fast when disabled, or not compiled.
|
| - return TrackedTime::Now();
|
| +base::TimeTicks ThreadData::Now() {
|
| + if (kTrackAllTaskObjects && status_ == ACTIVE)
|
| + return base::TimeTicks::Now();
|
| + return base::TimeTicks(); // Super fast when disabled, or not compiled in.
|
| }
|
|
|
| // static
|
| @@ -531,12 +492,12 @@
|
| // 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 (!InitializeAndSetTrackingStatus(false))
|
| + if (!StartTracking(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_;
|
| @@ -613,13 +574,15 @@
|
| return dictionary;
|
| }
|
|
|
| +void Snapshot::Add(const Snapshot& other) {
|
| + death_data_.AddDeathData(other.death_data_);
|
| +}
|
| +
|
| //------------------------------------------------------------------------------
|
| // DataCollector
|
|
|
| DataCollector::DataCollector() {
|
| - if (!kTrackAllTaskObjects)
|
| - return; // Not compiled in.
|
| - if (!ThreadData::tracking_status())
|
| + if (!ThreadData::IsActive())
|
| return;
|
|
|
| // Get an unchanging copy of a ThreadData list.
|
| @@ -777,35 +740,6 @@
|
| 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_) {
|
| @@ -1022,6 +956,28 @@
|
| }
|
|
|
| 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);
|
| @@ -1031,14 +987,13 @@
|
| }
|
| std::string keyword(key_phrase.substr(0, equal_offset));
|
| keyword = StringToLowerASCII(keyword);
|
| - Selector selector = FindSelector(keyword);
|
| - if (selector == UNKNOWN_KEYWORD)
|
| - return;
|
| - if (selector == RESET_ALL_DATA) {
|
| + KeyMap::iterator it = key_map.find(keyword);
|
| + if (key_map.end() == it)
|
| + return; // Unknown keyword.
|
| + if (it->second == RESET_ALL_DATA)
|
| ThreadData::ResetAllThreadData();
|
| - return;
|
| - }
|
| - SetTiebreaker(selector, required);
|
| + else
|
| + SetTiebreaker(key_map[keyword], required);
|
| }
|
|
|
| bool Comparator::ParseQuery(const std::string& query) {
|
|
|