Commit 62fe1880 authored by Alex Clarke's avatar Alex Clarke

Add SequenceManager debug option to show task reordering

This is helpful whern diagnosing test failures resulting from scheduler
policy changes.

Bug: 971191
Change-Id: I835db83e62895e45ef7400d231db8b6e30f14ab3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1806464Reviewed-by: default avatarAlexander Timin <altimin@chromium.org>
Reviewed-by: default avatarSami Kyöstilä <skyostil@chromium.org>
Cr-Commit-Position: refs/heads/master@{#697201}
parent dcc366c2
...@@ -102,6 +102,11 @@ class BASE_EXPORT SequenceManager { ...@@ -102,6 +102,11 @@ class BASE_EXPORT SequenceManager {
kNone, kNone,
kEnabled, kEnabled,
kEnabledWithBacktrace, kEnabledWithBacktrace,
// Logs high priority tasks and the lower priority tasks they skipped
// past. Useful for debugging test failures caused by scheduler policy
// changes.
kReorderedOnly,
}; };
TaskLogging task_execution_logging = TaskLogging::kNone; TaskLogging task_execution_logging = TaskLogging::kNone;
......
...@@ -503,52 +503,60 @@ Optional<Task> SequenceManagerImpl::TakeTask() { ...@@ -503,52 +503,60 @@ Optional<Task> SequenceManagerImpl::TakeTask() {
"task_type", executing_task.task_type); "task_type", executing_task.task_type);
TRACE_EVENT_BEGIN0("sequence_manager", executing_task.task_queue_name); TRACE_EVENT_BEGIN0("sequence_manager", executing_task.task_queue_name);
#if DCHECK_IS_ON() && !defined(OS_NACL)
LogTaskDebugInfo(executing_task);
#endif
return task; return task;
} }
#if DCHECK_IS_ON() && !defined(OS_NACL) #if DCHECK_IS_ON() && !defined(OS_NACL)
void SequenceManagerImpl::LogTaskDebugInfo( void SequenceManagerImpl::LogTaskDebugInfo(
const ExecutingTask& executing_task) { const WorkQueue* selected_work_queue) const {
const Task* task = selected_work_queue->GetFrontTask();
switch (settings_.task_execution_logging) { switch (settings_.task_execution_logging) {
case Settings::TaskLogging::kNone: case Settings::TaskLogging::kNone:
break; break;
case Settings::TaskLogging::kEnabled: case Settings::TaskLogging::kEnabled:
LOG(INFO) << "#" LOG(INFO) << "#" << static_cast<uint64_t>(task->enqueue_order()) << " "
<< static_cast<uint64_t>( << selected_work_queue->task_queue()->GetName()
executing_task.pending_task.enqueue_order()) << (task->cross_thread_ ? " Run crossthread " : " Run ")
<< " " << executing_task.task_queue_name << task->posted_from.ToString();
<< (executing_task.pending_task.cross_thread_
? " Run crossthread "
: " Run ")
<< executing_task.pending_task.posted_from.ToString();
break; break;
case Settings::TaskLogging::kEnabledWithBacktrace: { case Settings::TaskLogging::kEnabledWithBacktrace: {
std::array<const void*, PendingTask::kTaskBacktraceLength + 1> task_trace; std::array<const void*, PendingTask::kTaskBacktraceLength + 1> task_trace;
task_trace[0] = executing_task.pending_task.posted_from.program_counter(); task_trace[0] = task->posted_from.program_counter();
std::copy(executing_task.pending_task.task_backtrace.begin(), std::copy(task->task_backtrace.begin(), task->task_backtrace.end(),
executing_task.pending_task.task_backtrace.end(),
task_trace.begin() + 1); task_trace.begin() + 1);
size_t length = 0; size_t length = 0;
while (length < task_trace.size() && task_trace[length]) while (length < task_trace.size() && task_trace[length])
++length; ++length;
if (length == 0) if (length == 0)
break; break;
LOG(INFO) << "#" LOG(INFO) << "#" << static_cast<uint64_t>(task->enqueue_order()) << " "
<< static_cast<uint64_t>( << selected_work_queue->task_queue()->GetName()
executing_task.pending_task.enqueue_order()) << (task->cross_thread_ ? " Run crossthread " : " Run ")
<< " " << executing_task.task_queue_name
<< (executing_task.pending_task.cross_thread_
? " Run crossthread "
: " Run ")
<< debug::StackTrace(task_trace.data(), length); << debug::StackTrace(task_trace.data(), length);
break; break;
} }
case Settings::TaskLogging::kReorderedOnly: {
std::vector<const Task*> skipped_tasks;
main_thread_only().selector.CollectSkippedOverLowerPriorityTasks(
selected_work_queue, &skipped_tasks);
if (skipped_tasks.empty())
break;
LOG(INFO) << "#" << static_cast<uint64_t>(task->enqueue_order()) << " "
<< selected_work_queue->task_queue()->GetName()
<< (task->cross_thread_ ? " Run crossthread " : " Run ")
<< task->posted_from.ToString();
for (const Task* skipped_task : skipped_tasks) {
LOG(INFO) << "# (skipped over) "
<< static_cast<uint64_t>(skipped_task->enqueue_order()) << " "
<< skipped_task->posted_from.ToString();
}
}
} }
} }
#endif // DCHECK_IS_ON() && !defined(OS_NACL) #endif // DCHECK_IS_ON() && !defined(OS_NACL)
...@@ -607,6 +615,10 @@ Optional<Task> SequenceManagerImpl::TakeTaskImpl() { ...@@ -607,6 +615,10 @@ Optional<Task> SequenceManagerImpl::TakeTaskImpl() {
return nullopt; return nullopt;
} }
#if DCHECK_IS_ON() && !defined(OS_NACL)
LogTaskDebugInfo(work_queue);
#endif // DCHECK_IS_ON() && !defined(OS_NACL)
main_thread_only().task_execution_stack.emplace_back( main_thread_only().task_execution_stack.emplace_back(
work_queue->TakeTaskFromWorkQueue(), work_queue->task_queue(), work_queue->TakeTaskFromWorkQueue(), work_queue->task_queue(),
InitializeTaskTiming(work_queue->task_queue())); InitializeTaskTiming(work_queue->task_queue()));
......
...@@ -388,7 +388,7 @@ class BASE_EXPORT SequenceManagerImpl ...@@ -388,7 +388,7 @@ class BASE_EXPORT SequenceManagerImpl
TimeDelta GetDelayTillNextDelayedTask(LazyNow* lazy_now) const; TimeDelta GetDelayTillNextDelayedTask(LazyNow* lazy_now) const;
#if DCHECK_IS_ON() #if DCHECK_IS_ON()
void LogTaskDebugInfo(const ExecutingTask& executing_task); void LogTaskDebugInfo(const internal::WorkQueue* work_queue) const;
#endif #endif
// Determines if wall time or thread time should be recorded for the next // Determines if wall time or thread time should be recorded for the next
......
...@@ -159,6 +159,15 @@ void TaskQueueSelector::WorkQueueSetBecameNonEmpty(size_t set_index) { ...@@ -159,6 +159,15 @@ void TaskQueueSelector::WorkQueueSetBecameNonEmpty(size_t set_index) {
} }
} }
void TaskQueueSelector::CollectSkippedOverLowerPriorityTasks(
const internal::WorkQueue* selected_work_queue,
std::vector<const Task*>* result) const {
delayed_work_queue_sets_.CollectSkippedOverLowerPriorityTasks(
selected_work_queue, result);
immediate_work_queue_sets_.CollectSkippedOverLowerPriorityTasks(
selected_work_queue, result);
}
#if DCHECK_IS_ON() || !defined(NDEBUG) #if DCHECK_IS_ON() || !defined(NDEBUG)
bool TaskQueueSelector::CheckContainsQueueForTest( bool TaskQueueSelector::CheckContainsQueueForTest(
const internal::TaskQueueImpl* queue) const { const internal::TaskQueueImpl* queue) const {
......
...@@ -76,6 +76,12 @@ class BASE_EXPORT TaskQueueSelector : public WorkQueueSets::Observer { ...@@ -76,6 +76,12 @@ class BASE_EXPORT TaskQueueSelector : public WorkQueueSets::Observer {
void WorkQueueSetBecameEmpty(size_t set_index) override; void WorkQueueSetBecameEmpty(size_t set_index) override;
void WorkQueueSetBecameNonEmpty(size_t set_index) override; void WorkQueueSetBecameNonEmpty(size_t set_index) override;
// Populates |result| with tasks with lower priority than the first task from
// |selected_work_queue| which could otherwise run now.
void CollectSkippedOverLowerPriorityTasks(
const internal::WorkQueue* selected_work_queue,
std::vector<const Task*>* result) const;
protected: protected:
WorkQueueSets* delayed_work_queue_sets() { return &delayed_work_queue_sets_; } WorkQueueSets* delayed_work_queue_sets() { return &delayed_work_queue_sets_; }
......
...@@ -762,6 +762,21 @@ TEST_F(TaskQueueSelectorTest, TestObserverWithTwoBlockedQueues) { ...@@ -762,6 +762,21 @@ TEST_F(TaskQueueSelectorTest, TestObserverWithTwoBlockedQueues) {
task_queue2->UnregisterTaskQueue(); task_queue2->UnregisterTaskQueue();
} }
TEST_F(TaskQueueSelectorTest, CollectSkippedOverLowerPriorityTasks) {
size_t queue_order[] = {0, 1, 2, 3, 2, 1, 0};
PushTasks(queue_order, 7);
selector_.SetQueuePriority(task_queues_[3].get(), TaskQueue::kHighPriority);
std::vector<const Task*> result;
selector_.CollectSkippedOverLowerPriorityTasks(
task_queues_[3]->immediate_work_queue(), &result);
ASSERT_EQ(3u, result.size());
EXPECT_EQ(2u, result[0]->enqueue_order()); // The order here isn't important.
EXPECT_EQ(3u, result[1]->enqueue_order());
EXPECT_EQ(4u, result[2]->enqueue_order());
}
class DisabledAntiStarvationLogicTaskQueueSelectorTest class DisabledAntiStarvationLogicTaskQueueSelectorTest
: public TaskQueueSelectorTestBase, : public TaskQueueSelectorTestBase,
public testing::WithParamInterface<TaskQueue::QueuePriority> { public testing::WithParamInterface<TaskQueue::QueuePriority> {
......
...@@ -305,6 +305,16 @@ void WorkQueue::PopTaskForTesting() { ...@@ -305,6 +305,16 @@ void WorkQueue::PopTaskForTesting() {
tasks_.pop_front(); tasks_.pop_front();
} }
void WorkQueue::CollectTasksOlderThan(EnqueueOrder reference,
std::vector<const Task*>* result) const {
for (const Task& task : tasks_) {
if (task.enqueue_order() >= reference)
break;
result->push_back(&task);
}
}
} // namespace internal } // namespace internal
} // namespace sequence_manager } // namespace sequence_manager
} // namespace base } // namespace base
...@@ -161,6 +161,11 @@ class BASE_EXPORT WorkQueue { ...@@ -161,6 +161,11 @@ class BASE_EXPORT WorkQueue {
// Test support function. This should not be used in production code. // Test support function. This should not be used in production code.
void PopTaskForTesting(); void PopTaskForTesting();
// Iterates through |tasks_| adding any that are older than |reference| to
// |result|.
void CollectTasksOlderThan(EnqueueOrder reference,
std::vector<const Task*>* result) const;
private: private:
bool InsertFenceImpl(EnqueueOrder fence); bool InsertFenceImpl(EnqueueOrder fence);
......
...@@ -237,6 +237,19 @@ bool WorkQueueSets::ContainsWorkQueueForTest( ...@@ -237,6 +237,19 @@ bool WorkQueueSets::ContainsWorkQueueForTest(
} }
#endif #endif
void WorkQueueSets::CollectSkippedOverLowerPriorityTasks(
const internal::WorkQueue* selected_work_queue,
std::vector<const Task*>* result) const {
EnqueueOrder selected_enqueue_order;
CHECK(selected_work_queue->GetFrontTaskEnqueueOrder(&selected_enqueue_order));
for (size_t priority = selected_work_queue->work_queue_set_index() + 1;
priority < TaskQueue::kQueuePriorityCount; priority++) {
for (const OldestTaskEnqueueOrder& pair : work_queue_heaps_[priority]) {
pair.value->CollectTasksOlderThan(selected_enqueue_order, result);
}
}
}
} // namespace internal } // namespace internal
} // namespace sequence_manager } // namespace sequence_manager
} // namespace base } // namespace base
...@@ -95,6 +95,12 @@ class BASE_EXPORT WorkQueueSets { ...@@ -95,6 +95,12 @@ class BASE_EXPORT WorkQueueSets {
const char* GetName() const { return name_; } const char* GetName() const { return name_; }
// Collects ready tasks which where skipped over when |selected_work_queue|
// was selected. Note this is somewhat expensive.
void CollectSkippedOverLowerPriorityTasks(
const internal::WorkQueue* selected_work_queue,
std::vector<const Task*>* result) const;
private: private:
struct OldestTaskEnqueueOrder { struct OldestTaskEnqueueOrder {
EnqueueOrder key; EnqueueOrder key;
......
...@@ -331,6 +331,32 @@ TEST_F(WorkQueueSetsTest, PushNonNestableTaskToFront) { ...@@ -331,6 +331,32 @@ TEST_F(WorkQueueSetsTest, PushNonNestableTaskToFront) {
EXPECT_EQ(queue1, work_queue_sets_->GetOldestQueueInSet(set)); EXPECT_EQ(queue1, work_queue_sets_->GetOldestQueueInSet(set));
} }
TEST_F(WorkQueueSetsTest, CollectSkippedOverLowerPriorityTasks) {
WorkQueue* queue1 = NewTaskQueue("queue1");
WorkQueue* queue2 = NewTaskQueue("queue2");
WorkQueue* queue3 = NewTaskQueue("queue3");
work_queue_sets_->ChangeSetIndex(queue1, 3);
work_queue_sets_->ChangeSetIndex(queue2, 2);
work_queue_sets_->ChangeSetIndex(queue3, 1);
queue1->Push(FakeTaskWithEnqueueOrder(1));
queue1->Push(FakeTaskWithEnqueueOrder(2));
queue2->Push(FakeTaskWithEnqueueOrder(3));
queue3->Push(FakeTaskWithEnqueueOrder(4));
queue3->Push(FakeTaskWithEnqueueOrder(5));
queue2->Push(FakeTaskWithEnqueueOrder(6));
queue1->Push(FakeTaskWithEnqueueOrder(7));
std::vector<const Task*> result;
work_queue_sets_->CollectSkippedOverLowerPriorityTasks(queue3, &result);
ASSERT_EQ(3u, result.size());
EXPECT_EQ(3u, result[0]->enqueue_order()); // The order here isn't important.
EXPECT_EQ(1u, result[1]->enqueue_order());
EXPECT_EQ(2u, result[2]->enqueue_order());
}
} // namespace internal } // namespace internal
} // namespace sequence_manager } // namespace sequence_manager
} // namespace base } // namespace base
...@@ -555,6 +555,20 @@ TEST_F(WorkQueueTest, RemoveAllCanceledTasksFromFrontQueueBlockedByFence) { ...@@ -555,6 +555,20 @@ TEST_F(WorkQueueTest, RemoveAllCanceledTasksFromFrontQueueBlockedByFence) {
EXPECT_FALSE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order)); EXPECT_FALSE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
} }
TEST_F(WorkQueueTest, CollectTasksOlderThan) {
work_queue_->Push(FakeTaskWithEnqueueOrder(2));
work_queue_->Push(FakeTaskWithEnqueueOrder(3));
work_queue_->Push(FakeTaskWithEnqueueOrder(4));
std::vector<const Task*> result;
work_queue_->CollectTasksOlderThan(EnqueueOrder::FromIntForTesting(4),
&result);
ASSERT_EQ(2u, result.size());
EXPECT_EQ(2u, result[0]->enqueue_order());
EXPECT_EQ(3u, result[1]->enqueue_order());
}
} // namespace internal } // namespace internal
} // namespace sequence_manager } // namespace sequence_manager
} // namespace base } // namespace base
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment