Add ftrace per_cpu/stats to beginning/end of the trace

This is to detect ftrace event going missing because
the kernel buffer cannot keep up. Both the first and
last snapshot are dumped at the end of the trace, during
the flush.

Bug: 78765090
Bug: 73886018
Change-Id: I9a806274532f0007f63f5e729c8b4c032495edaa
diff --git a/Android.bp b/Android.bp
index 1deeabe..5ed3bff 100644
--- a/Android.bp
+++ b/Android.bp
@@ -32,6 +32,7 @@
     "src/base/file_utils.cc",
     "src/base/page_allocator.cc",
     "src/base/string_splitter.cc",
+    "src/base/string_utils.cc",
     "src/base/temp_file.cc",
     "src/base/thread_checker.cc",
     "src/base/unix_task_runner.cc",
@@ -39,6 +40,7 @@
     "src/base/watchdog_posix.cc",
     "src/ftrace_reader/atrace_wrapper.cc",
     "src/ftrace_reader/cpu_reader.cc",
+    "src/ftrace_reader/cpu_stats_parser.cc",
     "src/ftrace_reader/event_info.cc",
     "src/ftrace_reader/event_info_constants.cc",
     "src/ftrace_reader/format_parser.cc",
@@ -140,6 +142,7 @@
     "src/base/file_utils.cc",
     "src/base/page_allocator.cc",
     "src/base/string_splitter.cc",
+    "src/base/string_utils.cc",
     "src/base/temp_file.cc",
     "src/base/thread_checker.cc",
     "src/base/unix_task_runner.cc",
@@ -263,6 +266,7 @@
     "src/base/file_utils.cc",
     "src/base/page_allocator.cc",
     "src/base/string_splitter.cc",
+    "src/base/string_utils.cc",
     "src/base/temp_file.cc",
     "src/base/test/test_task_runner.cc",
     "src/base/test/vm_test_utils.cc",
@@ -272,6 +276,7 @@
     "src/base/watchdog_posix.cc",
     "src/ftrace_reader/atrace_wrapper.cc",
     "src/ftrace_reader/cpu_reader.cc",
+    "src/ftrace_reader/cpu_stats_parser.cc",
     "src/ftrace_reader/end_to_end_integrationtest.cc",
     "src/ftrace_reader/event_info.cc",
     "src/ftrace_reader/event_info_constants.cc",
@@ -886,6 +891,7 @@
     "protos/perfetto/trace/ftrace/f2fs_write_end.proto",
     "protos/perfetto/trace/ftrace/ftrace_event.proto",
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
+    "protos/perfetto/trace/ftrace/ftrace_stats.proto",
     "protos/perfetto/trace/ftrace/i2c_read.proto",
     "protos/perfetto/trace/ftrace/i2c_reply.proto",
     "protos/perfetto/trace/ftrace/i2c_result.proto",
@@ -1155,6 +1161,7 @@
     "external/perfetto/protos/perfetto/trace/ftrace/f2fs_write_end.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.pb.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c_read.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c_reply.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c_result.pb.cc",
@@ -1425,6 +1432,7 @@
     "protos/perfetto/trace/ftrace/f2fs_write_end.proto",
     "protos/perfetto/trace/ftrace/ftrace_event.proto",
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
+    "protos/perfetto/trace/ftrace/ftrace_stats.proto",
     "protos/perfetto/trace/ftrace/i2c_read.proto",
     "protos/perfetto/trace/ftrace/i2c_reply.proto",
     "protos/perfetto/trace/ftrace/i2c_result.proto",
@@ -1694,6 +1702,7 @@
     "external/perfetto/protos/perfetto/trace/ftrace/f2fs_write_end.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.pb.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c_read.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c_reply.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c_result.pb.h",
@@ -1967,6 +1976,7 @@
     "protos/perfetto/trace/ftrace/f2fs_write_end.proto",
     "protos/perfetto/trace/ftrace/ftrace_event.proto",
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
+    "protos/perfetto/trace/ftrace/ftrace_stats.proto",
     "protos/perfetto/trace/ftrace/i2c_read.proto",
     "protos/perfetto/trace/ftrace/i2c_reply.proto",
     "protos/perfetto/trace/ftrace/i2c_result.proto",
@@ -2237,6 +2247,7 @@
     "external/perfetto/protos/perfetto/trace/ftrace/f2fs_write_end.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c_read.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c_reply.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c_result.pbzero.cc",
@@ -2507,6 +2518,7 @@
     "protos/perfetto/trace/ftrace/f2fs_write_end.proto",
     "protos/perfetto/trace/ftrace/ftrace_event.proto",
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
+    "protos/perfetto/trace/ftrace/ftrace_stats.proto",
     "protos/perfetto/trace/ftrace/i2c_read.proto",
     "protos/perfetto/trace/ftrace/i2c_reply.proto",
     "protos/perfetto/trace/ftrace/i2c_result.proto",
@@ -2777,6 +2789,7 @@
     "external/perfetto/protos/perfetto/trace/ftrace/f2fs_write_end.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c_read.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c_reply.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c_result.pbzero.h",
@@ -3400,6 +3413,7 @@
     "src/base/file_utils.cc",
     "src/base/page_allocator.cc",
     "src/base/string_splitter.cc",
+    "src/base/string_utils.cc",
     "src/base/temp_file.cc",
     "src/base/thread_checker.cc",
     "src/base/unix_task_runner.cc",
@@ -3570,6 +3584,8 @@
     "src/base/scoped_file_unittest.cc",
     "src/base/string_splitter.cc",
     "src/base/string_splitter_unittest.cc",
+    "src/base/string_utils.cc",
+    "src/base/string_utils_unittest.cc",
     "src/base/task_runner_unittest.cc",
     "src/base/temp_file.cc",
     "src/base/temp_file_unittest.cc",
@@ -3587,6 +3603,8 @@
     "src/ftrace_reader/atrace_wrapper.cc",
     "src/ftrace_reader/cpu_reader.cc",
     "src/ftrace_reader/cpu_reader_unittest.cc",
+    "src/ftrace_reader/cpu_stats_parser.cc",
+    "src/ftrace_reader/cpu_stats_parser_unittest.cc",
     "src/ftrace_reader/event_info.cc",
     "src/ftrace_reader/event_info_constants.cc",
     "src/ftrace_reader/event_info_unittest.cc",
diff --git a/include/perfetto/base/BUILD.gn b/include/perfetto/base/BUILD.gn
index 9d29811..985824c 100644
--- a/include/perfetto/base/BUILD.gn
+++ b/include/perfetto/base/BUILD.gn
@@ -21,6 +21,7 @@
     "scoped_file.h",
     "small_set.h",
     "string_splitter.h",
+    "string_utils.h",
     "task_runner.h",
     "thread_checker.h",
     "time.h",
diff --git a/include/perfetto/base/string_utils.h b/include/perfetto/base/string_utils.h
new file mode 100644
index 0000000..9b4248a
--- /dev/null
+++ b/include/perfetto/base/string_utils.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDE_PERFETTO_BASE_STRING_UTILS_H_
+#define INCLUDE_PERFETTO_BASE_STRING_UTILS_H_
+
+#include <string>
+
+namespace perfetto {
+namespace base {
+
+bool StartsWith(const std::string& str, const std::string& prefix);
+
+}  // namespace base
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_BASE_STRING_UTILS_H_
diff --git a/include/perfetto/ftrace_reader/ftrace_controller.h b/include/perfetto/ftrace_reader/ftrace_controller.h
index f01b885..7712dcc 100644
--- a/include/perfetto/ftrace_reader/ftrace_controller.h
+++ b/include/perfetto/ftrace_reader/ftrace_controller.h
@@ -39,9 +39,37 @@
 
 namespace perfetto {
 
+namespace protos {
+namespace pbzero {
+class FtraceEventBundle;
+class FtraceStats;
+class FtraceCpuStats;
+}  // namespace pbzero
+}  // namespace protos
+
 using BlockDeviceID = decltype(stat::st_dev);
 using Inode = decltype(stat::st_ino);
 
+struct FtraceCpuStats {
+  uint64_t cpu;
+  uint64_t entries;
+  uint64_t overrun;
+  uint64_t commit_overrun;
+  uint64_t bytes_read;
+  double oldest_event_ts;
+  double now_ts;
+  uint64_t dropped_events;
+  uint64_t read_events;
+
+  void Write(protos::pbzero::FtraceCpuStats*) const;
+};
+
+struct FtraceStats {
+  std::vector<FtraceCpuStats> cpu_stats;
+
+  void Write(protos::pbzero::FtraceStats*) const;
+};
+
 struct FtraceMetadata {
   FtraceMetadata();
 
@@ -64,12 +92,6 @@
   void FinishEvent();
 };
 
-namespace protos {
-namespace pbzero {
-class FtraceEventBundle;
-}  // namespace pbzero
-}  // namespace protos
-
 constexpr size_t kMaxSinks = 32;
 constexpr size_t kMaxCpus = 64;
 
@@ -92,6 +114,7 @@
   using FtraceEventBundle = protos::pbzero::FtraceEventBundle;
   class Delegate {
    public:
+    virtual void OnCreate(FtraceSink*) {}
     virtual protozero::MessageHandle<FtraceEventBundle> GetBundleForCpu(
         size_t) = 0;
     virtual void OnBundleComplete(size_t,
@@ -107,6 +130,8 @@
              Delegate*);
   ~FtraceSink();
 
+  void DumpFtraceStats(FtraceStats*);
+
   const FtraceConfig& config() const { return config_; }
 
  private:
@@ -156,6 +181,9 @@
                    std::unique_ptr<FtraceConfigMuxer>,
                    base::TaskRunner*);
 
+  // Write
+  void DumpFtraceStats(FtraceStats*);
+
   // Called to read data from the staging pipe for the given |cpu| and parse it
   // into the sinks. Protected and virtual for testing.
   virtual void OnRawFtraceDataAvailable(size_t cpu);
diff --git a/protos/perfetto/trace/ftrace/all_protos.gni b/protos/perfetto/trace/ftrace/all_protos.gni
index daacb00..4ba9466 100644
--- a/protos/perfetto/trace/ftrace/all_protos.gni
+++ b/protos/perfetto/trace/ftrace/all_protos.gni
@@ -17,6 +17,7 @@
 ftrace_proto_names = [
   "ftrace_event.proto",
   "ftrace_event_bundle.proto",
+  "ftrace_stats.proto",
   "test_bundle_wrapper.proto",
   "print.proto",
   "sched_switch.proto",
diff --git a/protos/perfetto/trace/ftrace/ftrace_stats.proto b/protos/perfetto/trace/ftrace/ftrace_stats.proto
new file mode 100644
index 0000000..7d0ed20
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/ftrace_stats.proto
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+package perfetto.protos;
+
+message FtraceCpuStats {
+  optional uint64 cpu = 1;
+  optional uint64 entries = 2;
+  optional uint64 overrun = 3;
+  optional uint64 commit_overrun = 4;
+  optional uint64 bytes_read = 5;
+  optional double oldest_event_ts = 6;
+  optional double now_ts = 7;
+  optional uint64 dropped_events = 8;
+  optional uint64 read_events = 9;
+}
+
+message FtraceStats {
+  enum Phase {
+    UNUSED = 0;
+    START_OF_TRACE = 1;
+    END_OF_TRACE = 2;
+  }
+  optional Phase phase = 1;
+  repeated FtraceCpuStats cpu_stats = 2;
+}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index b0b56b9..5dc7c0a 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -42,6 +42,7 @@
     // IDs up to 32 are reserved for events that are quite frequent because they
     // take only one byte to encode their preamble.
     // removed field with id 33
+    // removed field with id 34
 
     // This field is only used for testing.
     // removed field with id 536870911  // 2^29 - 1, max field id for protos.
diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto
index 9c38a8b..6fcee5a 100644
--- a/protos/perfetto/trace/trace_packet.proto
+++ b/protos/perfetto/trace/trace_packet.proto
@@ -22,6 +22,7 @@
 import "perfetto/trace/clock_snapshot.proto";
 import "perfetto/trace/filesystem/inode_file_map.proto";
 import "perfetto/trace/ftrace/ftrace_event_bundle.proto";
+import "perfetto/trace/ftrace/ftrace_stats.proto";
 import "perfetto/trace/ps/process_tree.proto";
 import "perfetto/trace/test_event.proto";
 
@@ -42,6 +43,7 @@
     // IDs up to 32 are reserved for events that are quite frequent because they
     // take only one byte to encode their preamble.
     TraceConfig trace_config = 33;
+    FtraceStats ftrace_stats = 34;
 
     // This field is only used for testing.
     TestEvent for_testing = 536870911;  // 2^29 - 1, max field id for protos.
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index 746f999..18d11b6 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -26,6 +26,7 @@
     "file_utils.cc",
     "page_allocator.cc",
     "string_splitter.cc",
+    "string_utils.cc",
     "temp_file.cc",
     "thread_checker.cc",
     "unix_task_runner.cc",
@@ -106,6 +107,7 @@
     "page_allocator_unittest.cc",
     "scoped_file_unittest.cc",
     "string_splitter_unittest.cc",
+    "string_utils_unittest.cc",
     "task_runner_unittest.cc",
     "temp_file_unittest.cc",
     "thread_checker_unittest.cc",
diff --git a/src/base/string_utils.cc b/src/base/string_utils.cc
new file mode 100644
index 0000000..9a5b92b
--- /dev/null
+++ b/src/base/string_utils.cc
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/base/string_utils.h"
+
+namespace perfetto {
+namespace base {
+
+bool StartsWith(const std::string& str, const std::string& prefix) {
+  return str.compare(0, prefix.length(), prefix) == 0;
+}
+
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/base/string_utils_unittest.cc b/src/base/string_utils_unittest.cc
new file mode 100644
index 0000000..5362057
--- /dev/null
+++ b/src/base/string_utils_unittest.cc
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/base/string_utils.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace perfetto {
+namespace base {
+namespace {
+
+TEST(StringUtilsTest, StartsWith) {
+  EXPECT_TRUE(StartsWith("", ""));
+  EXPECT_TRUE(StartsWith("abc", ""));
+  EXPECT_TRUE(StartsWith("abc", "a"));
+  EXPECT_TRUE(StartsWith("abc", "ab"));
+  EXPECT_TRUE(StartsWith("abc", "abc"));
+  EXPECT_FALSE(StartsWith("abc", "abcd"));
+  EXPECT_FALSE(StartsWith("aa", "ab"));
+  EXPECT_FALSE(StartsWith("", "ab"));
+}
+
+}  // namespace
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/ftrace_reader/BUILD.gn b/src/ftrace_reader/BUILD.gn
index f2e010e..755799f 100644
--- a/src/ftrace_reader/BUILD.gn
+++ b/src/ftrace_reader/BUILD.gn
@@ -51,9 +51,11 @@
     "../../gn:gtest_deps",
     "../../protos/perfetto/trace/ftrace:lite",
     "../base:test_support",
+    "../tracing:test_support",
   ]
   sources = [
     "cpu_reader_unittest.cc",
+    "cpu_stats_parser_unittest.cc",
     "event_info_unittest.cc",
     "format_parser_unittest.cc",
     "ftrace_config_muxer_unittest.cc",
@@ -116,6 +118,8 @@
     "atrace_wrapper.h",
     "cpu_reader.cc",
     "cpu_reader.h",
+    "cpu_stats_parser.cc",
+    "cpu_stats_parser.h",
     "event_info.cc",
     "event_info.h",
     "event_info_constants.cc",
diff --git a/src/ftrace_reader/cpu_stats_parser.cc b/src/ftrace_reader/cpu_stats_parser.cc
new file mode 100644
index 0000000..e5f2e94
--- /dev/null
+++ b/src/ftrace_reader/cpu_stats_parser.cc
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/ftrace_reader/cpu_stats_parser.h"
+
+#include "perfetto/base/string_splitter.h"
+#include "perfetto/base/string_utils.h"
+#include "perfetto/ftrace_reader/ftrace_controller.h"
+#include "src/ftrace_reader/ftrace_procfs.h"
+
+namespace perfetto {
+namespace {
+
+uint32_t ExtractInt(const char* s) {
+  for (; *s != '\0'; s++) {
+    if (*s == ':') {
+      return static_cast<uint32_t>(atoi(s + 1));
+    }
+  }
+  return 0;
+}
+
+double ExtractDouble(const char* s) {
+  for (; *s != '\0'; s++) {
+    if (*s == ':') {
+      return strtod(s + 1, nullptr);
+    }
+  }
+  return 0;
+}
+
+}  // namespace
+
+bool DumpCpuStats(std::string text, FtraceCpuStats* stats) {
+  if (text.empty())
+    return false;
+
+  base::StringSplitter splitter(std::move(text), '\n');
+  while (splitter.Next()) {
+    if (base::StartsWith(splitter.cur_token(), "entries")) {
+      stats->entries = ExtractInt(splitter.cur_token());
+    } else if (base::StartsWith(splitter.cur_token(), "overrun")) {
+      stats->overrun = ExtractInt(splitter.cur_token());
+    } else if (base::StartsWith(splitter.cur_token(), "commit overrun")) {
+      stats->commit_overrun = ExtractInt(splitter.cur_token());
+    } else if (base::StartsWith(splitter.cur_token(), "bytes")) {
+      stats->bytes_read = ExtractInt(splitter.cur_token());
+    } else if (base::StartsWith(splitter.cur_token(), "oldest event ts")) {
+      stats->oldest_event_ts = ExtractDouble(splitter.cur_token());
+    } else if (base::StartsWith(splitter.cur_token(), "now ts")) {
+      stats->now_ts = ExtractDouble(splitter.cur_token());
+    } else if (base::StartsWith(splitter.cur_token(), "dropped events")) {
+      stats->dropped_events = ExtractInt(splitter.cur_token());
+    } else if (base::StartsWith(splitter.cur_token(), "read events")) {
+      stats->read_events = ExtractInt(splitter.cur_token());
+    }
+  }
+
+  return true;
+}
+
+bool DumpAllCpuStats(FtraceProcfs* ftrace, FtraceStats* stats) {
+  stats->cpu_stats.resize(ftrace->NumberOfCpus(), {});
+  for (size_t cpu = 0; cpu < ftrace->NumberOfCpus(); cpu++) {
+    stats->cpu_stats[cpu].cpu = cpu;
+    if (!DumpCpuStats(ftrace->ReadCpuStats(cpu), &stats->cpu_stats[cpu]))
+      return false;
+  }
+  return true;
+}
+
+}  // namespace perfetto
diff --git a/src/ftrace_reader/cpu_stats_parser.h b/src/ftrace_reader/cpu_stats_parser.h
new file mode 100644
index 0000000..5cb4b15
--- /dev/null
+++ b/src/ftrace_reader/cpu_stats_parser.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_FTRACE_READER_CPU_STATS_PARSER_H_
+#define SRC_FTRACE_READER_CPU_STATS_PARSER_H_
+
+#include <string>
+
+namespace perfetto {
+
+class FtraceProcfs;
+struct FtraceStats;
+struct FtraceCpuStats;
+
+bool DumpCpuStats(std::string text, FtraceCpuStats*);
+bool DumpAllCpuStats(FtraceProcfs*, FtraceStats*);
+
+}  // namespace perfetto
+
+#endif  // SRC_FTRACE_READER_CPU_STATS_PARSER_H_
diff --git a/src/ftrace_reader/cpu_stats_parser_unittest.cc b/src/ftrace_reader/cpu_stats_parser_unittest.cc
new file mode 100644
index 0000000..0bb6a46
--- /dev/null
+++ b/src/ftrace_reader/cpu_stats_parser_unittest.cc
@@ -0,0 +1,36 @@
+#include "src/ftrace_reader/cpu_stats_parser.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "perfetto/ftrace_reader/ftrace_controller.h"
+
+namespace perfetto {
+namespace {
+
+TEST(CpuStatsParserTest, DumpCpu) {
+  std::string text = R"(entries: 1
+overrun: 2
+commit overrun: 3
+bytes: 4
+oldest event ts:     5123.000
+now ts:  6123.123
+dropped events	 	:7
+read events: 8
+)";
+
+  FtraceCpuStats stats{};
+  EXPECT_TRUE(DumpCpuStats(text, &stats));
+
+  EXPECT_EQ(stats.entries, 1);
+  EXPECT_EQ(stats.overrun, 2);
+  EXPECT_EQ(stats.commit_overrun, 3);
+  EXPECT_EQ(stats.bytes_read, 4);
+  EXPECT_EQ(stats.oldest_event_ts, 5123.000);
+  EXPECT_EQ(stats.now_ts, 6123.123);
+  EXPECT_EQ(stats.dropped_events, 7);
+  EXPECT_EQ(stats.read_events, 8);
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/src/ftrace_reader/ftrace_controller.cc b/src/ftrace_reader/ftrace_controller.cc
index c3a4f43..b3f81c3 100644
--- a/src/ftrace_reader/ftrace_controller.cc
+++ b/src/ftrace_reader/ftrace_controller.cc
@@ -33,12 +33,14 @@
 #include "perfetto/base/time.h"
 #include "perfetto/base/utils.h"
 #include "src/ftrace_reader/cpu_reader.h"
+#include "src/ftrace_reader/cpu_stats_parser.h"
 #include "src/ftrace_reader/event_info.h"
 #include "src/ftrace_reader/ftrace_config_muxer.h"
 #include "src/ftrace_reader/ftrace_procfs.h"
 #include "src/ftrace_reader/proto_translation_table.h"
 
 #include "perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "perfetto/trace/ftrace/ftrace_stats.pbzero.h"
 
 namespace perfetto {
 namespace {
@@ -290,6 +292,7 @@
       new FtraceSink(std::move(controller_weak), id, std::move(config),
                      std::move(filter), delegate));
   Register(sink.get());
+  delegate->OnCreate(sink.get());
   return sink;
 }
 
@@ -339,6 +342,10 @@
   StopIfNeeded();
 }
 
+void FtraceController::DumpFtraceStats(FtraceStats* stats) {
+  DumpAllCpuStats(ftrace_procfs_.get(), stats);
+}
+
 FtraceSink::FtraceSink(base::WeakPtr<FtraceController> controller_weak,
                        FtraceConfigId id,
                        FtraceConfig config,
@@ -359,6 +366,29 @@
   return filter_->enabled_names();
 }
 
+void FtraceSink::DumpFtraceStats(FtraceStats* stats) {
+  if (controller_weak_)
+    controller_weak_->DumpFtraceStats(stats);
+}
+
+void FtraceStats::Write(protos::pbzero::FtraceStats* writer) const {
+  for (const FtraceCpuStats& cpu_specific_stats : cpu_stats) {
+    cpu_specific_stats.Write(writer->add_cpu_stats());
+  }
+}
+
+void FtraceCpuStats::Write(protos::pbzero::FtraceCpuStats* writer) const {
+  writer->set_cpu(cpu);
+  writer->set_entries(entries);
+  writer->set_overrun(overrun);
+  writer->set_commit_overrun(commit_overrun);
+  writer->set_bytes_read(bytes_read);
+  writer->set_oldest_event_ts(oldest_event_ts);
+  writer->set_now_ts(now_ts);
+  writer->set_dropped_events(dropped_events);
+  writer->set_read_events(read_events);
+}
+
 FtraceMetadata::FtraceMetadata() {
   // A lot of the time there will only be a small number of inodes.
   inode_and_device.reserve(10);
diff --git a/src/ftrace_reader/ftrace_controller_unittest.cc b/src/ftrace_reader/ftrace_controller_unittest.cc
index 5ffad09..5e3163d 100644
--- a/src/ftrace_reader/ftrace_controller_unittest.cc
+++ b/src/ftrace_reader/ftrace_controller_unittest.cc
@@ -22,10 +22,13 @@
 
 #include "perfetto/ftrace_reader/ftrace_config.h"
 #include "perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "perfetto/trace/trace_packet.pb.h"
+#include "perfetto/trace/trace_packet.pbzero.h"
 #include "src/ftrace_reader/cpu_reader.h"
 #include "src/ftrace_reader/ftrace_config_muxer.h"
 #include "src/ftrace_reader/ftrace_procfs.h"
 #include "src/ftrace_reader/proto_translation_table.h"
+#include "src/tracing/core/trace_writer_for_testing.h"
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
@@ -650,4 +653,27 @@
   EXPECT_THAT(metadata.pids, ElementsAre(1, 2, 3));
 }
 
+TEST(FtraceStatsTest, Write) {
+  FtraceStats stats{};
+  FtraceCpuStats cpu_stats{};
+  cpu_stats.cpu = 0;
+  cpu_stats.entries = 1;
+  cpu_stats.overrun = 2;
+  stats.cpu_stats.push_back(cpu_stats);
+
+  std::unique_ptr<TraceWriterForTesting> writer =
+      std::unique_ptr<TraceWriterForTesting>(new TraceWriterForTesting());
+  {
+    auto packet = writer->NewTracePacket();
+    auto* out = packet->set_ftrace_stats();
+    stats.Write(out);
+  }
+
+  std::unique_ptr<protos::TracePacket> result_packet = writer->ParseProto();
+  auto result = result_packet->ftrace_stats().cpu_stats(0);
+  EXPECT_EQ(result.cpu(), 0);
+  EXPECT_EQ(result.entries(), 1);
+  EXPECT_EQ(result.overrun(), 2);
+}
+
 }  // namespace perfetto
diff --git a/src/ftrace_reader/ftrace_procfs.cc b/src/ftrace_reader/ftrace_procfs.cc
index 95294bc..70aa050 100644
--- a/src/ftrace_reader/ftrace_procfs.cc
+++ b/src/ftrace_reader/ftrace_procfs.cc
@@ -88,6 +88,11 @@
   return ReadFileIntoString(path);
 }
 
+std::string FtraceProcfs::ReadCpuStats(size_t cpu) const {
+  std::string path = root_ + "per_cpu/cpu" + std::to_string(cpu) + "/stats";
+  return ReadFileIntoString(path);
+}
+
 size_t FtraceProcfs::NumberOfCpus() const {
   static size_t num_cpus = static_cast<size_t>(sysconf(_SC_NPROCESSORS_CONF));
   return num_cpus;
diff --git a/src/ftrace_reader/ftrace_procfs.h b/src/ftrace_reader/ftrace_procfs.h
index faaca88..df37293 100644
--- a/src/ftrace_reader/ftrace_procfs.h
+++ b/src/ftrace_reader/ftrace_procfs.h
@@ -47,6 +47,9 @@
   virtual std::string ReadEventFormat(const std::string& group,
                                       const std::string& name) const;
 
+  // Read the "/per_cpu/cpuXX/stats" file for the given |cpu|.
+  std::string ReadCpuStats(size_t cpu) const;
+
   // Set ftrace buffer size in pages.
   // This size is *per cpu* so for the total size you have to multiply
   // by the number of CPUs.
diff --git a/src/ftrace_reader/proto_translation_table.cc b/src/ftrace_reader/proto_translation_table.cc
index 5ce772a..739e609 100644
--- a/src/ftrace_reader/proto_translation_table.cc
+++ b/src/ftrace_reader/proto_translation_table.cc
@@ -20,6 +20,7 @@
 
 #include <algorithm>
 
+#include "perfetto/base/string_utils.h"
 #include "perfetto/ftrace_reader/format_parser.h"
 #include "src/ftrace_reader/event_info.h"
 #include "src/ftrace_reader/ftrace_procfs.h"
@@ -124,10 +125,6 @@
   return fields_end;
 }
 
-bool StartsWith(const std::string& str, const std::string& prefix) {
-  return str.compare(0, prefix.length(), prefix) == 0;
-}
-
 bool Contains(const std::string& haystack, const std::string& needle) {
   return haystack.find(needle) != std::string::npos;
 }
@@ -179,18 +176,18 @@
   }
 
   // Variable length strings: "char foo" + size: 0 (as in 'print').
-  if (StartsWith(type_and_name, "char ") && size == 0) {
+  if (base::StartsWith(type_and_name, "char ") && size == 0) {
     *out = kFtraceCString;
     return true;
   }
 
-  if (StartsWith(type_and_name, "bool ")) {
+  if (base::StartsWith(type_and_name, "bool ")) {
     *out = kFtraceBool;
     return true;
   }
 
-  if (StartsWith(type_and_name, "ino_t ") ||
-      StartsWith(type_and_name, "i_ino ")) {
+  if (base::StartsWith(type_and_name, "ino_t ") ||
+      base::StartsWith(type_and_name, "i_ino ")) {
     if (size == 4) {
       *out = kFtraceInode32;
       return true;
@@ -200,7 +197,7 @@
     }
   }
 
-  if (StartsWith(type_and_name, "dev_t ")) {
+  if (base::StartsWith(type_and_name, "dev_t ")) {
     if (size == 4) {
       *out = kFtraceDevId32;
       return true;
@@ -211,7 +208,7 @@
   }
 
   // Pids (as in 'sched_switch').
-  if (StartsWith(type_and_name, "pid_t ") && size == 4) {
+  if (base::StartsWith(type_and_name, "pid_t ") && size == 4) {
     *out = kFtracePid32;
     return true;
   }
diff --git a/src/traced/probes/BUILD.gn b/src/traced/probes/BUILD.gn
index 43b4d0e..5d0ae5d 100644
--- a/src/traced/probes/BUILD.gn
+++ b/src/traced/probes/BUILD.gn
@@ -50,7 +50,7 @@
     ":probes_src",
     "../../../gn:default_deps",
     "../../../gn:gtest_deps",
-    "../../tracing:unittests",
+    "../../tracing:test_support",
   ]
   sources = [
     "process_stats_data_source_unittest.cc",
diff --git a/src/traced/probes/probes_producer.cc b/src/traced/probes/probes_producer.cc
index 6131d44..be97d12 100644
--- a/src/traced/probes/probes_producer.cc
+++ b/src/traced/probes/probes_producer.cc
@@ -35,6 +35,7 @@
 
 #include "perfetto/trace/filesystem/inode_file_map.pbzero.h"
 #include "perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "perfetto/trace/ftrace/ftrace_stats.pbzero.h"
 #include "perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto {
@@ -313,13 +314,36 @@
 
 ProbesProducer::SinkDelegate::~SinkDelegate() = default;
 
+void ProbesProducer::SinkDelegate::OnCreate(FtraceSink* sink) {
+  sink->DumpFtraceStats(&stats_before_);
+}
+
 void ProbesProducer::SinkDelegate::Flush() {
   // TODO(primiano): this still doesn't flush data from the kernel ftrace
   // buffers (see b/73886018). We should do that and delay the
   // NotifyFlushComplete() until the ftrace data has been drained from the
   // kernel ftrace buffer and written in the SMB.
-  if (writer_ && (!trace_packet_ || trace_packet_->is_finalized()))
+  if (writer_ && (!trace_packet_ || trace_packet_->is_finalized())) {
+    WriteStats();
     writer_->Flush();
+  }
+}
+
+void ProbesProducer::SinkDelegate::WriteStats() {
+  {
+    auto before_packet = writer_->NewTracePacket();
+    auto out = before_packet->set_ftrace_stats();
+    out->set_phase(protos::pbzero::FtraceStats_Phase_START_OF_TRACE);
+    stats_before_.Write(out);
+  }
+  {
+    FtraceStats stats_after{};
+    sink_->DumpFtraceStats(&stats_after);
+    auto after_packet = writer_->NewTracePacket();
+    auto out = after_packet->set_ftrace_stats();
+    out->set_phase(protos::pbzero::FtraceStats_Phase_END_OF_TRACE);
+    stats_after.Write(out);
+  }
 }
 
 ProbesProducer::FtraceBundleHandle
diff --git a/src/traced/probes/probes_producer.h b/src/traced/probes/probes_producer.h
index 7d9d1e0..cefa582 100644
--- a/src/traced/probes/probes_producer.h
+++ b/src/traced/probes/probes_producer.h
@@ -70,6 +70,8 @@
  private:
   using FtraceBundleHandle =
       protozero::MessageHandle<protos::pbzero::FtraceEventBundle>;
+  using FtraceStatsHandle =
+      protozero::MessageHandle<protos::pbzero::FtraceStats>;
 
   class SinkDelegate : public FtraceSink::Delegate {
    public:
@@ -87,6 +89,9 @@
     void OnBundleComplete(size_t cpu,
                           FtraceBundleHandle bundle,
                           const FtraceMetadata& metadata) override;
+    void OnCreate(FtraceSink*) override;
+
+    void WriteStats();
 
     void set_sink(std::unique_ptr<FtraceSink> sink) { sink_ = std::move(sink); }
 
@@ -109,6 +114,7 @@
     base::TaskRunner* task_runner_;
     std::unique_ptr<FtraceSink> sink_ = nullptr;
     std::unique_ptr<TraceWriter> writer_;
+    FtraceStats stats_before_ = {};
 
     base::WeakPtr<ProcessStatsDataSource> ps_source_;
     base::WeakPtr<InodeFileDataSource> file_source_;
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index 41b0130..6b718a2 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -121,6 +121,7 @@
   testonly = true
   deps = [
     ":ipc",
+    ":test_support",
     ":tracing",
     "../../gn:default_deps",
     "../../gn:gtest_deps",
@@ -128,7 +129,6 @@
     "../../protos/perfetto/trace:zero",
     "../base",
     "../base:test_support",
-    "../protozero:test_support",
   ]
   sources = [
     "core/id_allocator_unittest.cc",
@@ -141,8 +141,6 @@
     "core/sliced_protobuf_input_stream_unittest.cc",
     "core/trace_buffer_unittest.cc",
     "core/trace_packet_unittest.cc",
-    "core/trace_writer_for_testing.cc",
-    "core/trace_writer_for_testing.h",
     "core/trace_writer_impl_unittest.cc",
     "ipc/posix_shared_memory_unittest.cc",
     "test/aligned_buffer_test.cc",
@@ -159,6 +157,19 @@
   ]
 }
 
+source_set("test_support") {
+  testonly = true
+  public_deps = [
+    "../../protos/perfetto/trace:lite",
+    "../../protos/perfetto/trace:zero",
+    "../protozero:test_support",
+  ]
+  sources = [
+    "core/trace_writer_for_testing.cc",
+    "core/trace_writer_for_testing.h",
+  ]
+}
+
 if (!build_with_chromium) {
   source_set("tracing_benchmarks") {
     testonly = true
diff --git a/tools/ftrace_proto_gen/main.cc b/tools/ftrace_proto_gen/main.cc
index a0b3a97..486ee0a 100644
--- a/tools/ftrace_proto_gen/main.cc
+++ b/tools/ftrace_proto_gen/main.cc
@@ -213,6 +213,7 @@
 ftrace_proto_names = [
   "ftrace_event.proto",
   "ftrace_event_bundle.proto",
+  "ftrace_stats.proto",
   "test_bundle_wrapper.proto",
 )";
     for (const perfetto::FtraceEventName& event : whitelist) {