Frame Timing tracker for http://w3c.github.io/frame-timing/

New classes:

* FrameTimingTracker - class to collect CompositeEvents from LayerTreeHostImpl and buffer them up until LayerTreeHost can request them and forward them to Blink.

* FrameTimingTracker::CompositeTimingEvent - helper class to store the information about a give Composite event. This currently includes the Main Frame # that generated the event, and the timestamp corresponding to the requestAnimationFrame beginTime. In the future it may be extended to include more detailed timing information about the cost of producing this frame.

BUG=441555

Review URL: https://codereview.chromium.org/848693003

Cr-Commit-Position: refs/heads/master@{#312002}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 67aabfd..746cf75 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -78,6 +78,8 @@
     "debug/devtools_instrumentation.h",
     "debug/frame_rate_counter.cc",
     "debug/frame_rate_counter.h",
+    "debug/frame_timing_tracker.cc",
+    "debug/frame_timing_tracker.h",
     "debug/frame_viewer_instrumentation.cc",
     "debug/frame_viewer_instrumentation.h",
     "debug/invalidation_benchmark.cc",
@@ -739,6 +741,7 @@
     "base/simple_enclosed_region_unittest.cc",
     "base/tiling_data_unittest.cc",
     "base/util_unittest.cc",
+    "debug/frame_timing_tracker_unittest.cc",
     "debug/micro_benchmark_controller_unittest.cc",
     "input/top_controls_manager_unittest.cc",
     "layers/contents_scaling_layer_unittest.cc",
diff --git a/cc/cc.gyp b/cc/cc.gyp
index f77506e0..69d1907 100644
--- a/cc/cc.gyp
+++ b/cc/cc.gyp
@@ -106,6 +106,8 @@
         'debug/devtools_instrumentation.h',
         'debug/frame_rate_counter.cc',
         'debug/frame_rate_counter.h',
+        'debug/frame_timing_tracker.cc',
+        'debug/frame_timing_tracker.h',
         'debug/frame_viewer_instrumentation.cc',
         'debug/frame_viewer_instrumentation.h',
         'debug/invalidation_benchmark.cc',
diff --git a/cc/cc_tests.gyp b/cc/cc_tests.gyp
index 96ee4ebf..74cb1bfd 100644
--- a/cc/cc_tests.gyp
+++ b/cc/cc_tests.gyp
@@ -23,6 +23,7 @@
       'base/tiling_data_unittest.cc',
       'base/unique_notifier_unittest.cc',
       'base/util_unittest.cc',
+      'debug/frame_timing_tracker_unittest.cc',
       'debug/micro_benchmark_controller_unittest.cc',
       'debug/rendering_stats_unittest.cc',
       'input/top_controls_manager_unittest.cc',
diff --git a/cc/debug/frame_timing_tracker.cc b/cc/debug/frame_timing_tracker.cc
new file mode 100644
index 0000000..009d298
--- /dev/null
+++ b/cc/debug/frame_timing_tracker.cc
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/debug/frame_timing_tracker.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/metrics/histogram.h"
+#include "cc/trees/proxy.h"
+
+namespace cc {
+
+FrameTimingTracker::CompositeTimingEvent::CompositeTimingEvent(
+    int _frame_id,
+    base::TimeTicks _timestamp)
+    : frame_id(_frame_id), timestamp(_timestamp) {
+}
+
+FrameTimingTracker::CompositeTimingEvent::~CompositeTimingEvent() {
+}
+
+// static
+scoped_ptr<FrameTimingTracker> FrameTimingTracker::Create() {
+  return make_scoped_ptr(new FrameTimingTracker);
+}
+
+FrameTimingTracker::FrameTimingTracker() {
+}
+
+FrameTimingTracker::~FrameTimingTracker() {
+}
+
+void FrameTimingTracker::SaveTimeStamps(
+    base::TimeTicks timestamp,
+    const std::vector<FrameAndRectIds>& frame_ids) {
+  if (!composite_events_)
+    composite_events_.reset(new CompositeTimingSet);
+  for (const auto& pair : frame_ids)
+    (*composite_events_)[pair.second].push_back(
+        CompositeTimingEvent(pair.first, timestamp));
+}
+
+scoped_ptr<FrameTimingTracker::CompositeTimingSet>
+FrameTimingTracker::GroupCountsByRectId() {
+  if (!composite_events_)
+    return make_scoped_ptr(new CompositeTimingSet);
+  scoped_ptr<CompositeTimingSet> composite_info(new CompositeTimingSet);
+  for (auto& infos : *composite_events_)
+    std::sort(
+        infos.second.begin(), infos.second.end(),
+        [](const CompositeTimingEvent& lhs, const CompositeTimingEvent& rhs) {
+          return lhs.timestamp < rhs.timestamp;
+        });
+  return composite_events_.Pass();
+}
+
+}  // namespace cc
diff --git a/cc/debug/frame_timing_tracker.h b/cc/debug/frame_timing_tracker.h
new file mode 100644
index 0000000..019fe794
--- /dev/null
+++ b/cc/debug/frame_timing_tracker.h
@@ -0,0 +1,63 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_DEBUG_FRAME_TIMING_TRACKER_H_
+#define CC_DEBUG_FRAME_TIMING_TRACKER_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+// This class maintains a history of timestamps and rect IDs to communicate
+// frame events back to Blink
+// TODO(mpb): Start using this. crbug.com/442554
+class CC_EXPORT FrameTimingTracker {
+ public:
+  struct CC_EXPORT CompositeTimingEvent {
+    CompositeTimingEvent(int, base::TimeTicks);
+    ~CompositeTimingEvent();
+
+    int frame_id;
+    base::TimeTicks timestamp;
+  };
+
+  using CompositeTimingSet =
+      base::hash_map<int, std::vector<CompositeTimingEvent>>;
+
+  static scoped_ptr<FrameTimingTracker> Create();
+
+  ~FrameTimingTracker();
+
+  // This routine takes all of the individual CompositeEvents stored in the
+  // tracker and collects them by "rect_id", as in the example below.
+  // [ {f_id1,r_id1,t1}, {f_id2,r_id1,t2}, {f_id3,r_id2,t3} ]
+  // ====>
+  // [ {r_id1,<{f_id1,t1},{f_id2,t2}>}, {r_id2,<{f_id3,t3}>} ]
+  scoped_ptr<CompositeTimingSet> GroupCountsByRectId();
+
+  // This routine takes a timestamp and an array of frame_id,rect_id pairs
+  // and generates CompositeTimingEvents (frame_id, timestamp) and adds them to
+  // internal hash_map keyed on rect_id
+  using FrameAndRectIds = std::pair<int, int64_t>;
+  void SaveTimeStamps(base::TimeTicks timestamp,
+                      const std::vector<FrameAndRectIds>& frame_ids);
+
+ private:
+  FrameTimingTracker();
+
+  scoped_ptr<CompositeTimingSet> composite_events_;
+
+  DISALLOW_COPY_AND_ASSIGN(FrameTimingTracker);
+};
+
+}  // namespace cc
+
+#endif  // CC_DEBUG_FRAME_TIMING_TRACKER_H_
diff --git a/cc/debug/frame_timing_tracker_unittest.cc b/cc/debug/frame_timing_tracker_unittest.cc
new file mode 100644
index 0000000..b27dd07
--- /dev/null
+++ b/cc/debug/frame_timing_tracker_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/debug/trace_event_argument.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "cc/debug/frame_timing_tracker.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+static std::string ToString(
+    scoped_ptr<FrameTimingTracker::CompositeTimingSet> timingset) {
+  scoped_refptr<base::debug::TracedValue> value =
+      new base::debug::TracedValue();
+  value->BeginArray("values");
+  for (const auto& it : *timingset) {
+    value->BeginDictionary();
+    value->SetInteger("rect_id", it.first);
+    value->BeginArray("events");
+    for (const auto& event : it.second) {
+      value->BeginDictionary();
+      value->SetInteger("frame_id", event.frame_id);
+      value->SetInteger("timestamp", event.timestamp.ToInternalValue());
+      value->EndDictionary();
+    }
+    value->EndArray();
+    value->EndDictionary();
+  }
+  value->EndArray();
+  return value->ToString();
+}
+
+TEST(FrameTimingTrackerTest, DefaultTrackerIsEmpty) {
+  scoped_ptr<FrameTimingTracker> tracker(FrameTimingTracker::Create());
+  EXPECT_EQ("{\"values\":[]}", ToString(tracker->GroupCountsByRectId()));
+}
+
+TEST(FrameTimingTrackerTest, NoFrameIdsIsEmpty) {
+  scoped_ptr<FrameTimingTracker> tracker(FrameTimingTracker::Create());
+  std::vector<std::pair<int, int64_t>> ids;
+  tracker->SaveTimeStamps(base::TimeTicks::FromInternalValue(100), ids);
+  EXPECT_EQ("{\"values\":[]}", ToString(tracker->GroupCountsByRectId()));
+}
+
+TEST(FrameTimingTrackerTest, OneFrameId) {
+  scoped_ptr<FrameTimingTracker> tracker(FrameTimingTracker::Create());
+  std::vector<std::pair<int, int64_t>> ids;
+  ids.push_back(std::make_pair(1, 2));
+  tracker->SaveTimeStamps(base::TimeTicks::FromInternalValue(100), ids);
+  EXPECT_EQ(
+      "{\"values\":[{\"events\":["
+      "{\"frame_id\":1,\"timestamp\":100}],\"rect_id\":2}]}",
+      ToString(tracker->GroupCountsByRectId()));
+}
+
+TEST(FrameTimingTrackerTest, UnsortedTimestampsIds) {
+  scoped_ptr<FrameTimingTracker> tracker(FrameTimingTracker::Create());
+  std::vector<std::pair<int, int64_t>> ids;
+  ids.push_back(std::make_pair(1, 2));
+  tracker->SaveTimeStamps(base::TimeTicks::FromInternalValue(200), ids);
+  tracker->SaveTimeStamps(base::TimeTicks::FromInternalValue(400), ids);
+  tracker->SaveTimeStamps(base::TimeTicks::FromInternalValue(100), ids);
+  EXPECT_EQ(
+      "{\"values\":[{\"events\":["
+      "{\"frame_id\":1,\"timestamp\":100},"
+      "{\"frame_id\":1,\"timestamp\":200},"
+      "{\"frame_id\":1,\"timestamp\":400}],\"rect_id\":2}]}",
+      ToString(tracker->GroupCountsByRectId()));
+}
+
+TEST(FrameTimingTrackerTest, MultipleFrameIds) {
+  scoped_ptr<FrameTimingTracker> tracker(FrameTimingTracker::Create());
+
+  std::vector<std::pair<int, int64_t>> ids200;
+  ids200.push_back(std::make_pair(1, 2));
+  ids200.push_back(std::make_pair(1, 3));
+  tracker->SaveTimeStamps(base::TimeTicks::FromInternalValue(200), ids200);
+
+  std::vector<std::pair<int, int64_t>> ids400;
+  ids400.push_back(std::make_pair(2, 2));
+  tracker->SaveTimeStamps(base::TimeTicks::FromInternalValue(400), ids400);
+
+  std::vector<std::pair<int, int64_t>> ids100;
+  ids100.push_back(std::make_pair(3, 2));
+  ids100.push_back(std::make_pair(2, 3));
+  ids100.push_back(std::make_pair(3, 4));
+  tracker->SaveTimeStamps(base::TimeTicks::FromInternalValue(100), ids100);
+
+  std::string result = ToString(tracker->GroupCountsByRectId());
+
+  EXPECT_EQ(strlen(
+                "{\"values\":[{\"events\":["
+                "{\"frame_id\":3,\"timestamp\":100},"
+                "{\"frame_id\":1,\"timestamp\":200},"
+                "{\"frame_id\":2,\"timestamp\":400}],\"rect_id\":2},"
+                "{\"events\":["
+                "{\"frame_id\":2,\"timestamp\":100},"
+                "{\"frame_id\":1,\"timestamp\":200}],\"rect_id\":3},"
+                "{\"events\":["
+                "{\"frame_id\":3,\"timestamp\":100}],\"rect_id\":4}"
+                "]}"),
+            result.size());
+  EXPECT_NE(std::string::npos,
+            result.find(
+                "{\"frame_id\":3,\"timestamp\":100},"
+                "{\"frame_id\":1,\"timestamp\":200},"
+                "{\"frame_id\":2,\"timestamp\":400}],\"rect_id\":2}"));
+  EXPECT_NE(std::string::npos,
+            result.find(
+                "{\"events\":["
+                "{\"frame_id\":2,\"timestamp\":100},"
+                "{\"frame_id\":1,\"timestamp\":200}],\"rect_id\":3}"));
+  EXPECT_NE(std::string::npos,
+            result.find(
+                "{\"events\":["
+                "{\"frame_id\":3,\"timestamp\":100}],\"rect_id\":4}"));
+}
+
+}  // namespace
+}  // namespace cc