Update task tracking to not depend on message_loop_ singleton
I also did a bunch of cleanup, and transitioned to tracking the duration of a run, rather than the time from posting (construction of a task) to completion of the run. It is less interesting for now to track queueing delay, and we need a focus on task execution time. I left in the hook (API) with the expectation that I'll be extending the tracked_objects code to include this as well. I also landed changes to run in Linux/Mac. The fact that I've punted on shutdown made this landing easy (all code support was previously lost during migration to some flavor of bind support). r=willchan,jam,viettrungluu,ajwong BUG=62728 Review URL: http://codereview.chromium.org/8233037 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@105694 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
@@ -476,23 +476,25 @@ void MessageLoop::RunTask(const PendingTask& pending_task) {
|
|||||||
base::debug::Alias(&program_counter);
|
base::debug::Alias(&program_counter);
|
||||||
|
|
||||||
HistogramEvent(kTaskRunEvent);
|
HistogramEvent(kTaskRunEvent);
|
||||||
|
|
||||||
|
#if defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
|
TimeTicks start_of_run = tracked_objects::ThreadData::Now();
|
||||||
|
#endif // defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
|
|
||||||
FOR_EACH_OBSERVER(TaskObserver, task_observers_,
|
FOR_EACH_OBSERVER(TaskObserver, task_observers_,
|
||||||
WillProcessTask(pending_task.time_posted));
|
WillProcessTask(pending_task.time_posted));
|
||||||
pending_task.task.Run();
|
pending_task.task.Run();
|
||||||
FOR_EACH_OBSERVER(TaskObserver, task_observers_,
|
FOR_EACH_OBSERVER(TaskObserver, task_observers_,
|
||||||
DidProcessTask(pending_task.time_posted));
|
DidProcessTask(pending_task.time_posted));
|
||||||
|
|
||||||
#if defined(TRACK_ALL_TASK_OBJECTS)
|
#if defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
tracked_objects::ThreadData::TallyADeathIfActive(
|
tracked_objects::ThreadData::TallyADeathIfActive(pending_task.post_births,
|
||||||
pending_task.post_births,
|
pending_task.time_posted, pending_task.delayed_run_time, start_of_run);
|
||||||
TimeTicks::Now() - pending_task.time_posted);
|
|
||||||
#endif // defined(TRACK_ALL_TASK_OBJECTS)
|
#endif // defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
|
|
||||||
nestable_tasks_allowed_ = true;
|
nestable_tasks_allowed_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MessageLoop::DeferOrRunPendingTask(
|
bool MessageLoop::DeferOrRunPendingTask(const PendingTask& pending_task) {
|
||||||
const PendingTask& pending_task) {
|
|
||||||
if (pending_task.nestable || state_->run_depth == 1) {
|
if (pending_task.nestable || state_->run_depth == 1) {
|
||||||
RunTask(pending_task);
|
RunTask(pending_task);
|
||||||
// Show that we ran a task (Note: a new one might arrive as a
|
// Show that we ran a task (Note: a new one might arrive as a
|
||||||
|
@@ -87,7 +87,15 @@ void WorkerThread::ThreadMain() {
|
|||||||
UNSHIPPED_TRACE_EVENT2("task", "WorkerThread::ThreadMain::Run",
|
UNSHIPPED_TRACE_EVENT2("task", "WorkerThread::ThreadMain::Run",
|
||||||
"src_file", pending_task.posted_from.file_name(),
|
"src_file", pending_task.posted_from.file_name(),
|
||||||
"src_func", pending_task.posted_from.function_name());
|
"src_func", pending_task.posted_from.function_name());
|
||||||
|
|
||||||
|
#if defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
|
TimeTicks start_of_run = tracked_objects::ThreadData::Now();
|
||||||
|
#endif // defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
pending_task.task.Run();
|
pending_task.task.Run();
|
||||||
|
#if defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
|
tracked_objects::ThreadData::TallyADeathIfActive(pending_task.post_births,
|
||||||
|
pending_task.time_posted, TimeTicks::TimeTicks(), start_of_run);
|
||||||
|
#endif // defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The WorkerThread is non-joinable, so it deletes itself.
|
// The WorkerThread is non-joinable, so it deletes itself.
|
||||||
@@ -113,6 +121,10 @@ PosixDynamicThreadPool::PendingTask::PendingTask(
|
|||||||
const base::Closure& task)
|
const base::Closure& task)
|
||||||
: posted_from(posted_from),
|
: posted_from(posted_from),
|
||||||
task(task) {
|
task(task) {
|
||||||
|
#if defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
|
post_births = tracked_objects::ThreadData::TallyABirthIfActive(posted_from);
|
||||||
|
time_posted = tracked_objects::ThreadData::Now();
|
||||||
|
#endif // defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
}
|
}
|
||||||
|
|
||||||
PosixDynamicThreadPool::PendingTask::~PendingTask() {
|
PosixDynamicThreadPool::PendingTask::~PendingTask() {
|
||||||
|
@@ -31,11 +31,13 @@
|
|||||||
#include "base/basictypes.h"
|
#include "base/basictypes.h"
|
||||||
#include "base/callback.h"
|
#include "base/callback.h"
|
||||||
#include "base/location.h"
|
#include "base/location.h"
|
||||||
|
#include "base/time.h"
|
||||||
#include "base/memory/ref_counted.h"
|
#include "base/memory/ref_counted.h"
|
||||||
#include "base/memory/scoped_ptr.h"
|
#include "base/memory/scoped_ptr.h"
|
||||||
#include "base/synchronization/condition_variable.h"
|
#include "base/synchronization/condition_variable.h"
|
||||||
#include "base/synchronization/lock.h"
|
#include "base/synchronization/lock.h"
|
||||||
#include "base/threading/platform_thread.h"
|
#include "base/threading/platform_thread.h"
|
||||||
|
#include "base/tracked_objects.h"
|
||||||
|
|
||||||
class Task;
|
class Task;
|
||||||
|
|
||||||
@@ -50,10 +52,14 @@ class BASE_EXPORT PosixDynamicThreadPool
|
|||||||
PendingTask(const tracked_objects::Location& posted_from,
|
PendingTask(const tracked_objects::Location& posted_from,
|
||||||
const base::Closure& task);
|
const base::Closure& task);
|
||||||
~PendingTask();
|
~PendingTask();
|
||||||
// TODO(ajwong): After we figure out why Mac's ~AtExitManager dies when
|
|
||||||
// destructing the lock, add in extra info so we can call
|
#if defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
// tracked_objects::TallyADeathIfActive() and
|
// Counter for location where the Closure was posted from.
|
||||||
// tracked_objects::TallyABirthIfActive correctly.
|
tracked_objects::Births* post_births;
|
||||||
|
|
||||||
|
// Time the task was posted.
|
||||||
|
TimeTicks time_posted;
|
||||||
|
#endif // defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
|
|
||||||
const tracked_objects::Location posted_from;
|
const tracked_objects::Location posted_from;
|
||||||
|
|
||||||
|
@@ -22,7 +22,7 @@ struct PendingTask {
|
|||||||
task(task) {
|
task(task) {
|
||||||
#if defined(TRACK_ALL_TASK_OBJECTS)
|
#if defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
post_births = tracked_objects::ThreadData::TallyABirthIfActive(posted_from);
|
post_births = tracked_objects::ThreadData::TallyABirthIfActive(posted_from);
|
||||||
time_posted = TimeTicks::Now();
|
time_posted = tracked_objects::ThreadData::Now();
|
||||||
#endif // defined(TRACK_ALL_TASK_OBJECTS)
|
#endif // defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,12 +46,16 @@ DWORD CALLBACK WorkItemCallback(void* param) {
|
|||||||
UNSHIPPED_TRACE_EVENT2("task", "WorkItemCallback::Run",
|
UNSHIPPED_TRACE_EVENT2("task", "WorkItemCallback::Run",
|
||||||
"src_file", pending_task->posted_from.file_name(),
|
"src_file", pending_task->posted_from.file_name(),
|
||||||
"src_func", pending_task->posted_from.function_name());
|
"src_func", pending_task->posted_from.function_name());
|
||||||
|
|
||||||
|
#if defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
|
TimeTicks start_of_run = tracked_objects::ThreadData::Now();
|
||||||
|
#endif // defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
pending_task->task.Run();
|
pending_task->task.Run();
|
||||||
#if defined(TRACK_ALL_TASK_OBJECTS)
|
#if defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
tracked_objects::ThreadData::TallyADeathIfActive(
|
tracked_objects::ThreadData::TallyADeathIfActive(pending_task->post_births,
|
||||||
pending_task->post_births,
|
pending_task->time_posted, TimeTicks::TimeTicks(), start_of_run);
|
||||||
TimeTicks::Now() - pending_task->time_posted);
|
|
||||||
#endif // defined(TRACK_ALL_TASK_OBJECTS)
|
#endif // defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
|
|
||||||
delete pending_task;
|
delete pending_task;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@@ -24,55 +24,57 @@ base::ThreadLocalStorage::Slot ThreadData::tls_index_(base::LINKER_INITIALIZED);
|
|||||||
// static
|
// static
|
||||||
AutoTracking::State AutoTracking::state_ = AutoTracking::kNeverBeenRun;
|
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.
|
// Death data tallies durations when a death takes place.
|
||||||
|
|
||||||
void DeathData::RecordDeath(const TimeDelta& duration) {
|
void DeathData::RecordDeath(const TimeDelta& queue_duration,
|
||||||
|
const TimeDelta& run_duration) {
|
||||||
++count_;
|
++count_;
|
||||||
life_duration_ += duration;
|
queue_duration_ += queue_duration;
|
||||||
int64 milliseconds = duration.InMilliseconds();
|
run_duration_ += run_duration;
|
||||||
square_duration_ += milliseconds * milliseconds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int DeathData::AverageMsDuration() const {
|
int DeathData::AverageMsRunDuration() const {
|
||||||
return static_cast<int>(life_duration_.InMilliseconds() / count_);
|
return static_cast<int>(run_duration_.InMilliseconds() / count_);
|
||||||
}
|
}
|
||||||
|
|
||||||
double DeathData::StandardDeviation() const {
|
int DeathData::AverageMsQueueDuration() const {
|
||||||
double average = AverageMsDuration();
|
return static_cast<int>(queue_duration_.InMilliseconds() / count_);
|
||||||
double variance = static_cast<float>(square_duration_)/count_
|
|
||||||
- average * average;
|
|
||||||
return sqrt(variance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DeathData::AddDeathData(const DeathData& other) {
|
void DeathData::AddDeathData(const DeathData& other) {
|
||||||
count_ += other.count_;
|
count_ += other.count_;
|
||||||
life_duration_ += other.life_duration_;
|
queue_duration_ += other.queue_duration_;
|
||||||
square_duration_ += other.square_duration_;
|
run_duration_ += other.run_duration_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeathData::Write(std::string* output) const {
|
void DeathData::Write(std::string* output) const {
|
||||||
if (!count_)
|
if (!count_)
|
||||||
return;
|
return;
|
||||||
if (1 == count_) {
|
base::StringAppendF(output, "%s:%d, ",
|
||||||
base::StringAppendF(output, "(1)Life in %dms ", AverageMsDuration());
|
(count_ == 1) ? "Life" : "Lives", count_);
|
||||||
} else {
|
base::StringAppendF(output, "Run:%"PRId64"ms(%dms/life) ",
|
||||||
base::StringAppendF(output, "(%d)Lives %dms/life ",
|
run_duration_.InMilliseconds(),
|
||||||
count_, AverageMsDuration());
|
AverageMsRunDuration());
|
||||||
}
|
base::StringAppendF(output, "Queue:%"PRId64"ms(%dms/life) ",
|
||||||
|
queue_duration_.InMilliseconds(),
|
||||||
|
AverageMsQueueDuration());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeathData::Clear() {
|
void DeathData::Clear() {
|
||||||
count_ = 0;
|
count_ = 0;
|
||||||
life_duration_ = TimeDelta();
|
queue_duration_ = TimeDelta();
|
||||||
square_duration_ = 0;
|
run_duration_ = TimeDelta();
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
BirthOnThread::BirthOnThread(const Location& location)
|
BirthOnThread::BirthOnThread(const Location& location)
|
||||||
: location_(location),
|
: location_(location),
|
||||||
birth_thread_(ThreadData::current()) { }
|
birth_thread_(ThreadData::Get()) { }
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
Births::Births(const Location& location)
|
Births::Births(const Location& location)
|
||||||
@@ -90,45 +92,66 @@ base::Lock ThreadData::list_lock_;
|
|||||||
// static
|
// static
|
||||||
ThreadData::Status ThreadData::status_ = ThreadData::UNINITIALIZED;
|
ThreadData::Status ThreadData::status_ = ThreadData::UNINITIALIZED;
|
||||||
|
|
||||||
|
ThreadData::ThreadData(const std::string& suggested_name) : next_(NULL) {
|
||||||
|
DCHECK_GE(suggested_name.size(), 0u);
|
||||||
|
thread_name_ = suggested_name;
|
||||||
|
}
|
||||||
|
|
||||||
ThreadData::ThreadData() : next_(NULL) {
|
ThreadData::ThreadData() : next_(NULL) {
|
||||||
// This shouldn't use the MessageLoop::current() LazyInstance since this might
|
int thread_number;
|
||||||
// be used on a non-joinable thread.
|
{
|
||||||
// http://crbug.com/62728
|
base::AutoLock lock(list_lock_);
|
||||||
base::ThreadRestrictions::ScopedAllowSingleton scoped_allow_singleton;
|
thread_number = ++thread_number_counter;
|
||||||
message_loop_ = MessageLoop::current();
|
}
|
||||||
|
base::StringAppendF(&thread_name_, "WorkerThread-%d", thread_number);
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadData::~ThreadData() {}
|
ThreadData::~ThreadData() {}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
ThreadData* ThreadData::current() {
|
void ThreadData::InitializeThreadContext(const std::string& suggested_name) {
|
||||||
if (!tls_index_.initialized())
|
if (!tls_index_.initialized())
|
||||||
return NULL;
|
return; // For unittests only.
|
||||||
|
RegisterCurrentContext(new ThreadData(suggested_name));
|
||||||
|
}
|
||||||
|
|
||||||
ThreadData* registry = static_cast<ThreadData*>(tls_index_.Get());
|
// static
|
||||||
if (!registry) {
|
ThreadData* ThreadData::Get() {
|
||||||
// We have to create a new registry for ThreadData.
|
if (!tls_index_.initialized())
|
||||||
bool too_late_to_create = false;
|
return NULL; // For unittests only.
|
||||||
{
|
ThreadData* registered = static_cast<ThreadData*>(tls_index_.Get());
|
||||||
registry = new ThreadData;
|
if (!registered) {
|
||||||
base::AutoLock lock(list_lock_);
|
// We have to create a new registry entry for this ThreadData.
|
||||||
// Use lock to insure we have most recent status.
|
// TODO(jar): Host all unamed (Worker) threads in *one* ThreadData instance,
|
||||||
if (!IsActive()) {
|
// (with locking protection on that instance) or else recycle and re-use
|
||||||
too_late_to_create = true;
|
// worker thread ThreadData when the worker thread terminates.
|
||||||
} else {
|
registered = RegisterCurrentContext(new ThreadData());
|
||||||
// Use lock to insert into list.
|
|
||||||
registry->next_ = first_;
|
|
||||||
first_ = registry;
|
|
||||||
}
|
|
||||||
} // Release lock.
|
|
||||||
if (too_late_to_create) {
|
|
||||||
delete registry;
|
|
||||||
registry = NULL;
|
|
||||||
} else {
|
|
||||||
tls_index_.Set(registry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return registry;
|
return registered;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
ThreadData* ThreadData::RegisterCurrentContext(ThreadData* unregistered) {
|
||||||
|
DCHECK_EQ(tls_index_.Get(), static_cast<void*>(0));
|
||||||
|
bool too_late_to_register = false;
|
||||||
|
{
|
||||||
|
base::AutoLock lock(list_lock_);
|
||||||
|
// Use lock to insure we have most recent status.
|
||||||
|
if (!IsActive()) {
|
||||||
|
too_late_to_register = true;
|
||||||
|
} else {
|
||||||
|
// Use list_lock_ to insert as new head of list.
|
||||||
|
unregistered->next_ = first_;
|
||||||
|
first_ = unregistered;
|
||||||
|
}
|
||||||
|
} // Release lock.
|
||||||
|
if (too_late_to_register) {
|
||||||
|
delete unregistered;
|
||||||
|
unregistered = NULL;
|
||||||
|
} else {
|
||||||
|
tls_index_.Set(unregistered);
|
||||||
|
}
|
||||||
|
return unregistered;
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
@@ -136,7 +159,6 @@ void ThreadData::WriteHTML(const std::string& query, std::string* output) {
|
|||||||
if (!ThreadData::IsActive())
|
if (!ThreadData::IsActive())
|
||||||
return; // Not yet initialized.
|
return; // Not yet initialized.
|
||||||
|
|
||||||
DCHECK(ThreadData::current());
|
|
||||||
DataCollector collected_data; // Gather data.
|
DataCollector collected_data; // Gather data.
|
||||||
collected_data.AddListOfLivingObjects(); // Add births that are still alive.
|
collected_data.AddListOfLivingObjects(); // Add births that are still alive.
|
||||||
|
|
||||||
@@ -165,19 +187,26 @@ void ThreadData::WriteHTML(const std::string& query, std::string* output) {
|
|||||||
|
|
||||||
const char* help_string = "The following are the keywords that can be used to"
|
const char* help_string = "The following are the keywords that can be used to"
|
||||||
" sort and aggregate the data, or to select data.<br><ul>"
|
" sort and aggregate the data, or to select data.<br><ul>"
|
||||||
"<li><b>count</b> Number of instances seen."
|
"<li><b>Count</b> Number of instances seen."
|
||||||
"<li><b>duration</b> Duration in ms from construction to descrution."
|
"<li><b>Duration</b> Average duration in ms of Run() time."
|
||||||
"<li><b>birth</b> Thread on which the task was constructed."
|
"<li><b>TotalDuration</b> Summed durations in ms of Run() times."
|
||||||
"<li><b>death</b> Thread on which the task was run and deleted."
|
"<li><b>AverageQueueDuration</b> Average duration in ms of queueing time."
|
||||||
"<li><b>file</b> File in which the task was contructed."
|
"<li><b>TotalQueueDuration</b> Summed durations in ms of Run() times."
|
||||||
"<li><b>function</b> Function in which the task was constructed."
|
"<li><b>Birth</b> Thread on which the task was constructed."
|
||||||
"<li><b>line</b> Line number of the file in which the task was constructed."
|
"<li><b>Death</b> Thread on which the task was run and deleted."
|
||||||
|
"<li><b>File</b> File in which the task was contructed."
|
||||||
|
"<li><b>Function</b> Function in which the task was constructed."
|
||||||
|
"<li><b>Line</b> Line number of the file in which the task was constructed."
|
||||||
"</ul><br>"
|
"</ul><br>"
|
||||||
"As examples:<ul>"
|
"As examples:<ul>"
|
||||||
"<li><b>about:tracking/file</b> would sort the above data by file, and"
|
"<li><b>about:tracking/file</b> would sort the above data by file, and"
|
||||||
" aggregate data on a per-file basis."
|
" aggregate data on a per-file basis."
|
||||||
"<li><b>about:tracking/file=Dns</b> would only list data for tasks"
|
"<li><b>about:tracking/file=Dns</b> would only list data for tasks"
|
||||||
" constructed in a file containing the text |Dns|."
|
" constructed in a file containing the text |Dns|."
|
||||||
|
"<li><b>about:tracking/death/duration</b> would sort the data by death"
|
||||||
|
" thread(i.e., where tasks ran) and then by the average runtime for the"
|
||||||
|
" tasks. Form an aggregation group, one per thread, showing the results on"
|
||||||
|
" each thread."
|
||||||
"<li><b>about:tracking/birth/death</b> would sort the above list by birth"
|
"<li><b>about:tracking/birth/death</b> would sort the above list by birth"
|
||||||
" thread, and then by death thread, and would aggregate data for each pair"
|
" thread, and then by death thread, and would aggregate data for each pair"
|
||||||
" of lifetime events."
|
" of lifetime events."
|
||||||
@@ -234,15 +263,6 @@ void ThreadData::WriteHTMLTotalAndSubtotals(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Births* ThreadData::TallyABirth(const Location& location) {
|
Births* ThreadData::TallyABirth(const Location& location) {
|
||||||
{
|
|
||||||
// This shouldn't use the MessageLoop::current() LazyInstance since this
|
|
||||||
// might be used on a non-joinable thread.
|
|
||||||
// http://crbug.com/62728
|
|
||||||
base::ThreadRestrictions::ScopedAllowSingleton scoped_allow_singleton;
|
|
||||||
if (!message_loop_) // In case message loop wasn't yet around...
|
|
||||||
message_loop_ = MessageLoop::current(); // Find it now.
|
|
||||||
}
|
|
||||||
|
|
||||||
BirthMap::iterator it = birth_map_.find(location);
|
BirthMap::iterator it = birth_map_.find(location);
|
||||||
if (it != birth_map_.end()) {
|
if (it != birth_map_.end()) {
|
||||||
it->second->RecordBirth();
|
it->second->RecordBirth();
|
||||||
@@ -257,43 +277,59 @@ Births* ThreadData::TallyABirth(const Location& location) {
|
|||||||
return tracker;
|
return tracker;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadData::TallyADeath(const Births& lifetimes,
|
void ThreadData::TallyADeath(const Births& the_birth,
|
||||||
const TimeDelta& duration) {
|
const TimeDelta& queue_duration,
|
||||||
{
|
const TimeDelta& run_duration) {
|
||||||
// http://crbug.com/62728
|
DeathMap::iterator it = death_map_.find(&the_birth);
|
||||||
base::ThreadRestrictions::ScopedAllowSingleton scoped_allow_singleton;
|
|
||||||
if (!message_loop_) // In case message loop wasn't yet around...
|
|
||||||
message_loop_ = MessageLoop::current(); // Find it now.
|
|
||||||
}
|
|
||||||
|
|
||||||
DeathMap::iterator it = death_map_.find(&lifetimes);
|
|
||||||
if (it != death_map_.end()) {
|
if (it != death_map_.end()) {
|
||||||
it->second.RecordDeath(duration);
|
it->second.RecordDeath(queue_duration, run_duration);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
base::AutoLock lock(lock_); // Lock since the map may get relocated now.
|
base::AutoLock lock(lock_); // Lock since the map may get relocated now.
|
||||||
death_map_[&lifetimes].RecordDeath(duration);
|
death_map_[&the_birth].RecordDeath(queue_duration, run_duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
Births* ThreadData::TallyABirthIfActive(const Location& location) {
|
Births* ThreadData::TallyABirthIfActive(const Location& location) {
|
||||||
if (IsActive()) {
|
#if !defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
ThreadData* current_thread_data = current();
|
return NULL; // Not compiled in.
|
||||||
if (current_thread_data) {
|
#else
|
||||||
return current_thread_data->TallyABirth(location);
|
if (!IsActive())
|
||||||
}
|
return NULL;
|
||||||
}
|
ThreadData* current_thread_data = Get();
|
||||||
|
if (!current_thread_data)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
return current_thread_data->TallyABirth(location);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void ThreadData::TallyADeathIfActive(const Births* the_birth,
|
void ThreadData::TallyADeathIfActive(const Births* the_birth,
|
||||||
const base::TimeDelta& duration) {
|
const base::TimeTicks& time_posted,
|
||||||
if (IsActive() && the_birth) {
|
const base::TimeTicks& delayed_start_time,
|
||||||
current()->TallyADeath(*the_birth, duration);
|
const base::TimeTicks& start_of_run) {
|
||||||
}
|
#if !defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
|
return; // Not compiled in.
|
||||||
|
#else
|
||||||
|
if (!IsActive() || !the_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
|
||||||
|
// 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 = Now() - start_of_run;
|
||||||
|
current_thread_data->TallyADeath(*the_birth, queue_duration, run_duration);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
@@ -302,12 +338,6 @@ ThreadData* ThreadData::first() {
|
|||||||
return first_;
|
return first_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string ThreadData::ThreadName() const {
|
|
||||||
if (message_loop_)
|
|
||||||
return message_loop_->thread_name();
|
|
||||||
return "ThreadWithoutMessageLoop";
|
|
||||||
}
|
|
||||||
|
|
||||||
// This may be called from another thread.
|
// This may be called from another thread.
|
||||||
void ThreadData::SnapshotBirthMap(BirthMap *output) const {
|
void ThreadData::SnapshotBirthMap(BirthMap *output) const {
|
||||||
base::AutoLock lock(lock_);
|
base::AutoLock lock(lock_);
|
||||||
@@ -326,7 +356,7 @@ void ThreadData::SnapshotDeathMap(DeathMap *output) const {
|
|||||||
|
|
||||||
// static
|
// static
|
||||||
void ThreadData::ResetAllThreadData() {
|
void ThreadData::ResetAllThreadData() {
|
||||||
ThreadData* my_list = ThreadData::current()->first();
|
ThreadData* my_list = Get()->first();
|
||||||
|
|
||||||
for (ThreadData* thread_data = my_list;
|
for (ThreadData* thread_data = my_list;
|
||||||
thread_data;
|
thread_data;
|
||||||
@@ -344,109 +374,11 @@ void ThreadData::Reset() {
|
|||||||
it->second->Clear();
|
it->second->Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef OS_WIN
|
|
||||||
// A class used to count down which is accessed by several threads. This is
|
|
||||||
// used to make sure RunOnAllThreads() actually runs a task on the expected
|
|
||||||
// count of threads.
|
|
||||||
class ThreadData::ThreadSafeDownCounter {
|
|
||||||
public:
|
|
||||||
// Constructor sets the count, once and for all.
|
|
||||||
explicit ThreadSafeDownCounter(size_t count);
|
|
||||||
|
|
||||||
// Decrement the count, and return true if we hit zero. Also delete this
|
|
||||||
// instance automatically when we hit zero.
|
|
||||||
bool LastCaller();
|
|
||||||
|
|
||||||
private:
|
|
||||||
size_t remaining_count_;
|
|
||||||
base::Lock lock_; // protect access to remaining_count_.
|
|
||||||
};
|
|
||||||
|
|
||||||
ThreadData::ThreadSafeDownCounter::ThreadSafeDownCounter(size_t count)
|
|
||||||
: remaining_count_(count) {
|
|
||||||
DCHECK_GT(remaining_count_, 0u);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ThreadData::ThreadSafeDownCounter::LastCaller() {
|
|
||||||
{
|
|
||||||
base::AutoLock lock(lock_);
|
|
||||||
if (--remaining_count_)
|
|
||||||
return false;
|
|
||||||
} // Release lock, so we can delete everything in this instance.
|
|
||||||
delete this;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Task class that runs a static method supplied, and checks to see if this
|
|
||||||
// is the last tasks instance (on last thread) that will run the method.
|
|
||||||
// IF this is the last run, then the supplied event is signalled.
|
|
||||||
class ThreadData::RunTheStatic : public Task {
|
|
||||||
public:
|
|
||||||
typedef void (*FunctionPointer)();
|
|
||||||
RunTheStatic(FunctionPointer function,
|
|
||||||
HANDLE completion_handle,
|
|
||||||
ThreadSafeDownCounter* counter);
|
|
||||||
// Run the supplied static method, and optionally set the event.
|
|
||||||
void Run();
|
|
||||||
|
|
||||||
private:
|
|
||||||
FunctionPointer function_;
|
|
||||||
HANDLE completion_handle_;
|
|
||||||
// Make sure enough tasks are called before completion is signaled.
|
|
||||||
ThreadSafeDownCounter* counter_;
|
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(RunTheStatic);
|
|
||||||
};
|
|
||||||
|
|
||||||
ThreadData::RunTheStatic::RunTheStatic(FunctionPointer function,
|
|
||||||
HANDLE completion_handle,
|
|
||||||
ThreadSafeDownCounter* counter)
|
|
||||||
: function_(function),
|
|
||||||
completion_handle_(completion_handle),
|
|
||||||
counter_(counter) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThreadData::RunTheStatic::Run() {
|
|
||||||
function_();
|
|
||||||
if (counter_->LastCaller())
|
|
||||||
SetEvent(completion_handle_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(jar): This should use condition variables, and be cross platform.
|
|
||||||
void ThreadData::RunOnAllThreads(void (*function)()) {
|
|
||||||
ThreadData* list = first(); // Get existing list.
|
|
||||||
|
|
||||||
std::vector<MessageLoop*> message_loops;
|
|
||||||
for (ThreadData* it = list; it; it = it->next()) {
|
|
||||||
if (current() != it && it->message_loop())
|
|
||||||
message_loops.push_back(it->message_loop());
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadSafeDownCounter* counter =
|
|
||||||
new ThreadSafeDownCounter(message_loops.size() + 1); // Extra one for us!
|
|
||||||
|
|
||||||
HANDLE completion_handle = CreateEvent(NULL, false, false, NULL);
|
|
||||||
// Tell all other threads to run.
|
|
||||||
for (size_t i = 0; i < message_loops.size(); ++i)
|
|
||||||
message_loops[i]->PostTask(
|
|
||||||
FROM_HERE, new RunTheStatic(function, completion_handle, counter));
|
|
||||||
|
|
||||||
// Also run Task on our thread.
|
|
||||||
RunTheStatic local_task(function, completion_handle, counter);
|
|
||||||
local_task.Run();
|
|
||||||
|
|
||||||
WaitForSingleObject(completion_handle, INFINITE);
|
|
||||||
int ret_val = CloseHandle(completion_handle);
|
|
||||||
DCHECK(ret_val);
|
|
||||||
}
|
|
||||||
#endif // OS_WIN
|
|
||||||
|
|
||||||
// static
|
// static
|
||||||
bool ThreadData::StartTracking(bool status) {
|
bool ThreadData::StartTracking(bool status) {
|
||||||
#if !defined(TRACK_ALL_TASK_OBJECTS)
|
#if !defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
return false; // Not compiled in.
|
return false; // Not compiled in.
|
||||||
#endif
|
#else
|
||||||
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
base::AutoLock lock(list_lock_);
|
base::AutoLock lock(list_lock_);
|
||||||
DCHECK(status_ == ACTIVE || status_ == SHUTDOWN);
|
DCHECK(status_ == ACTIVE || status_ == SHUTDOWN);
|
||||||
@@ -458,6 +390,7 @@ bool ThreadData::StartTracking(bool status) {
|
|||||||
CHECK(tls_index_.Initialize(NULL));
|
CHECK(tls_index_.Initialize(NULL));
|
||||||
status_ = ACTIVE;
|
status_ = ACTIVE;
|
||||||
return true;
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
@@ -465,24 +398,14 @@ bool ThreadData::IsActive() {
|
|||||||
return status_ == ACTIVE;
|
return status_ == ACTIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef OS_WIN
|
|
||||||
// static
|
// static
|
||||||
void ThreadData::ShutdownMultiThreadTracking() {
|
base::TimeTicks ThreadData::Now() {
|
||||||
// Using lock, guarantee that no new ThreadData instances will be created.
|
#if defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
if (!StartTracking(false))
|
if (status_ == ACTIVE)
|
||||||
return;
|
return base::TimeTicks::Now();
|
||||||
|
|
||||||
RunOnAllThreads(ShutdownDisablingFurtherTracking);
|
|
||||||
|
|
||||||
// Now the *only* threads that might change the database are the threads with
|
|
||||||
// no messages loops. They might still be adding data to their birth records,
|
|
||||||
// but since no objects are deleted on those threads, there will be no further
|
|
||||||
// access to to cross-thread data.
|
|
||||||
// We could do a cleanup on all threads except for the ones without
|
|
||||||
// MessageLoops, but we won't bother doing cleanup (destruction of data) yet.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
return base::TimeTicks(); // Super fast when disabled, or not compiled in.
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void ThreadData::ShutdownSingleThreadedCleanup() {
|
void ThreadData::ShutdownSingleThreadedCleanup() {
|
||||||
@@ -514,13 +437,6 @@ void ThreadData::ShutdownSingleThreadedCleanup() {
|
|||||||
status_ = UNINITIALIZED;
|
status_ = UNINITIALIZED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
|
||||||
void ThreadData::ShutdownDisablingFurtherTracking() {
|
|
||||||
// Redundantly set status SHUTDOWN on this thread.
|
|
||||||
if (!StartTracking(false))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Individual 3-tuple of birth (place and thread) along with death thread, and
|
// Individual 3-tuple of birth (place and thread) along with death thread, and
|
||||||
// the accumulated stats for instances (DeathData).
|
// the accumulated stats for instances (DeathData).
|
||||||
@@ -541,15 +457,15 @@ Snapshot::Snapshot(const BirthOnThread& birth_on_thread, int count)
|
|||||||
|
|
||||||
const std::string Snapshot::DeathThreadName() const {
|
const std::string Snapshot::DeathThreadName() const {
|
||||||
if (death_thread_)
|
if (death_thread_)
|
||||||
return death_thread_->ThreadName();
|
return death_thread_->thread_name();
|
||||||
return "Still_Alive";
|
return "Still_Alive";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Snapshot::Write(std::string* output) const {
|
void Snapshot::Write(std::string* output) const {
|
||||||
death_data_.Write(output);
|
death_data_.Write(output);
|
||||||
base::StringAppendF(output, "%s->%s ",
|
base::StringAppendF(output, "%s->%s ",
|
||||||
birth_->birth_thread()->ThreadName().c_str(),
|
birth_->birth_thread()->thread_name().c_str(),
|
||||||
death_thread_->ThreadName().c_str());
|
death_thread_->thread_name().c_str());
|
||||||
birth_->location().Write(true, true, output);
|
birth_->location().Write(true, true, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,25 +480,14 @@ DataCollector::DataCollector() {
|
|||||||
DCHECK(ThreadData::IsActive());
|
DCHECK(ThreadData::IsActive());
|
||||||
|
|
||||||
// Get an unchanging copy of a ThreadData list.
|
// Get an unchanging copy of a ThreadData list.
|
||||||
ThreadData* my_list = ThreadData::current()->first();
|
ThreadData* my_list = ThreadData::Get()->first();
|
||||||
|
|
||||||
count_of_contributing_threads_ = 0;
|
// Gather data serially.
|
||||||
for (ThreadData* thread_data = my_list;
|
|
||||||
thread_data;
|
|
||||||
thread_data = thread_data->next()) {
|
|
||||||
++count_of_contributing_threads_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gather data serially. A different constructor could be used to do in
|
|
||||||
// parallel, and then invoke an OnCompletion task.
|
|
||||||
// This hackish approach *can* get some slighly corrupt tallies, as we are
|
// This hackish approach *can* get some slighly corrupt tallies, as we are
|
||||||
// grabbing values without the protection of a lock, but it has the advantage
|
// grabbing values without the protection of a lock, but it has the advantage
|
||||||
// of working even with threads that don't have message loops. If a user
|
// of working even with threads that don't have message loops. If a user
|
||||||
// sees any strangeness, they can always just run their stats gathering a
|
// sees any strangeness, they can always just run their stats gathering a
|
||||||
// second time.
|
// second time.
|
||||||
// TODO(jar): Provide version that gathers stats safely via PostTask in all
|
|
||||||
// cases where thread_data supplies a message_loop to post to. Be careful to
|
|
||||||
// handle message_loops that are destroyed!?!
|
|
||||||
for (ThreadData* thread_data = my_list;
|
for (ThreadData* thread_data = my_list;
|
||||||
thread_data;
|
thread_data;
|
||||||
thread_data = thread_data->next()) {
|
thread_data = thread_data->next()) {
|
||||||
@@ -594,17 +499,12 @@ DataCollector::~DataCollector() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DataCollector::Append(const ThreadData& thread_data) {
|
void DataCollector::Append(const ThreadData& thread_data) {
|
||||||
// Get copy of data (which is done under ThreadData's lock).
|
// Get copy of data.
|
||||||
ThreadData::BirthMap birth_map;
|
ThreadData::BirthMap birth_map;
|
||||||
thread_data.SnapshotBirthMap(&birth_map);
|
thread_data.SnapshotBirthMap(&birth_map);
|
||||||
ThreadData::DeathMap death_map;
|
ThreadData::DeathMap death_map;
|
||||||
thread_data.SnapshotDeathMap(&death_map);
|
thread_data.SnapshotDeathMap(&death_map);
|
||||||
|
|
||||||
// Use our lock to protect our accumulation activity.
|
|
||||||
base::AutoLock lock(accumulation_lock_);
|
|
||||||
|
|
||||||
DCHECK(count_of_contributing_threads_);
|
|
||||||
|
|
||||||
for (ThreadData::DeathMap::const_iterator it = death_map.begin();
|
for (ThreadData::DeathMap::const_iterator it = death_map.begin();
|
||||||
it != death_map.end(); ++it) {
|
it != death_map.end(); ++it) {
|
||||||
collection_.push_back(Snapshot(*it->first, thread_data, it->second));
|
collection_.push_back(Snapshot(*it->first, thread_data, it->second));
|
||||||
@@ -615,17 +515,13 @@ void DataCollector::Append(const ThreadData& thread_data) {
|
|||||||
it != birth_map.end(); ++it) {
|
it != birth_map.end(); ++it) {
|
||||||
global_birth_count_[it->second] += it->second->birth_count();
|
global_birth_count_[it->second] += it->second->birth_count();
|
||||||
}
|
}
|
||||||
|
|
||||||
--count_of_contributing_threads_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DataCollector::Collection* DataCollector::collection() {
|
DataCollector::Collection* DataCollector::collection() {
|
||||||
DCHECK(!count_of_contributing_threads_);
|
|
||||||
return &collection_;
|
return &collection_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataCollector::AddListOfLivingObjects() {
|
void DataCollector::AddListOfLivingObjects() {
|
||||||
DCHECK(!count_of_contributing_threads_);
|
|
||||||
for (BirthCount::iterator it = global_birth_count_.begin();
|
for (BirthCount::iterator it = global_birth_count_.begin();
|
||||||
it != global_birth_count_.end(); ++it) {
|
it != global_birth_count_.end(); ++it) {
|
||||||
if (it->second > 0)
|
if (it->second > 0)
|
||||||
@@ -681,7 +577,7 @@ void Aggregation::Write(std::string* output) const {
|
|||||||
birth_threads_.size());
|
birth_threads_.size());
|
||||||
} else {
|
} else {
|
||||||
base::StringAppendF(output, "All born on %s. ",
|
base::StringAppendF(output, "All born on %s. ",
|
||||||
birth_threads_.begin()->first->ThreadName().c_str());
|
birth_threads_.begin()->first->thread_name().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (death_threads_.size() > 1) {
|
if (death_threads_.size() > 1) {
|
||||||
@@ -690,7 +586,7 @@ void Aggregation::Write(std::string* output) const {
|
|||||||
} else {
|
} else {
|
||||||
if (death_threads_.begin()->first) {
|
if (death_threads_.begin()->first) {
|
||||||
base::StringAppendF(output, "All deleted on %s. ",
|
base::StringAppendF(output, "All deleted on %s. ",
|
||||||
death_threads_.begin()->first->ThreadName().c_str());
|
death_threads_.begin()->first->thread_name().c_str());
|
||||||
} else {
|
} else {
|
||||||
output->append("All these objects are still alive.");
|
output->append("All these objects are still alive.");
|
||||||
}
|
}
|
||||||
@@ -735,10 +631,10 @@ bool Comparator::operator()(const Snapshot& left,
|
|||||||
switch (selector_) {
|
switch (selector_) {
|
||||||
case BIRTH_THREAD:
|
case BIRTH_THREAD:
|
||||||
if (left.birth_thread() != right.birth_thread() &&
|
if (left.birth_thread() != right.birth_thread() &&
|
||||||
left.birth_thread()->ThreadName() !=
|
left.birth_thread()->thread_name() !=
|
||||||
right.birth_thread()->ThreadName())
|
right.birth_thread()->thread_name())
|
||||||
return left.birth_thread()->ThreadName() <
|
return left.birth_thread()->thread_name() <
|
||||||
right.birth_thread()->ThreadName();
|
right.birth_thread()->thread_name();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DEATH_THREAD:
|
case DEATH_THREAD:
|
||||||
@@ -783,11 +679,32 @@ bool Comparator::operator()(const Snapshot& left,
|
|||||||
return left.count() > right.count(); // Sort large at front of vector.
|
return left.count() > right.count(); // Sort large at front of vector.
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AVERAGE_DURATION:
|
case AVERAGE_RUN_DURATION:
|
||||||
if (!left.count() || !right.count())
|
if (!left.count() || !right.count())
|
||||||
break;
|
break;
|
||||||
if (left.AverageMsDuration() != right.AverageMsDuration())
|
if (left.AverageMsRunDuration() != right.AverageMsRunDuration())
|
||||||
return left.AverageMsDuration() > right.AverageMsDuration();
|
return left.AverageMsRunDuration() > right.AverageMsRunDuration();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOTAL_RUN_DURATION:
|
||||||
|
if (!left.count() || !right.count())
|
||||||
|
break;
|
||||||
|
if (left.run_duration() != right.run_duration())
|
||||||
|
return left.run_duration() > right.run_duration();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AVERAGE_QUEUE_DURATION:
|
||||||
|
if (!left.count() || !right.count())
|
||||||
|
break;
|
||||||
|
if (left.AverageMsQueueDuration() != right.AverageMsQueueDuration())
|
||||||
|
return left.AverageMsQueueDuration() > right.AverageMsQueueDuration();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOTAL_QUEUE_DURATION:
|
||||||
|
if (!left.count() || !right.count())
|
||||||
|
break;
|
||||||
|
if (left.queue_duration() != right.queue_duration())
|
||||||
|
return left.queue_duration() > right.queue_duration();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -807,8 +724,8 @@ bool Comparator::Equivalent(const Snapshot& left,
|
|||||||
switch (selector_) {
|
switch (selector_) {
|
||||||
case BIRTH_THREAD:
|
case BIRTH_THREAD:
|
||||||
if (left.birth_thread() != right.birth_thread() &&
|
if (left.birth_thread() != right.birth_thread() &&
|
||||||
left.birth_thread()->ThreadName() !=
|
left.birth_thread()->thread_name() !=
|
||||||
right.birth_thread()->ThreadName())
|
right.birth_thread()->thread_name())
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -837,13 +754,11 @@ bool Comparator::Equivalent(const Snapshot& left,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case COUNT:
|
case COUNT:
|
||||||
if (left.count() != right.count())
|
case AVERAGE_RUN_DURATION:
|
||||||
return false;
|
case TOTAL_RUN_DURATION:
|
||||||
break;
|
case AVERAGE_QUEUE_DURATION:
|
||||||
|
case TOTAL_QUEUE_DURATION:
|
||||||
case AVERAGE_DURATION:
|
// We don't produce separate aggretation when only counts or times differ.
|
||||||
if (left.life_duration() != right.life_duration())
|
|
||||||
return false;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -858,7 +773,7 @@ bool Comparator::Acceptable(const Snapshot& sample) const {
|
|||||||
if (required_.size()) {
|
if (required_.size()) {
|
||||||
switch (selector_) {
|
switch (selector_) {
|
||||||
case BIRTH_THREAD:
|
case BIRTH_THREAD:
|
||||||
if (sample.birth_thread()->ThreadName().find(required_) ==
|
if (sample.birth_thread()->thread_name().find(required_) ==
|
||||||
std::string::npos)
|
std::string::npos)
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
@@ -934,13 +849,16 @@ void Comparator::ParseKeyphrase(const std::string& key_phrase) {
|
|||||||
initialized = true;
|
initialized = true;
|
||||||
// Sorting and aggretation keywords, which specify how to sort the data, or
|
// Sorting and aggretation keywords, which specify how to sort the data, or
|
||||||
// can specify a required match from the specified field in the record.
|
// can specify a required match from the specified field in the record.
|
||||||
key_map["count"] = COUNT;
|
key_map["count"] = COUNT;
|
||||||
key_map["duration"] = AVERAGE_DURATION;
|
key_map["totalduration"] = TOTAL_RUN_DURATION;
|
||||||
key_map["birth"] = BIRTH_THREAD;
|
key_map["duration"] = AVERAGE_RUN_DURATION;
|
||||||
key_map["death"] = DEATH_THREAD;
|
key_map["totalqueueduration"] = TOTAL_QUEUE_DURATION;
|
||||||
key_map["file"] = BIRTH_FILE;
|
key_map["averagequeueduration"] = AVERAGE_QUEUE_DURATION;
|
||||||
key_map["function"] = BIRTH_FUNCTION;
|
key_map["birth"] = BIRTH_THREAD;
|
||||||
key_map["line"] = BIRTH_LINE;
|
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.
|
// Immediate commands that do not involve setting sort order.
|
||||||
key_map["reset"] = RESET_ALL_DATA;
|
key_map["reset"] = RESET_ALL_DATA;
|
||||||
@@ -976,7 +894,8 @@ bool Comparator::ParseQuery(const std::string& query) {
|
|||||||
|
|
||||||
// Select subgroup ordering (if we want to display the subgroup)
|
// Select subgroup ordering (if we want to display the subgroup)
|
||||||
SetSubgroupTiebreaker(COUNT);
|
SetSubgroupTiebreaker(COUNT);
|
||||||
SetSubgroupTiebreaker(AVERAGE_DURATION);
|
SetSubgroupTiebreaker(AVERAGE_RUN_DURATION);
|
||||||
|
SetSubgroupTiebreaker(TOTAL_RUN_DURATION);
|
||||||
SetSubgroupTiebreaker(BIRTH_THREAD);
|
SetSubgroupTiebreaker(BIRTH_THREAD);
|
||||||
SetSubgroupTiebreaker(DEATH_THREAD);
|
SetSubgroupTiebreaker(DEATH_THREAD);
|
||||||
SetSubgroupTiebreaker(BIRTH_FUNCTION);
|
SetSubgroupTiebreaker(BIRTH_FUNCTION);
|
||||||
@@ -992,7 +911,7 @@ bool Comparator::WriteSortGrouping(const Snapshot& sample,
|
|||||||
switch (selector_) {
|
switch (selector_) {
|
||||||
case BIRTH_THREAD:
|
case BIRTH_THREAD:
|
||||||
base::StringAppendF(output, "All new on %s ",
|
base::StringAppendF(output, "All new on %s ",
|
||||||
sample.birth_thread()->ThreadName().c_str());
|
sample.birth_thread()->thread_name().c_str());
|
||||||
wrote_data = true;
|
wrote_data = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -1033,7 +952,7 @@ void Comparator::WriteSnapshot(const Snapshot& sample,
|
|||||||
!(combined_selectors_ & DEATH_THREAD))
|
!(combined_selectors_ & DEATH_THREAD))
|
||||||
base::StringAppendF(output, "%s->%s ",
|
base::StringAppendF(output, "%s->%s ",
|
||||||
(combined_selectors_ & BIRTH_THREAD) ? "*" :
|
(combined_selectors_ & BIRTH_THREAD) ? "*" :
|
||||||
sample.birth().birth_thread()->ThreadName().c_str(),
|
sample.birth().birth_thread()->thread_name().c_str(),
|
||||||
(combined_selectors_ & DEATH_THREAD) ? "*" :
|
(combined_selectors_ & DEATH_THREAD) ? "*" :
|
||||||
sample.DeathThreadName().c_str());
|
sample.DeathThreadName().c_str());
|
||||||
sample.birth().location().Write(!(combined_selectors_ & BIRTH_FILE),
|
sample.birth().location().Write(!(combined_selectors_ & BIRTH_FILE),
|
||||||
|
@@ -31,9 +31,7 @@
|
|||||||
// marginal allocation cost associated with construction or destruction of
|
// marginal allocation cost associated with construction or destruction of
|
||||||
// tracked objects, no locks are generally employed, and probably the largest
|
// tracked objects, no locks are generally employed, and probably the largest
|
||||||
// computational cost is associated with obtaining start and stop times for
|
// computational cost is associated with obtaining start and stop times for
|
||||||
// instances as they are created and destroyed. The introduction of worker
|
// instances as they are created and destroyed.
|
||||||
// threads had a slight impact on this approach, and required use of some locks
|
|
||||||
// when accessing data from the worker threads.
|
|
||||||
//
|
//
|
||||||
// The following describes the lifecycle of tracking an instance.
|
// The following describes the lifecycle of tracking an instance.
|
||||||
//
|
//
|
||||||
@@ -54,9 +52,10 @@
|
|||||||
// The derived Births class contains slots for recording statistics about all
|
// The derived Births class contains slots for recording statistics about all
|
||||||
// instances born at the same location. Statistics currently include only the
|
// instances born at the same location. Statistics currently include only the
|
||||||
// count of instances constructed.
|
// count of instances constructed.
|
||||||
|
//
|
||||||
// Since the base class BirthOnThread contains only constant data, it can be
|
// Since the base class BirthOnThread contains only constant data, it can be
|
||||||
// freely accessed by any thread at any time (i.e., only the statistic needs to
|
// freely accessed by any thread at any time (i.e., only the statistic needs to
|
||||||
// be handled carefully, and it is ONLY read or written by the birth thread).
|
// be handled carefully, and stats are updated exclusively on the birth thread).
|
||||||
//
|
//
|
||||||
// For Tasks, having now either constructed or found the Births instance
|
// For Tasks, having now either constructed or found the Births instance
|
||||||
// described above, a pointer to the Births instance is then recorded into the
|
// described above, a pointer to the Births instance is then recorded into the
|
||||||
@@ -67,7 +66,7 @@
|
|||||||
// can find out a Task's location of birth, and thread of birth, without using
|
// can find out a Task's location of birth, and thread of birth, without using
|
||||||
// any locks, as all that data is constant across the life of the process.
|
// any locks, as all that data is constant across the life of the process.
|
||||||
//
|
//
|
||||||
// This can also be done for any other object as well by calling
|
// The above work *could* also be done for any other object as well by calling
|
||||||
// TallyABirthIfActive() and TallyADeathIfActive() as appropriate.
|
// TallyABirthIfActive() and TallyADeathIfActive() as appropriate.
|
||||||
//
|
//
|
||||||
// The amount of memory used in the above data structures depends on how many
|
// The amount of memory used in the above data structures depends on how many
|
||||||
@@ -81,10 +80,11 @@
|
|||||||
// carefully accumulated. That tallying wrties into slots (members) in a
|
// carefully accumulated. That tallying wrties into slots (members) in a
|
||||||
// collection of DeathData instances. For each birth place Location that is
|
// collection of DeathData instances. For each birth place Location that is
|
||||||
// destroyed on a thread, there is a DeathData instance to record the additional
|
// destroyed on a thread, there is a DeathData instance to record the additional
|
||||||
// death count, as well as accumulate the lifetime duration of the instance as
|
// death count, as well as accumulate the run-time and queue-time durations for
|
||||||
// it is destroyed (dies). By maintaining a single place to aggregate this
|
// the instance as it is destroyed (dies). By maintaining a single place to
|
||||||
// addition *only* for the given thread, we avoid the need to lock such
|
// aggregate this running sum *only* for the given thread, we avoid the need to
|
||||||
// DeathData instances.
|
// lock such DeathData instances. (i.e., these accumulated stats in a DeathData
|
||||||
|
// instance are exclusively updated by the singular owning thread).
|
||||||
//
|
//
|
||||||
// With the above lifecycle description complete, the major remaining detail is
|
// With the above lifecycle description complete, the major remaining detail is
|
||||||
// explaining how each thread maintains a list of DeathData instances, and of
|
// explaining how each thread maintains a list of DeathData instances, and of
|
||||||
@@ -129,18 +129,26 @@
|
|||||||
// birth and death datastructures, but have local (frozen) copies of the actual
|
// birth and death datastructures, but have local (frozen) copies of the actual
|
||||||
// statistics (birth count, durations, etc. etc.).
|
// statistics (birth count, durations, etc. etc.).
|
||||||
//
|
//
|
||||||
// A DataCollector is a container object that holds a set of Snapshots. A
|
// A DataCollector is a container object that holds a set of Snapshots. The
|
||||||
// DataCollector can be passed from thread to thread, and each thread
|
// statistics in a snapshot are gathered asynhcronously relative to their
|
||||||
// contributes to it by adding or updating Snapshot instances. DataCollector
|
// ongoing updates. It is possible, though highly unlikely, that stats such
|
||||||
// instances are thread safe containers which are passed to various threads to
|
// as a 64bit counter could incorrectly recorded by this process. The advantage
|
||||||
// accumulate all Snapshot instances.
|
// to having fast (non-atomic) updates of the data outweighs the minimal risk
|
||||||
|
// of a singular corrupt statistic snapshot (only the snapshot could be corrupt,
|
||||||
|
// not the underlying and ongoing stistic). In constrast, pointer data that is
|
||||||
|
// accessed during snapshotting is completely invariant, and hence is perfectly
|
||||||
|
// acquired (i.e., no potential corruption, and no risk of a bad memory
|
||||||
|
// reference).
|
||||||
//
|
//
|
||||||
// After an array of Snapshots instances are colleted into a DataCollector, they
|
// After an array of Snapshots instances are colleted into a DataCollector, they
|
||||||
// need to be sorted, and possibly aggregated (example: how many threads are in
|
// need to be prepared for display our output. We currently implement a direct
|
||||||
// a specific consecutive set of Snapshots? What was the total birth count for
|
// renderin to HTML, but we will soon also have a JSON serialization as well.
|
||||||
// that set? etc.). Aggregation instances collect running sums of any set of
|
|
||||||
// snapshot instances, and are used to print sub-totals in an about:tracking
|
// For direct HTML display, the data must be sorted, and possibly aggregated
|
||||||
// page.
|
// (example: how many threads are in a specific consecutive set of Snapshots?
|
||||||
|
// What was the total birth count for that set? etc.). Aggregation instances
|
||||||
|
// collect running sums of any set of snapshot instances, and are used to print
|
||||||
|
// sub-totals in an about:tracking page.
|
||||||
//
|
//
|
||||||
// TODO(jar): I need to store DataCollections, and provide facilities for taking
|
// TODO(jar): I need to store DataCollections, and provide facilities for taking
|
||||||
// the difference between two gathered DataCollections. For now, I'm just
|
// the difference between two gathered DataCollections. For now, I'm just
|
||||||
@@ -167,7 +175,7 @@ class BASE_EXPORT BirthOnThread {
|
|||||||
const ThreadData* birth_thread() const { return birth_thread_; }
|
const ThreadData* birth_thread() const { return birth_thread_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// File/lineno of birth. This defines the essence of the type, as the context
|
// File/lineno of birth. This defines the essence of the task, as the context
|
||||||
// of the birth (construction) often tell what the item is for. This field
|
// of the birth (construction) often tell what the item is for. This field
|
||||||
// is const, and hence safe to access from any thread.
|
// is const, and hence safe to access from any thread.
|
||||||
const Location location_;
|
const Location location_;
|
||||||
@@ -213,35 +221,39 @@ class BASE_EXPORT Births: public BirthOnThread {
|
|||||||
class BASE_EXPORT DeathData {
|
class BASE_EXPORT DeathData {
|
||||||
public:
|
public:
|
||||||
// Default initializer.
|
// Default initializer.
|
||||||
DeathData() : count_(0), square_duration_(0) {}
|
DeathData() : count_(0) {}
|
||||||
|
|
||||||
// When deaths have not yet taken place, and we gather data from all the
|
// When deaths have not yet taken place, and we gather data from all the
|
||||||
// threads, we create DeathData stats that tally the number of births without
|
// threads, we create DeathData stats that tally the number of births without
|
||||||
// a corrosponding death.
|
// a corrosponding death.
|
||||||
explicit DeathData(int count) : count_(count), square_duration_(0) {}
|
explicit DeathData(int count) : count_(count) {}
|
||||||
|
|
||||||
void RecordDeath(const base::TimeDelta& duration);
|
// Update stats for a task destruction (death) that had a Run() time of
|
||||||
|
// |duration|, and has had a queueing delay of |queue_duration|.
|
||||||
|
void RecordDeath(const base::TimeDelta& queue_duration,
|
||||||
|
const base::TimeDelta& run_duration);
|
||||||
|
|
||||||
// Metrics accessors.
|
// Metrics accessors.
|
||||||
int count() const { return count_; }
|
int count() const { return count_; }
|
||||||
base::TimeDelta life_duration() const { return life_duration_; }
|
base::TimeDelta run_duration() const { return run_duration_; }
|
||||||
int64 square_duration() const { return square_duration_; }
|
int AverageMsRunDuration() const;
|
||||||
int AverageMsDuration() const;
|
base::TimeDelta queue_duration() const { return queue_duration_; }
|
||||||
double StandardDeviation() const;
|
int AverageMsQueueDuration() const;
|
||||||
|
|
||||||
// Accumulate metrics from other into this.
|
// Accumulate metrics from other into this. This method is never used on
|
||||||
|
// realtime statistics, and only used in snapshots and aggregatinos.
|
||||||
void AddDeathData(const DeathData& other);
|
void AddDeathData(const DeathData& other);
|
||||||
|
|
||||||
// Simple print of internal state.
|
// Simple print of internal state.
|
||||||
void Write(std::string* output) const;
|
void Write(std::string* output) const;
|
||||||
|
|
||||||
// Reset all tallies to zero.
|
// Reset all tallies to zero. This is used as a hack on realtime data.
|
||||||
void Clear();
|
void Clear();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int count_; // Number of destructions.
|
int count_; // Number of destructions.
|
||||||
base::TimeDelta life_duration_; // Sum of all lifetime durations.
|
base::TimeDelta run_duration_; // Sum of all Run()time durations.
|
||||||
int64 square_duration_; // Sum of squares in milliseconds.
|
base::TimeDelta queue_duration_; // Sum of all queue time durations.
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -260,7 +272,6 @@ class BASE_EXPORT Snapshot {
|
|||||||
// When snapshotting a birth, with no death yet, use this:
|
// When snapshotting a birth, with no death yet, use this:
|
||||||
Snapshot(const BirthOnThread& birth_on_thread, int count);
|
Snapshot(const BirthOnThread& birth_on_thread, int count);
|
||||||
|
|
||||||
|
|
||||||
const ThreadData* birth_thread() const { return birth_->birth_thread(); }
|
const ThreadData* birth_thread() const { return birth_->birth_thread(); }
|
||||||
const Location location() const { return birth_->location(); }
|
const Location location() const { return birth_->location(); }
|
||||||
const BirthOnThread& birth() const { return *birth_; }
|
const BirthOnThread& birth() const { return *birth_; }
|
||||||
@@ -269,9 +280,16 @@ class BASE_EXPORT Snapshot {
|
|||||||
const std::string DeathThreadName() const;
|
const std::string DeathThreadName() const;
|
||||||
|
|
||||||
int count() const { return death_data_.count(); }
|
int count() const { return death_data_.count(); }
|
||||||
base::TimeDelta life_duration() const { return death_data_.life_duration(); }
|
base::TimeDelta run_duration() const { return death_data_.run_duration(); }
|
||||||
int64 square_duration() const { return death_data_.square_duration(); }
|
int AverageMsRunDuration() const {
|
||||||
int AverageMsDuration() const { return death_data_.AverageMsDuration(); }
|
return death_data_.AverageMsRunDuration();
|
||||||
|
}
|
||||||
|
base::TimeDelta queue_duration() const {
|
||||||
|
return death_data_.queue_duration();
|
||||||
|
}
|
||||||
|
int AverageMsQueueDuration() const {
|
||||||
|
return death_data_.AverageMsQueueDuration();
|
||||||
|
}
|
||||||
|
|
||||||
void Write(std::string* output) const;
|
void Write(std::string* output) const;
|
||||||
|
|
||||||
@@ -282,10 +300,10 @@ class BASE_EXPORT Snapshot {
|
|||||||
const ThreadData* death_thread_;
|
const ThreadData* death_thread_;
|
||||||
DeathData death_data_;
|
DeathData death_data_;
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// DataCollector is a container class for Snapshot and BirthOnThread count
|
// DataCollector is a container class for Snapshot and BirthOnThread count
|
||||||
// items. It protects the gathering under locks, so that it could be called via
|
// items.
|
||||||
// Posttask on any threads, or passed to all the target threads in parallel.
|
|
||||||
|
|
||||||
class BASE_EXPORT DataCollector {
|
class BASE_EXPORT DataCollector {
|
||||||
public:
|
public:
|
||||||
@@ -312,12 +330,6 @@ class BASE_EXPORT DataCollector {
|
|||||||
private:
|
private:
|
||||||
typedef std::map<const BirthOnThread*, int> BirthCount;
|
typedef std::map<const BirthOnThread*, int> BirthCount;
|
||||||
|
|
||||||
// This instance may be provided to several threads to contribute data. The
|
|
||||||
// following counter tracks how many more threads will contribute. When it is
|
|
||||||
// zero, then all asynchronous contributions are complete, and locked access
|
|
||||||
// is no longer needed.
|
|
||||||
int count_of_contributing_threads_;
|
|
||||||
|
|
||||||
// The array that we collect data into.
|
// The array that we collect data into.
|
||||||
Collection collection_;
|
Collection collection_;
|
||||||
|
|
||||||
@@ -325,8 +337,6 @@ class BASE_EXPORT DataCollector {
|
|||||||
// seen a death count.
|
// seen a death count.
|
||||||
BirthCount global_birth_count_;
|
BirthCount global_birth_count_;
|
||||||
|
|
||||||
base::Lock accumulation_lock_; // Protects access during accumulation phase.
|
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(DataCollector);
|
DISALLOW_COPY_AND_ASSIGN(DataCollector);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -381,8 +391,10 @@ class BASE_EXPORT Comparator {
|
|||||||
BIRTH_FUNCTION = 8,
|
BIRTH_FUNCTION = 8,
|
||||||
BIRTH_LINE = 16,
|
BIRTH_LINE = 16,
|
||||||
COUNT = 32,
|
COUNT = 32,
|
||||||
AVERAGE_DURATION = 64,
|
AVERAGE_RUN_DURATION = 64,
|
||||||
TOTAL_DURATION = 128,
|
TOTAL_RUN_DURATION = 128,
|
||||||
|
AVERAGE_QUEUE_DURATION = 256,
|
||||||
|
TOTAL_QUEUE_DURATION = 512,
|
||||||
|
|
||||||
// Imediate action keywords.
|
// Imediate action keywords.
|
||||||
RESET_ALL_DATA = -1,
|
RESET_ALL_DATA = -1,
|
||||||
@@ -418,7 +430,7 @@ class BASE_EXPORT Comparator {
|
|||||||
// printed line.
|
// printed line.
|
||||||
bool IsGroupedBy(Selector selector) const;
|
bool IsGroupedBy(Selector selector) const;
|
||||||
|
|
||||||
// Using the tiebreakers as set above, we mostly get an ordering, which
|
// Using the tiebreakers as set above, we mostly get an ordering, with some
|
||||||
// equivalent groups. If those groups are displayed (rather than just being
|
// equivalent groups. If those groups are displayed (rather than just being
|
||||||
// aggregated, then the following is used to order them (within the group).
|
// aggregated, then the following is used to order them (within the group).
|
||||||
void SetSubgroupTiebreaker(Selector selector);
|
void SetSubgroupTiebreaker(Selector selector);
|
||||||
@@ -462,7 +474,6 @@ class BASE_EXPORT Comparator {
|
|||||||
bool use_tiebreaker_for_sort_only_;
|
bool use_tiebreaker_for_sort_only_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// For each thread, we have a ThreadData that stores all tracking info generated
|
// For each thread, we have a ThreadData that stores all tracking info generated
|
||||||
// on this thread. This prevents the need for locking as data accumulates.
|
// on this thread. This prevents the need for locking as data accumulates.
|
||||||
@@ -472,15 +483,18 @@ class BASE_EXPORT ThreadData {
|
|||||||
typedef std::map<Location, Births*> BirthMap;
|
typedef std::map<Location, Births*> BirthMap;
|
||||||
typedef std::map<const Births*, DeathData> DeathMap;
|
typedef std::map<const Births*, DeathData> DeathMap;
|
||||||
|
|
||||||
ThreadData();
|
// Initialize the current thread context with a new instance of ThreadData.
|
||||||
~ThreadData();
|
// This is used by all threads that have names, and can be explicitly
|
||||||
|
// set *before* any births are threads have taken place. It is generally
|
||||||
|
// only used by the message loop, which has a well defined name.
|
||||||
|
static void InitializeThreadContext(const std::string& suggested_name);
|
||||||
|
|
||||||
// Using Thread Local Store, find the current instance for collecting data.
|
// Using Thread Local Store, find the current instance for collecting data.
|
||||||
// If an instance does not exist, construct one (and remember it for use on
|
// If an instance does not exist, construct one (and remember it for use on
|
||||||
// this thread.
|
// this thread.
|
||||||
// If shutdown has already started, and we don't yet have an instance, then
|
// If shutdown has already started, and we don't yet have an instance, then
|
||||||
// return null.
|
// return null.
|
||||||
static ThreadData* current();
|
static ThreadData* Get();
|
||||||
|
|
||||||
// For a given (unescaped) about:tracking query, develop resulting HTML, and
|
// For a given (unescaped) about:tracking query, develop resulting HTML, and
|
||||||
// append to output.
|
// append to output.
|
||||||
@@ -496,22 +510,33 @@ class BASE_EXPORT ThreadData {
|
|||||||
Births* TallyABirth(const Location& location);
|
Births* TallyABirth(const Location& location);
|
||||||
|
|
||||||
// Find a place to record a death on this thread.
|
// Find a place to record a death on this thread.
|
||||||
void TallyADeath(const Births& lifetimes, const base::TimeDelta& duration);
|
void TallyADeath(const Births& the_birth,
|
||||||
|
const base::TimeDelta& queue_duration,
|
||||||
|
const base::TimeDelta& duration);
|
||||||
|
|
||||||
// Helper methods to only tally if the current thread has tracking active.
|
// Helper methods to only tally if the current thread has tracking active.
|
||||||
//
|
//
|
||||||
// TallyABirthIfActive will returns NULL if the birth cannot be tallied.
|
// TallyABirthIfActive will returns NULL if the birth cannot be tallied.
|
||||||
static Births* TallyABirthIfActive(const Location& location);
|
static Births* TallyABirthIfActive(const Location& location);
|
||||||
static void TallyADeathIfActive(const Births* lifetimes,
|
|
||||||
const base::TimeDelta& duration);
|
// Record the end of a timed run of an object. The |the_birth| is the record
|
||||||
|
// for the instance, the |time_posted| and |start_of_run| are times of posting
|
||||||
|
// into a message loop queue, and of starting to perform the run of the task.
|
||||||
|
// Implied is that the run just (Now()) ended. The current_message_loop is
|
||||||
|
// optional, and only used in DEBUG mode (when supplied) to verify that the
|
||||||
|
// ThreadData has a thread name that does indeed match the given loop's
|
||||||
|
// associated thread name (in RELEASE mode, its use is compiled away).
|
||||||
|
static void TallyADeathIfActive(const Births* the_birth,
|
||||||
|
const base::TimeTicks& time_posted,
|
||||||
|
const base::TimeTicks& delayed_start_time,
|
||||||
|
const base::TimeTicks& start_of_run);
|
||||||
|
|
||||||
// (Thread safe) Get start of list of instances.
|
// (Thread safe) Get start of list of instances.
|
||||||
static ThreadData* first();
|
static ThreadData* first();
|
||||||
// Iterate through the null terminated list of instances.
|
// Iterate through the null terminated list of instances.
|
||||||
ThreadData* next() const { return next_; }
|
ThreadData* next() const { return next_; }
|
||||||
|
|
||||||
MessageLoop* message_loop() const { return message_loop_; }
|
const std::string thread_name() const { return thread_name_; }
|
||||||
const std::string ThreadName() const;
|
|
||||||
|
|
||||||
// Using our lock, make a copy of the specified maps. These calls may arrive
|
// Using our lock, make a copy of the specified maps. These calls may arrive
|
||||||
// from non-local threads, and are used to quickly scan data from all threads
|
// from non-local threads, and are used to quickly scan data from all threads
|
||||||
@@ -528,31 +553,17 @@ class BASE_EXPORT ThreadData {
|
|||||||
// Using our lock to protect the iteration, Clear all birth and death data.
|
// Using our lock to protect the iteration, Clear all birth and death data.
|
||||||
void Reset();
|
void Reset();
|
||||||
|
|
||||||
// Using the "known list of threads" gathered during births and deaths, the
|
|
||||||
// following attempts to run the given function once all all such threads.
|
|
||||||
// Note that the function can only be run on threads which have a message
|
|
||||||
// loop!
|
|
||||||
static void RunOnAllThreads(void (*Func)());
|
|
||||||
|
|
||||||
// Set internal status_ to either become ACTIVE, or later, to be SHUTDOWN,
|
// Set internal status_ to either become ACTIVE, or later, to be SHUTDOWN,
|
||||||
// based on argument being true or false respectively.
|
// based on argument being true or false respectively.
|
||||||
// IF tracking is not compiled in, this function will return false.
|
// IF tracking is not compiled in, this function will return false.
|
||||||
static bool StartTracking(bool status);
|
static bool StartTracking(bool status);
|
||||||
static bool IsActive();
|
static bool IsActive();
|
||||||
|
|
||||||
#ifdef OS_WIN
|
// Provide a time function that does nothing (runs fast) when we don't have
|
||||||
// WARNING: ONLY call this function when all MessageLoops are still intact for
|
// the profiler enabled. It will generally be optimized away when it is
|
||||||
// all registered threads. IF you call it later, you will crash.
|
// ifdef'ed to be small enough (allowing the profiler to be "compiled out" of
|
||||||
// Note: You don't need to call it at all, and you can wait till you are
|
// the code).
|
||||||
// single threaded (again) to do the cleanup via
|
static base::TimeTicks Now();
|
||||||
// ShutdownSingleThreadedCleanup().
|
|
||||||
// Start the teardown (shutdown) process in a multi-thread mode by disabling
|
|
||||||
// further additions to thread database on all threads. First it makes a
|
|
||||||
// local (locked) change to prevent any more threads from registering. Then
|
|
||||||
// it Posts a Task to all registered threads to be sure they are aware that no
|
|
||||||
// more accumulation can take place.
|
|
||||||
static void ShutdownMultiThreadTracking();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// WARNING: ONLY call this function when you are running single threaded
|
// WARNING: ONLY call this function when you are running single threaded
|
||||||
// (again) and all message loops and threads have terminated. Until that
|
// (again) and all message loops and threads have terminated. Until that
|
||||||
@@ -562,6 +573,18 @@ class BASE_EXPORT ThreadData {
|
|||||||
static void ShutdownSingleThreadedCleanup();
|
static void ShutdownSingleThreadedCleanup();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Worker thread construction creates a name.
|
||||||
|
ThreadData();
|
||||||
|
// Message loop based construction should provide a name.
|
||||||
|
explicit ThreadData(const std::string& suggested_name);
|
||||||
|
|
||||||
|
~ThreadData();
|
||||||
|
|
||||||
|
// Enter a new instance into Thread Local Store.
|
||||||
|
// Return the instance, or null if we can't register it (because we're
|
||||||
|
// shutting down).
|
||||||
|
static ThreadData* RegisterCurrentContext(ThreadData* unregistered);
|
||||||
|
|
||||||
// Current allowable states of the tracking system. The states always
|
// Current allowable states of the tracking system. The states always
|
||||||
// proceed towards SHUTDOWN, and never go backwards.
|
// proceed towards SHUTDOWN, and never go backwards.
|
||||||
enum Status {
|
enum Status {
|
||||||
@@ -570,17 +593,6 @@ class BASE_EXPORT ThreadData {
|
|||||||
SHUTDOWN,
|
SHUTDOWN,
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(OS_WIN)
|
|
||||||
class ThreadSafeDownCounter;
|
|
||||||
class RunTheStatic;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Each registered thread is called to set status_ to SHUTDOWN.
|
|
||||||
// This is done redundantly on every registered thread because it is not
|
|
||||||
// protected by a mutex. Running on all threads guarantees we get the
|
|
||||||
// notification into the memory cache of all possible threads.
|
|
||||||
static void ShutdownDisablingFurtherTracking();
|
|
||||||
|
|
||||||
// We use thread local store to identify which ThreadData to interact with.
|
// We use thread local store to identify which ThreadData to interact with.
|
||||||
static base::ThreadLocalStorage::Slot tls_index_;
|
static base::ThreadLocalStorage::Slot tls_index_;
|
||||||
|
|
||||||
@@ -589,10 +601,7 @@ class BASE_EXPORT ThreadData {
|
|||||||
// Protection for access to first_.
|
// Protection for access to first_.
|
||||||
static base::Lock list_lock_;
|
static base::Lock list_lock_;
|
||||||
|
|
||||||
// We set status_ to SHUTDOWN when we shut down the tracking service. This
|
// We set status_ to SHUTDOWN when we shut down the tracking service.
|
||||||
// setting is redundantly established by all participating threads so that we
|
|
||||||
// are *guaranteed* (without locking) that all threads can "see" the status
|
|
||||||
// and avoid additional calls into the service.
|
|
||||||
static Status status_;
|
static Status status_;
|
||||||
|
|
||||||
// Link to next instance (null terminated list). Used to globally track all
|
// Link to next instance (null terminated list). Used to globally track all
|
||||||
@@ -600,10 +609,9 @@ class BASE_EXPORT ThreadData {
|
|||||||
// data).
|
// data).
|
||||||
ThreadData* next_;
|
ThreadData* next_;
|
||||||
|
|
||||||
// The message loop where tasks needing to access this instance's private data
|
// The name of the thread that is being recorded. If this thread has no
|
||||||
// should be directed. Since some threads have no message loop, some
|
// message_loop, then this is a worker thread, with a sequence number postfix.
|
||||||
// instances have data that can't be (safely) modified externally.
|
std::string thread_name_;
|
||||||
MessageLoop* message_loop_;
|
|
||||||
|
|
||||||
// A map used on each thread to keep track of Births on this thread.
|
// A map used on each thread to keep track of Births on this thread.
|
||||||
// This map should only be accessed on the thread it was constructed on.
|
// This map should only be accessed on the thread it was constructed on.
|
||||||
@@ -625,10 +633,13 @@ class BASE_EXPORT ThreadData {
|
|||||||
// writing is only done from this thread.
|
// writing is only done from this thread.
|
||||||
mutable base::Lock lock_;
|
mutable base::Lock lock_;
|
||||||
|
|
||||||
|
// The next available thread number. This should only be accessed when the
|
||||||
|
// list_lock_ is held.
|
||||||
|
static int thread_number_counter;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(ThreadData);
|
DISALLOW_COPY_AND_ASSIGN(ThreadData);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Provide simple way to to start global tracking, and to tear down tracking
|
// Provide simple way to to start global tracking, and to tear down tracking
|
||||||
// when done. Note that construction and destruction of this object must be
|
// when done. Note that construction and destruction of this object must be
|
||||||
|
@@ -23,11 +23,11 @@ TEST_F(TrackedObjectsTest, MinimalStartupShutdown) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
EXPECT_FALSE(ThreadData::first()); // No activity even on this thread.
|
EXPECT_FALSE(ThreadData::first()); // No activity even on this thread.
|
||||||
ThreadData* data = ThreadData::current();
|
ThreadData* data = ThreadData::Get();
|
||||||
EXPECT_TRUE(ThreadData::first()); // Now class was constructed.
|
EXPECT_TRUE(ThreadData::first()); // Now class was constructed.
|
||||||
EXPECT_TRUE(data);
|
EXPECT_TRUE(data);
|
||||||
EXPECT_TRUE(!data->next());
|
EXPECT_TRUE(!data->next());
|
||||||
EXPECT_EQ(data, ThreadData::current());
|
EXPECT_EQ(data, ThreadData::Get());
|
||||||
ThreadData::BirthMap birth_map;
|
ThreadData::BirthMap birth_map;
|
||||||
data->SnapshotBirthMap(&birth_map);
|
data->SnapshotBirthMap(&birth_map);
|
||||||
EXPECT_EQ(0u, birth_map.size());
|
EXPECT_EQ(0u, birth_map.size());
|
||||||
@@ -39,11 +39,11 @@ TEST_F(TrackedObjectsTest, MinimalStartupShutdown) {
|
|||||||
// Do it again, just to be sure we reset state completely.
|
// Do it again, just to be sure we reset state completely.
|
||||||
ThreadData::StartTracking(true);
|
ThreadData::StartTracking(true);
|
||||||
EXPECT_FALSE(ThreadData::first()); // No activity even on this thread.
|
EXPECT_FALSE(ThreadData::first()); // No activity even on this thread.
|
||||||
data = ThreadData::current();
|
data = ThreadData::Get();
|
||||||
EXPECT_TRUE(ThreadData::first()); // Now class was constructed.
|
EXPECT_TRUE(ThreadData::first()); // Now class was constructed.
|
||||||
EXPECT_TRUE(data);
|
EXPECT_TRUE(data);
|
||||||
EXPECT_TRUE(!data->next());
|
EXPECT_TRUE(!data->next());
|
||||||
EXPECT_EQ(data, ThreadData::current());
|
EXPECT_EQ(data, ThreadData::Get());
|
||||||
birth_map.clear();
|
birth_map.clear();
|
||||||
data->SnapshotBirthMap(&birth_map);
|
data->SnapshotBirthMap(&birth_map);
|
||||||
EXPECT_EQ(0u, birth_map.size());
|
EXPECT_EQ(0u, birth_map.size());
|
||||||
@@ -64,7 +64,7 @@ TEST_F(TrackedObjectsTest, TinyStartupShutdown) {
|
|||||||
const ThreadData* data = ThreadData::first();
|
const ThreadData* data = ThreadData::first();
|
||||||
ASSERT_TRUE(data);
|
ASSERT_TRUE(data);
|
||||||
EXPECT_TRUE(!data->next());
|
EXPECT_TRUE(!data->next());
|
||||||
EXPECT_EQ(data, ThreadData::current());
|
EXPECT_EQ(data, ThreadData::Get());
|
||||||
ThreadData::BirthMap birth_map;
|
ThreadData::BirthMap birth_map;
|
||||||
data->SnapshotBirthMap(&birth_map);
|
data->SnapshotBirthMap(&birth_map);
|
||||||
EXPECT_EQ(1u, birth_map.size()); // 1 birth location.
|
EXPECT_EQ(1u, birth_map.size()); // 1 birth location.
|
||||||
@@ -78,7 +78,9 @@ TEST_F(TrackedObjectsTest, TinyStartupShutdown) {
|
|||||||
const Births* second_birth = ThreadData::TallyABirthIfActive(location);
|
const Births* second_birth = ThreadData::TallyABirthIfActive(location);
|
||||||
ThreadData::TallyADeathIfActive(
|
ThreadData::TallyADeathIfActive(
|
||||||
second_birth,
|
second_birth,
|
||||||
base::TimeDelta::FromSeconds(1) /* Bogus duration. */);
|
base::TimeTicks::TimeTicks(), /* Bogus post_time. */
|
||||||
|
base::TimeTicks::TimeTicks(), /* Bogus delayed_start_time. */
|
||||||
|
base::TimeTicks::TimeTicks() /* Bogus start_run_time. */);
|
||||||
|
|
||||||
birth_map.clear();
|
birth_map.clear();
|
||||||
data->SnapshotBirthMap(&birth_map);
|
data->SnapshotBirthMap(&birth_map);
|
||||||
|
@@ -12,6 +12,7 @@
|
|||||||
#include "base/metrics/histogram.h"
|
#include "base/metrics/histogram.h"
|
||||||
#include "base/system_monitor/system_monitor.h"
|
#include "base/system_monitor/system_monitor.h"
|
||||||
#include "base/threading/thread_restrictions.h"
|
#include "base/threading/thread_restrictions.h"
|
||||||
|
#include "base/tracked_objects.h"
|
||||||
#include "content/browser/browser_thread.h"
|
#include "content/browser/browser_thread.h"
|
||||||
#include "content/browser/content_browser_client.h"
|
#include "content/browser/content_browser_client.h"
|
||||||
#include "content/common/hi_res_timer_manager.h"
|
#include "content/common/hi_res_timer_manager.h"
|
||||||
@@ -241,12 +242,11 @@ void BrowserMainParts::MainMessageLoopStart() {
|
|||||||
|
|
||||||
main_message_loop_.reset(new MessageLoop(MessageLoop::TYPE_UI));
|
main_message_loop_.reset(new MessageLoop(MessageLoop::TYPE_UI));
|
||||||
|
|
||||||
// TODO(viettrungluu): should these really go before setting the thread name?
|
InitializeMainThread();
|
||||||
|
|
||||||
system_monitor_.reset(new base::SystemMonitor);
|
system_monitor_.reset(new base::SystemMonitor);
|
||||||
hi_res_timer_manager_.reset(new HighResolutionTimerManager);
|
hi_res_timer_manager_.reset(new HighResolutionTimerManager);
|
||||||
|
|
||||||
InitializeMainThread();
|
|
||||||
|
|
||||||
network_change_notifier_.reset(net::NetworkChangeNotifier::Create());
|
network_change_notifier_.reset(net::NetworkChangeNotifier::Create());
|
||||||
|
|
||||||
PostMainMessageLoopStart();
|
PostMainMessageLoopStart();
|
||||||
@@ -274,6 +274,10 @@ void BrowserMainParts::InitializeMainThread() {
|
|||||||
base::PlatformThread::SetName(kThreadName);
|
base::PlatformThread::SetName(kThreadName);
|
||||||
main_message_loop().set_thread_name(kThreadName);
|
main_message_loop().set_thread_name(kThreadName);
|
||||||
|
|
||||||
|
#if defined(TRACK_ALL_TASK_OBJECTS)
|
||||||
|
tracked_objects::ThreadData::InitializeThreadContext(kThreadName);
|
||||||
|
#endif // TRACK_ALL_TASK_OBJECTS
|
||||||
|
|
||||||
// Register the main thread by instantiating it, but don't call any methods.
|
// Register the main thread by instantiating it, but don't call any methods.
|
||||||
main_thread_.reset(new BrowserThread(BrowserThread::UI,
|
main_thread_.reset(new BrowserThread(BrowserThread::UI,
|
||||||
MessageLoop::current()));
|
MessageLoop::current()));
|
||||||
|
Reference in New Issue
Block a user