[sequencemanager] Do not set |delayed_run_time| for immediate tasks.

Prior to this CL, the SequenceManager set |PendingTask::delayed_run_time|
for immediate tasks when the "add queue time to tasks" option was
enabled. This caused the responsiveness watcher to consider an interval
as janky only when a long task was running, not when a task was queued
for a long time as intended.

With this CL, SequenceManager only sets |PendingTask::delayed_run_time|
for delayed tasks. The responsiveness watcher is modified to keep
recording the same jank as before, to ensure that we can easily notice
if there are real jank regressions. In an upcoming CL, we will modify
the responsiveness watcher to record a new jank histogram that takes
into account queuing time.

Bug: 1029137
Change-Id: I8394cb42a8b6d77b01eb38cad1242fc4b0f6c74d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1940452
Reviewed-by: Alexei Svitkine <[email protected]>
Reviewed-by: Erik Chen <[email protected]>
Reviewed-by: Alex Clarke <[email protected]>
Commit-Queue: François Doray <[email protected]>
Cr-Commit-Position: refs/heads/master@{#721511}
diff --git a/base/pending_task.h b/base/pending_task.h
index eca97320..e7648c28 100644
--- a/base/pending_task.h
+++ b/base/pending_task.h
@@ -43,7 +43,7 @@
   // The site this PendingTask was posted from.
   Location posted_from;
 
-  // The time when the task should be run.
+  // The time when the task should be run. This is null for an immediate task.
   base::TimeTicks delayed_run_time;
 
   // The time at which the task was queued. For SequenceManager tasks and
diff --git a/base/task/sequence_manager/sequence_manager_impl_unittest.cc b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
index e85e0c8..3a7e4b9 100644
--- a/base/task/sequence_manager/sequence_manager_impl_unittest.cc
+++ b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
@@ -4918,6 +4918,52 @@
   EXPECT_EQ(2, task_ready_count);
 }
 
+namespace {
+
+class TaskObserverExpectingNoDelayedRunTime : public TaskObserver {
+ public:
+  TaskObserverExpectingNoDelayedRunTime() = default;
+  ~TaskObserverExpectingNoDelayedRunTime() override = default;
+
+  int num_will_process_task() const { return num_will_process_task_; }
+  int num_did_process_task() const { return num_did_process_task_; }
+
+ private:
+  void WillProcessTask(const base::PendingTask& pending_task) override {
+    EXPECT_TRUE(pending_task.delayed_run_time.is_null());
+    ++num_will_process_task_;
+  }
+  void DidProcessTask(const base::PendingTask& pending_task) override {
+    EXPECT_TRUE(pending_task.delayed_run_time.is_null());
+    ++num_did_process_task_;
+  }
+
+  int num_will_process_task_ = 0;
+  int num_did_process_task_ = 0;
+};
+
+}  // namespace
+
+// The |delayed_run_time| must not be set for immediate tasks as that prevents
+// external observers from correctly identifying delayed tasks.
+// https://crbug.com/1029137
+TEST_P(SequenceManagerTest, NoDelayedRunTimeForImmediateTask) {
+  TaskObserverExpectingNoDelayedRunTime task_observer;
+  sequence_manager()->SetAddQueueTimeToTasks(true);
+  sequence_manager()->AddTaskObserver(&task_observer);
+  auto queue = CreateTaskQueue();
+
+  base::RunLoop run_loop;
+  queue->task_runner()->PostTask(
+      FROM_HERE, BindLambdaForTesting([&]() { run_loop.Quit(); }));
+  run_loop.Run();
+
+  EXPECT_EQ(1, task_observer.num_will_process_task());
+  EXPECT_EQ(1, task_observer.num_did_process_task());
+
+  sequence_manager()->RemoveTaskObserver(&task_observer);
+}
+
 }  // namespace sequence_manager_impl_unittest
 }  // namespace internal
 }  // namespace sequence_manager
diff --git a/base/task/sequence_manager/task_queue_impl.cc b/base/task/sequence_manager/task_queue_impl.cc
index 7c7a44bc..2035111 100644
--- a/base/task/sequence_manager/task_queue_impl.cc
+++ b/base/task/sequence_manager/task_queue_impl.cc
@@ -22,6 +22,18 @@
 namespace base {
 namespace sequence_manager {
 
+namespace {
+
+base::TimeTicks GetTaskDesiredExecutionTime(const Task& task) {
+  if (!task.delayed_run_time.is_null())
+    return task.delayed_run_time;
+  // The desired run time for a non-delayed task is the queue time.
+  DCHECK(!task.queue_time.is_null());
+  return task.queue_time;
+}
+
+}  // namespace
+
 // static
 const char* TaskQueue::PriorityToString(TaskQueue::QueuePriority priority) {
   switch (priority) {
@@ -270,7 +282,7 @@
     if (any_thread_.task_queue_observer)
       any_thread_.task_queue_observer->OnPostTask(task.location, TimeDelta());
     bool add_queue_time_to_tasks = sequence_manager_->GetAddQueueTimeToTasks();
-    if (add_queue_time_to_tasks)
+    if (add_queue_time_to_tasks || delayed_fence_allowed_)
       task.queue_time = lazy_now.Now();
 
     // The sequence number must be incremented atomically with pushing onto the
@@ -280,17 +292,8 @@
     EnqueueOrder sequence_number = sequence_manager_->GetNextSequenceNumber();
     bool was_immediate_incoming_queue_empty =
         any_thread_.immediate_incoming_queue.empty();
+    // Delayed run time is null for an immediate task.
     base::TimeTicks delayed_run_time;
-    // The desired run time is only required when delayed fence is allowed.
-    // Avoid evaluating it when not required.
-    //
-    // TODO(https://crbug.com/997203) The code that records jank metrics depends
-    // on the delayed run time to be set when |add_queue_time_to_tasks| is true.
-    // We should remove that dependency and only set the delayed run time here
-    // when |delayed_fence_allowed_| is true. See https://crbug.com/997203#c22
-    // for details about the dependency.
-    if (delayed_fence_allowed_ || add_queue_time_to_tasks)
-      delayed_run_time = lazy_now.Now();
     any_thread_.immediate_incoming_queue.push_back(Task(
         std::move(task), delayed_run_time, sequence_number, sequence_number));
 
@@ -484,7 +487,9 @@
   // a fence.
   if (main_thread_only().delayed_fence) {
     for (const Task& task : *queue) {
-      if (task.delayed_run_time >= main_thread_only().delayed_fence.value()) {
+      DCHECK(!task.queue_time.is_null());
+      DCHECK(task.delayed_run_time.is_null());
+      if (task.queue_time >= main_thread_only().delayed_fence.value()) {
         main_thread_only().delayed_fence = nullopt;
         DCHECK(!main_thread_only().current_fence);
         main_thread_only().current_fence = task.enqueue_order();
@@ -581,7 +586,7 @@
       VLOG(0) << name_ << " Delay expired for " << task->posted_from.ToString();
 #endif  // DCHECK_IS_ON()
 
-    ActivateDelayedFenceIfNeeded(task->delayed_run_time);
+    ActivateDelayedFenceIfNeeded(GetTaskDesiredExecutionTime(*task));
     DCHECK(!task->enqueue_order_set());
     task->set_enqueue_order(sequence_manager_->GetNextSequenceNumber());
 
diff --git a/base/task/sequence_manager/tasks.cc b/base/task/sequence_manager/tasks.cc
index a3bd5ce..5c72447 100644
--- a/base/task/sequence_manager/tasks.cc
+++ b/base/task/sequence_manager/tasks.cc
@@ -8,13 +8,13 @@
 namespace sequence_manager {
 
 Task::Task(internal::PostedTask posted_task,
-           TimeTicks desired_run_time,
+           TimeTicks delayed_run_time,
            EnqueueOrder sequence_order,
            EnqueueOrder enqueue_order,
            internal::WakeUpResolution resolution)
     : PendingTask(posted_task.location,
                   std::move(posted_task.callback),
-                  desired_run_time,
+                  delayed_run_time,
                   posted_task.nestable),
       task_type(posted_task.task_type),
       task_runner(std::move(posted_task.task_runner)),
diff --git a/base/task/sequence_manager/tasks.h b/base/task/sequence_manager/tasks.h
index d1e1912..4e59908 100644
--- a/base/task/sequence_manager/tasks.h
+++ b/base/task/sequence_manager/tasks.h
@@ -76,7 +76,7 @@
 // PendingTask with extra metadata for SequenceManager.
 struct BASE_EXPORT Task : public PendingTask {
   Task(internal::PostedTask posted_task,
-       TimeTicks desired_run_time,
+       TimeTicks delayed_run_time,
        EnqueueOrder sequence_order,
        EnqueueOrder enqueue_order = EnqueueOrder(),
        internal::WakeUpResolution wake_up_resolution =
diff --git a/content/browser/scheduler/responsiveness/README b/content/browser/scheduler/responsiveness/README
index a5a3922..c134599 100644
--- a/content/browser/scheduler/responsiveness/README
+++ b/content/browser/scheduler/responsiveness/README
@@ -1,7 +1,8 @@
 The classes in this folder estimate the responsiveness of Chrome by measuring
-execution latency on the UI and IO threads of the browser process.
+execution duration of individual work items on the UI and IO threads of the
+browser process.
 
-There are four types of work executed on the UI and IO threads.
+There are four types of work items executed on the UI and IO threads.
 1) Both the UI and IO threads can have tasks posted to them via the Task
    Scheduler [e.g. via base::PostTask with a BrowserThread::ID].
 2) The UI thread processes native events directly from the message loop
@@ -10,26 +11,27 @@
 3) The IO thread's message pump processes IPCs by listening on data channels
    [e.g. fds] and waking up on available data.
 4) The UI thread's message loop may process other platform-specific sources.
+   This is currently not monitored because there is no consistent way to
+   instrument. If individual sources prove to be a source of non-responsiveness,
+   they will need to be addressed on a case-by-case basis.
 
-Execution latency is a measure of the duration between when a task or event is
-scheduled or created, and when it finishes executing. We measure this for (1)
-and (2) but not (3) and (4). More details:
+The classes in this folder do not monitor the queuing time of work items. The
+work to monitor this is tracked in https://crbug.com/1029137. This is how the
+queuing time of each work item type could be monitored:
 
-1) Record TimeTicks::Now() when the event is scheduled and compare to
-   TimeTicks::Now() when the event finishes execution.
-2) All native events have a creation timestamp. Compare that to
-   TimeTicks::Now() when the event finishes execution.
+1) Compute the delta between TimeTicks::Now() when the task starts execution and
+   |PendingTask::queue_time|. The queuing time of low priority tasks should
+   probably be ignored, since it can be long by design.
+2) All native events have a creation timestamp which could be compared to
+   TimeTicks::Now() when the event starts execution. However, we have evidence
+   on Windows, macOS and Linux that the timestamp on native events is not
+   reliable. See https://crbug.com/859155#c39.
 3) There's no good solution here, since the current wire format for IPCs does
    not record the time at which the IPC was written to the data channel. The
    time between reading from the data channel and finishing execution is
    typically small, as heavy tasks are supposed to be dispatched off the IO
    thread.
-4) There is no consistent way to measure the execution latency of work that
-   is neither a task nor an event. If individual sources prove to be
-   a source of non-responsiveness, they will need to be addressed on a
-   case-by-case basis.
-
-Note: As long as there are any tasks or events queued, jank caused by (3) or
-(4) will be accounted for, as it will show up as increased queueing time.
+4) As explained above, individual sources should be investigated on a
+   case-by-case basis if they prove to be a source of non-responsiveness.
 
 Design doc: https://docs.google.com/document/d/1vDSGFvJblh7yJ3U3RVB_7qZLubyfTbQdQjuN1GoUNkc/edit
diff --git a/content/browser/scheduler/responsiveness/watcher.cc b/content/browser/scheduler/responsiveness/watcher.cc
index dcc1ebd..5291b3ff 100644
--- a/content/browser/scheduler/responsiveness/watcher.cc
+++ b/content/browser/scheduler/responsiveness/watcher.cc
@@ -73,12 +73,8 @@
   }
 
   currently_running_metadata->emplace_back(task);
-
-  // For delayed tasks, record the time right before the task is run.
-  if (!task->delayed_run_time.is_null()) {
-    currently_running_metadata->back().execution_start_time =
-        base::TimeTicks::Now();
-  }
+  currently_running_metadata->back().execution_start_time =
+      base::TimeTicks::Now();
 }
 
 void Watcher::DidRunTask(const base::PendingTask* task,
@@ -113,22 +109,8 @@
   if (UNLIKELY(caused_reentrancy))
     return;
 
-  // For delayed tasks, measure the duration of the task itself, rather than the
-  // duration from schedule time to finish time.
-  base::TimeTicks schedule_time;
-  if (execution_start_time.is_null()) {
-    // Tasks which were posted before the MessageLoopObserver was created will
-    // not have a queue_time, and should be ignored. This doesn't affect delayed
-    // tasks.
-    if (UNLIKELY(task->queue_time.is_null()))
-      return;
-
-    schedule_time = task->queue_time;
-  } else {
-    schedule_time = execution_start_time;
-  }
-
-  std::move(callback).Run(schedule_time, base::TimeTicks::Now());
+  DCHECK(!execution_start_time.is_null());
+  std::move(callback).Run(execution_start_time, base::TimeTicks::Now());
 }
 
 void Watcher::WillRunEventOnUIThread(const void* opaque_identifier) {
diff --git a/content/browser/scheduler/responsiveness/watcher.h b/content/browser/scheduler/responsiveness/watcher.h
index 7ce3f39..01bc7912 100644
--- a/content/browser/scheduler/responsiveness/watcher.h
+++ b/content/browser/scheduler/responsiveness/watcher.h
@@ -60,16 +60,7 @@
     // Whether the task or event has caused reentrancy.
     bool caused_reentrancy = false;
 
-    // For delayed tasks, the time at which the event is scheduled to run
-    // is only loosely coupled to the time that the task actually runs. The
-    // difference between these is not interesting for computing responsiveness.
-    // Instead of measuring the duration between |queue_time| and |finish_time|,
-    // we measure the duration of execution itself.
-    //
-    // We have evidence on Windows, macOS and Linux that the timestamp on native
-    // events is not reliable. For native events, we also measure execution
-    // duration instead of queue time + execution duration. See
-    // https://crbug.com/859155#c39.
+    // The time at which the task or event started running.
     base::TimeTicks execution_start_time;
   };
 
diff --git a/content/browser/scheduler/responsiveness/watcher_unittest.cc b/content/browser/scheduler/responsiveness/watcher_unittest.cc
index 0fd4443..10cc312 100644
--- a/content/browser/scheduler/responsiveness/watcher_unittest.cc
+++ b/content/browser/scheduler/responsiveness/watcher_unittest.cc
@@ -25,32 +25,36 @@
 
 class FakeCalculator : public Calculator {
  public:
-  void TaskOrEventFinishedOnUIThread(base::TimeTicks schedule_time,
+  void TaskOrEventFinishedOnUIThread(base::TimeTicks execution_time,
                                      base::TimeTicks finish_time) override {
-    queue_times_ui_.push_back(schedule_time);
+    execution_times_ui_.push_back(execution_time);
   }
 
-  void TaskOrEventFinishedOnIOThread(base::TimeTicks schedule_time,
+  void TaskOrEventFinishedOnIOThread(base::TimeTicks execution_time,
                                      base::TimeTicks finish_time) override {
     base::AutoLock l(io_thread_lock_);
-    queue_times_io_.push_back(schedule_time);
+    execution_times_io_.push_back(execution_time);
   }
 
-  int NumTasksOnUIThread() { return static_cast<int>(queue_times_ui_.size()); }
-  std::vector<base::TimeTicks>& QueueTimesUIThread() { return queue_times_ui_; }
+  int NumTasksOnUIThread() {
+    return static_cast<int>(execution_times_ui_.size());
+  }
+  std::vector<base::TimeTicks>& ExecutionTimesUIThread() {
+    return execution_times_ui_;
+  }
   int NumTasksOnIOThread() {
     base::AutoLock l(io_thread_lock_);
-    return static_cast<int>(queue_times_io_.size());
+    return static_cast<int>(execution_times_io_.size());
   }
-  std::vector<base::TimeTicks>& QueueTimesIOThread() {
+  std::vector<base::TimeTicks>& ExecutionTimesIOThread() {
     base::AutoLock l(io_thread_lock_);
-    return queue_times_io_;
+    return execution_times_io_;
   }
 
  private:
-  std::vector<base::TimeTicks> queue_times_ui_;
+  std::vector<base::TimeTicks> execution_times_ui_;
   base::Lock io_thread_lock_;
-  std::vector<base::TimeTicks> queue_times_io_;
+  std::vector<base::TimeTicks> execution_times_io_;
 };
 
 class FakeMetricSource : public MetricSource {
@@ -96,11 +100,11 @@
         register_message_loop_observer_(register_message_loop_observer) {}
 
   int NumTasksOnUIThread() { return calculator_->NumTasksOnUIThread(); }
-  std::vector<base::TimeTicks>& QueueTimesUIThread() {
-    return calculator_->QueueTimesUIThread();
+  std::vector<base::TimeTicks>& ExecutionTimesUIThread() {
+    return calculator_->ExecutionTimesUIThread();
   }
-  std::vector<base::TimeTicks>& QueueTimesIOThread() {
-    return calculator_->QueueTimesIOThread();
+  std::vector<base::TimeTicks>& ExecutionTimesIOThread() {
+    return calculator_->ExecutionTimesIOThread();
   }
   int NumTasksOnIOThread() { return calculator_->NumTasksOnIOThread(); }
 
@@ -131,7 +135,8 @@
  protected:
   // This member sets up BrowserThread::IO and BrowserThread::UI. It must be the
   // first member, as other members may depend on these abstractions.
-  content::BrowserTaskEnvironment task_environment_;
+  content::BrowserTaskEnvironment task_environment_{
+      content::BrowserTaskEnvironment::TimeSource::MOCK_TIME};
 
   scoped_refptr<FakeWatcher> watcher_;
 };
@@ -140,7 +145,6 @@
 TEST_F(ResponsivenessWatcherTest, TaskForwarding) {
   for (int i = 0; i < 3; ++i) {
     base::PendingTask task(FROM_HERE, base::OnceClosure());
-    task.queue_time = base::TimeTicks::Now();
     watcher_->WillRunTaskOnUIThread(&task);
     watcher_->DidRunTaskOnUIThread(&task);
   }
@@ -149,7 +153,6 @@
 
   for (int i = 0; i < 4; ++i) {
     base::PendingTask task(FROM_HERE, base::OnceClosure());
-    task.queue_time = base::TimeTicks::Now();
     watcher_->WillRunTaskOnIOThread(&task);
     watcher_->DidRunTaskOnIOThread(&task);
   }
@@ -159,18 +162,20 @@
 
 // Test that nested tasks are not forwarded to the calculator.
 TEST_F(ResponsivenessWatcherTest, TaskNesting) {
-  base::TimeTicks now = base::TimeTicks::Now();
-
   base::PendingTask task1(FROM_HERE, base::OnceClosure());
-  task1.queue_time = now + base::TimeDelta::FromMilliseconds(1);
   base::PendingTask task2(FROM_HERE, base::OnceClosure());
-  task2.queue_time = now + base::TimeDelta::FromMilliseconds(2);
   base::PendingTask task3(FROM_HERE, base::OnceClosure());
-  task3.queue_time = now + base::TimeDelta::FromMilliseconds(3);
 
+  const base::TimeTicks task_1_execution_time = base::TimeTicks::Now();
   watcher_->WillRunTaskOnUIThread(&task1);
   watcher_->WillRunTaskOnUIThread(&task2);
+
+  task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1));
+  const base::TimeTicks task_3_execution_time = base::TimeTicks::Now();
+  EXPECT_EQ(task_1_execution_time + base::TimeDelta::FromMilliseconds(1),
+            task_3_execution_time);
   watcher_->WillRunTaskOnUIThread(&task3);
+
   watcher_->DidRunTaskOnUIThread(&task3);
   watcher_->DidRunTaskOnUIThread(&task2);
   watcher_->DidRunTaskOnUIThread(&task1);
@@ -179,8 +184,7 @@
 
   // The innermost task should be the one that is passed through, as it didn't
   // cause reentrancy.
-  EXPECT_EQ(now + base::TimeDelta::FromMilliseconds(3),
-            watcher_->QueueTimesUIThread()[0]);
+  EXPECT_EQ(task_3_execution_time, watcher_->ExecutionTimesUIThread()[0]);
   EXPECT_EQ(0, watcher_->NumTasksOnIOThread());
 }
 
@@ -196,7 +200,7 @@
 
   // The queue time should be after |start_time|, since we actually measure
   // execution time rather than queue time + execution time for native events.
-  EXPECT_GE(watcher_->QueueTimesUIThread()[0], start_time);
+  EXPECT_GE(watcher_->ExecutionTimesUIThread()[0], start_time);
   EXPECT_EQ(0, watcher_->NumTasksOnIOThread());
 }
 
@@ -254,9 +258,9 @@
   run_loop.Run();
 
   ASSERT_GE(watcher_->NumTasksOnUIThread(), 1);
-  EXPECT_FALSE(watcher_->QueueTimesUIThread()[0].is_null());
+  EXPECT_FALSE(watcher_->ExecutionTimesUIThread()[0].is_null());
   ASSERT_GE(watcher_->NumTasksOnIOThread(), 1);
-  EXPECT_FALSE(watcher_->QueueTimesIOThread()[0].is_null());
+  EXPECT_FALSE(watcher_->ExecutionTimesIOThread()[0].is_null());
 }
 
 }  // namespace responsiveness
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 789ee17..0a62bba 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -19521,6 +19521,15 @@
     (before the message loop gets to run). This was fixed and a fake-improvement
     in the high percentiles is expected when the fix goes live.
 
+    Due to a bug, this metric does not measure long queueing time as jankiness.
+    It only measures jankiness as individual tasks taking a long time to run.
+    Given that it's a long-standing bug, this metric will not be modified and we
+    will introduce a second metric
+    Browser.Responsiveness.JankyIntervalsPerThirtySeconds2 that correctly
+    accounts for queueing time. See
+    https://groups.google.com/a/google.com/forum/#!topic/chrome-scheduler/X32gKl6fW2A
+    for more details.
+
     This histogram is of special interest to the chrome-analysis-team@. Do not
     change its semantics or retire it without talking to them first.
   </summary>