service: Add GetTraceStats() to consumer endpoint

The new API allows a consumer to obtain trace and buffer stats about the
current tracing session separately from the actual trace data. Consumers
may use this to show stats such as buffer utilization in a UI.

Also adds a few new stats to TraceBuffer.

Change-Id: I929955b2854e5b84b6d1c0ca82e750c47df77ae1
diff --git a/Android.bp b/Android.bp
index ec143a5..8de77cf 100644
--- a/Android.bp
+++ b/Android.bp
@@ -94,6 +94,7 @@
     "src/tracing/core/trace_buffer.cc",
     "src/tracing/core/trace_config.cc",
     "src/tracing/core/trace_packet.cc",
+    "src/tracing/core/trace_stats.cc",
     "src/tracing/core/trace_writer_impl.cc",
     "src/tracing/core/tracing_service_impl.cc",
     "src/tracing/core/virtual_destructors.cc",
@@ -285,6 +286,7 @@
     "src/tracing/core/trace_buffer.cc",
     "src/tracing/core/trace_config.cc",
     "src/tracing/core/trace_packet.cc",
+    "src/tracing/core/trace_stats.cc",
     "src/tracing/core/trace_writer_impl.cc",
     "src/tracing/core/tracing_service_impl.cc",
     "src/tracing/core/virtual_destructors.cc",
@@ -429,6 +431,7 @@
     "src/tracing/core/trace_buffer.cc",
     "src/tracing/core/trace_config.cc",
     "src/tracing/core/trace_packet.cc",
+    "src/tracing/core/trace_stats.cc",
     "src/tracing/core/trace_writer_impl.cc",
     "src/tracing/core/tracing_service_impl.cc",
     "src/tracing/core/virtual_destructors.cc",
@@ -626,6 +629,7 @@
     "src/tracing/core/trace_buffer.cc",
     "src/tracing/core/trace_config.cc",
     "src/tracing/core/trace_packet.cc",
+    "src/tracing/core/trace_stats.cc",
     "src/tracing/core/trace_writer_impl.cc",
     "src/tracing/core/tracing_service_impl.cc",
     "src/tracing/core/virtual_destructors.cc",
@@ -697,6 +701,7 @@
     "protos/perfetto/common/android_log_constants.proto",
     "protos/perfetto/common/commit_data_request.proto",
     "protos/perfetto/common/sys_stats_counters.proto",
+    "protos/perfetto/common/trace_stats.proto",
   ],
   tools: [
     "aprotoc",
@@ -706,6 +711,7 @@
     "external/perfetto/protos/perfetto/common/android_log_constants.pb.cc",
     "external/perfetto/protos/perfetto/common/commit_data_request.pb.cc",
     "external/perfetto/protos/perfetto/common/sys_stats_counters.pb.cc",
+    "external/perfetto/protos/perfetto/common/trace_stats.pb.cc",
   ],
 }
 
@@ -716,6 +722,7 @@
     "protos/perfetto/common/android_log_constants.proto",
     "protos/perfetto/common/commit_data_request.proto",
     "protos/perfetto/common/sys_stats_counters.proto",
+    "protos/perfetto/common/trace_stats.proto",
   ],
   tools: [
     "aprotoc",
@@ -725,6 +732,7 @@
     "external/perfetto/protos/perfetto/common/android_log_constants.pb.h",
     "external/perfetto/protos/perfetto/common/commit_data_request.pb.h",
     "external/perfetto/protos/perfetto/common/sys_stats_counters.pb.h",
+    "external/perfetto/protos/perfetto/common/trace_stats.pb.h",
   ],
   export_include_dirs: [
     "protos",
@@ -738,6 +746,7 @@
     "protos/perfetto/common/android_log_constants.proto",
     "protos/perfetto/common/commit_data_request.proto",
     "protos/perfetto/common/sys_stats_counters.proto",
+    "protos/perfetto/common/trace_stats.proto",
   ],
   tools: [
     "aprotoc",
@@ -748,6 +757,7 @@
     "external/perfetto/protos/perfetto/common/android_log_constants.pbzero.cc",
     "external/perfetto/protos/perfetto/common/commit_data_request.pbzero.cc",
     "external/perfetto/protos/perfetto/common/sys_stats_counters.pbzero.cc",
+    "external/perfetto/protos/perfetto/common/trace_stats.pbzero.cc",
   ],
 }
 
@@ -758,6 +768,7 @@
     "protos/perfetto/common/android_log_constants.proto",
     "protos/perfetto/common/commit_data_request.proto",
     "protos/perfetto/common/sys_stats_counters.proto",
+    "protos/perfetto/common/trace_stats.proto",
   ],
   tools: [
     "aprotoc",
@@ -768,6 +779,7 @@
     "external/perfetto/protos/perfetto/common/android_log_constants.pbzero.h",
     "external/perfetto/protos/perfetto/common/commit_data_request.pbzero.h",
     "external/perfetto/protos/perfetto/common/sys_stats_counters.pbzero.h",
+    "external/perfetto/protos/perfetto/common/trace_stats.pbzero.h",
   ],
   export_include_dirs: [
     "protos",
@@ -1531,7 +1543,6 @@
   name: "perfetto_protos_perfetto_trace_minimal_lite_gen",
   srcs: [
     "protos/perfetto/trace/clock_snapshot.proto",
-    "protos/perfetto/trace/trace_stats.proto",
   ],
   tools: [
     "aprotoc",
@@ -1539,7 +1550,6 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/clock_snapshot.pb.cc",
-    "external/perfetto/protos/perfetto/trace/trace_stats.pb.cc",
   ],
 }
 
@@ -1548,7 +1558,6 @@
   name: "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
   srcs: [
     "protos/perfetto/trace/clock_snapshot.proto",
-    "protos/perfetto/trace/trace_stats.proto",
   ],
   tools: [
     "aprotoc",
@@ -1556,7 +1565,6 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/protos && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto/protos --proto_path=external/perfetto/protos $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/clock_snapshot.pb.h",
-    "external/perfetto/protos/perfetto/trace/trace_stats.pb.h",
   ],
   export_include_dirs: [
     "protos",
@@ -1884,7 +1892,6 @@
     "protos/perfetto/trace/test_event.proto",
     "protos/perfetto/trace/trace.proto",
     "protos/perfetto/trace/trace_packet.proto",
-    "protos/perfetto/trace/trace_stats.proto",
   ],
   tools: [
     "aprotoc",
@@ -1896,7 +1903,6 @@
     "external/perfetto/protos/perfetto/trace/test_event.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/trace.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/trace_packet.pbzero.cc",
-    "external/perfetto/protos/perfetto/trace/trace_stats.pbzero.cc",
   ],
 }
 
@@ -1908,7 +1914,6 @@
     "protos/perfetto/trace/test_event.proto",
     "protos/perfetto/trace/trace.proto",
     "protos/perfetto/trace/trace_packet.proto",
-    "protos/perfetto/trace/trace_stats.proto",
   ],
   tools: [
     "aprotoc",
@@ -1920,7 +1925,6 @@
     "external/perfetto/protos/perfetto/trace/test_event.pbzero.h",
     "external/perfetto/protos/perfetto/trace/trace.pbzero.h",
     "external/perfetto/protos/perfetto/trace/trace_packet.pbzero.h",
-    "external/perfetto/protos/perfetto/trace/trace_stats.pbzero.h",
   ],
   export_include_dirs: [
     "protos",
@@ -2349,6 +2353,7 @@
     "src/tracing/core/trace_buffer.cc",
     "src/tracing/core/trace_config.cc",
     "src/tracing/core/trace_packet.cc",
+    "src/tracing/core/trace_stats.cc",
     "src/tracing/core/trace_writer_impl.cc",
     "src/tracing/core/tracing_service_impl.cc",
     "src/tracing/core/virtual_destructors.cc",
@@ -2684,6 +2689,7 @@
     "src/tracing/core/trace_config.cc",
     "src/tracing/core/trace_packet.cc",
     "src/tracing/core/trace_packet_unittest.cc",
+    "src/tracing/core/trace_stats.cc",
     "src/tracing/core/trace_writer_for_testing.cc",
     "src/tracing/core/trace_writer_impl.cc",
     "src/tracing/core/trace_writer_impl_unittest.cc",
diff --git a/include/perfetto/tracing/core/BUILD.gn b/include/perfetto/tracing/core/BUILD.gn
index b09026c..c4e7eed 100644
--- a/include/perfetto/tracing/core/BUILD.gn
+++ b/include/perfetto/tracing/core/BUILD.gn
@@ -30,6 +30,7 @@
     "startup_trace_writer.h",
     "trace_config.h",
     "trace_packet.h",
+    "trace_stats.h",
     "trace_writer.h",
     "tracing_service.h",
   ]
diff --git a/include/perfetto/tracing/core/consumer.h b/include/perfetto/tracing/core/consumer.h
index e6a2614..6e5e779 100644
--- a/include/perfetto/tracing/core/consumer.h
+++ b/include/perfetto/tracing/core/consumer.h
@@ -27,6 +27,7 @@
 
 class TraceConfig;
 class TracePacket;
+class TraceStats;
 
 class PERFETTO_EXPORT Consumer {
  public:
@@ -68,6 +69,10 @@
   // Called back by the Service (or transport layer) after invoking
   // TracingService::ConsumerEndpoint::Attach().
   virtual void OnAttach(bool success, const TraceConfig&) = 0;
+
+  // Called back by the Service (or transport layer) after invoking
+  // TracingService::ConsumerEndpoint::GetTraceStats().
+  virtual void OnTraceStats(bool success, const TraceStats&) = 0;
 };
 
 }  // namespace perfetto
diff --git a/include/perfetto/tracing/core/trace_stats.h b/include/perfetto/tracing/core/trace_stats.h
new file mode 100644
index 0000000..5d02163
--- /dev/null
+++ b/include/perfetto/tracing/core/trace_stats.h
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+/*******************************************************************************
+ * AUTOGENERATED - DO NOT EDIT
+ *******************************************************************************
+ * This file has been generated from the protobuf message
+ * perfetto/common/trace_stats.proto
+ * by
+ * ../../tools/proto_to_cpp/proto_to_cpp.cc.
+ * If you need to make changes here, change the .proto file and then run
+ * ./tools/gen_tracing_cpp_headers_from_protos
+ */
+
+#ifndef INCLUDE_PERFETTO_TRACING_CORE_TRACE_STATS_H_
+#define INCLUDE_PERFETTO_TRACING_CORE_TRACE_STATS_H_
+
+#include <stdint.h>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "perfetto/base/export.h"
+
+// Forward declarations for protobuf types.
+namespace perfetto {
+namespace protos {
+class TraceStats;
+class TraceStats_BufferStats;
+}  // namespace protos
+}  // namespace perfetto
+
+namespace perfetto {
+
+class PERFETTO_EXPORT TraceStats {
+ public:
+  class PERFETTO_EXPORT BufferStats {
+   public:
+    BufferStats();
+    ~BufferStats();
+    BufferStats(BufferStats&&) noexcept;
+    BufferStats& operator=(BufferStats&&);
+    BufferStats(const BufferStats&);
+    BufferStats& operator=(const BufferStats&);
+
+    // Conversion methods from/to the corresponding protobuf types.
+    void FromProto(const perfetto::protos::TraceStats_BufferStats&);
+    void ToProto(perfetto::protos::TraceStats_BufferStats*) const;
+
+    uint64_t buffer_size() const { return buffer_size_; }
+    void set_buffer_size(uint64_t value) { buffer_size_ = value; }
+
+    uint64_t bytes_written() const { return bytes_written_; }
+    void set_bytes_written(uint64_t value) { bytes_written_ = value; }
+
+    uint64_t bytes_overwritten() const { return bytes_overwritten_; }
+    void set_bytes_overwritten(uint64_t value) { bytes_overwritten_ = value; }
+
+    uint64_t bytes_read() const { return bytes_read_; }
+    void set_bytes_read(uint64_t value) { bytes_read_ = value; }
+
+    uint64_t padding_bytes_written() const { return padding_bytes_written_; }
+    void set_padding_bytes_written(uint64_t value) {
+      padding_bytes_written_ = value;
+    }
+
+    uint64_t padding_bytes_cleared() const { return padding_bytes_cleared_; }
+    void set_padding_bytes_cleared(uint64_t value) {
+      padding_bytes_cleared_ = value;
+    }
+
+    uint64_t chunks_written() const { return chunks_written_; }
+    void set_chunks_written(uint64_t value) { chunks_written_ = value; }
+
+    uint64_t chunks_rewritten() const { return chunks_rewritten_; }
+    void set_chunks_rewritten(uint64_t value) { chunks_rewritten_ = value; }
+
+    uint64_t chunks_overwritten() const { return chunks_overwritten_; }
+    void set_chunks_overwritten(uint64_t value) { chunks_overwritten_ = value; }
+
+    uint64_t chunks_read() const { return chunks_read_; }
+    void set_chunks_read(uint64_t value) { chunks_read_ = value; }
+
+    uint64_t chunks_committed_out_of_order() const {
+      return chunks_committed_out_of_order_;
+    }
+    void set_chunks_committed_out_of_order(uint64_t value) {
+      chunks_committed_out_of_order_ = value;
+    }
+
+    uint64_t write_wrap_count() const { return write_wrap_count_; }
+    void set_write_wrap_count(uint64_t value) { write_wrap_count_ = value; }
+
+    uint64_t patches_succeeded() const { return patches_succeeded_; }
+    void set_patches_succeeded(uint64_t value) { patches_succeeded_ = value; }
+
+    uint64_t patches_failed() const { return patches_failed_; }
+    void set_patches_failed(uint64_t value) { patches_failed_ = value; }
+
+    uint64_t readaheads_succeeded() const { return readaheads_succeeded_; }
+    void set_readaheads_succeeded(uint64_t value) {
+      readaheads_succeeded_ = value;
+    }
+
+    uint64_t readaheads_failed() const { return readaheads_failed_; }
+    void set_readaheads_failed(uint64_t value) { readaheads_failed_ = value; }
+
+    uint64_t abi_violations() const { return abi_violations_; }
+    void set_abi_violations(uint64_t value) { abi_violations_ = value; }
+
+   private:
+    uint64_t buffer_size_ = {};
+    uint64_t bytes_written_ = {};
+    uint64_t bytes_overwritten_ = {};
+    uint64_t bytes_read_ = {};
+    uint64_t padding_bytes_written_ = {};
+    uint64_t padding_bytes_cleared_ = {};
+    uint64_t chunks_written_ = {};
+    uint64_t chunks_rewritten_ = {};
+    uint64_t chunks_overwritten_ = {};
+    uint64_t chunks_read_ = {};
+    uint64_t chunks_committed_out_of_order_ = {};
+    uint64_t write_wrap_count_ = {};
+    uint64_t patches_succeeded_ = {};
+    uint64_t patches_failed_ = {};
+    uint64_t readaheads_succeeded_ = {};
+    uint64_t readaheads_failed_ = {};
+    uint64_t abi_violations_ = {};
+
+    // Allows to preserve unknown protobuf fields for compatibility
+    // with future versions of .proto files.
+    std::string unknown_fields_;
+  };
+
+  TraceStats();
+  ~TraceStats();
+  TraceStats(TraceStats&&) noexcept;
+  TraceStats& operator=(TraceStats&&);
+  TraceStats(const TraceStats&);
+  TraceStats& operator=(const TraceStats&);
+
+  // Conversion methods from/to the corresponding protobuf types.
+  void FromProto(const perfetto::protos::TraceStats&);
+  void ToProto(perfetto::protos::TraceStats*) const;
+
+  int buffer_stats_size() const {
+    return static_cast<int>(buffer_stats_.size());
+  }
+  const std::vector<BufferStats>& buffer_stats() const { return buffer_stats_; }
+  BufferStats* add_buffer_stats() {
+    buffer_stats_.emplace_back();
+    return &buffer_stats_.back();
+  }
+
+  uint32_t producers_connected() const { return producers_connected_; }
+  void set_producers_connected(uint32_t value) { producers_connected_ = value; }
+
+  uint64_t producers_seen() const { return producers_seen_; }
+  void set_producers_seen(uint64_t value) { producers_seen_ = value; }
+
+  uint32_t data_sources_registered() const { return data_sources_registered_; }
+  void set_data_sources_registered(uint32_t value) {
+    data_sources_registered_ = value;
+  }
+
+  uint64_t data_sources_seen() const { return data_sources_seen_; }
+  void set_data_sources_seen(uint64_t value) { data_sources_seen_ = value; }
+
+  uint32_t tracing_sessions() const { return tracing_sessions_; }
+  void set_tracing_sessions(uint32_t value) { tracing_sessions_ = value; }
+
+  uint32_t total_buffers() const { return total_buffers_; }
+  void set_total_buffers(uint32_t value) { total_buffers_ = value; }
+
+ private:
+  std::vector<BufferStats> buffer_stats_;
+  uint32_t producers_connected_ = {};
+  uint64_t producers_seen_ = {};
+  uint32_t data_sources_registered_ = {};
+  uint64_t data_sources_seen_ = {};
+  uint32_t tracing_sessions_ = {};
+  uint32_t total_buffers_ = {};
+
+  // Allows to preserve unknown protobuf fields for compatibility
+  // with future versions of .proto files.
+  std::string unknown_fields_;
+};
+
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACING_CORE_TRACE_STATS_H_
diff --git a/include/perfetto/tracing/core/tracing_service.h b/include/perfetto/tracing/core/tracing_service.h
index c4f369e..b80f2a8 100644
--- a/include/perfetto/tracing/core/tracing_service.h
+++ b/include/perfetto/tracing/core/tracing_service.h
@@ -167,6 +167,9 @@
 
     // Will call OnAttach().
     virtual void Attach(const std::string& key) = 0;
+
+    // Will call OnTraceStats().
+    virtual void GetTraceStats() = 0;
   };  // class ConsumerEndpoint.
 
   // Implemented in src/core/tracing_service_impl.cc .
diff --git a/protos/perfetto/common/BUILD.gn b/protos/perfetto/common/BUILD.gn
index e2b22b8..fb34919 100644
--- a/protos/perfetto/common/BUILD.gn
+++ b/protos/perfetto/common/BUILD.gn
@@ -20,6 +20,7 @@
   "commit_data_request.proto",
   "android_log_constants.proto",
   "sys_stats_counters.proto",
+  "trace_stats.proto",
 ]
 
 # Proto messages that are required by the IPC service definitions but have also
diff --git a/protos/perfetto/trace/trace_stats.proto b/protos/perfetto/common/trace_stats.proto
similarity index 63%
rename from protos/perfetto/trace/trace_stats.proto
rename to protos/perfetto/common/trace_stats.proto
index cd69e2b..5993e3e 100644
--- a/protos/perfetto/trace/trace_stats.proto
+++ b/protos/perfetto/common/trace_stats.proto
@@ -23,11 +23,45 @@
 message TraceStats {
   // From TraceBuffer::Stats.
   //
-  // NEXT ID: 12
+  // Next id: 18.
   message BufferStats {
-    // Num. bytes written into the circular buffer.
+    // Size of the circular buffer in bytes.
+    optional uint64 buffer_size = 12;
+
+    // Num. bytes written into the circular buffer, including chunk headers.
     optional uint64 bytes_written = 1;
 
+    // Num. bytes overwritten before they have been read (i.e. loss of data).
+    optional uint64 bytes_overwritten = 13;
+
+    // Total size of chunks that were fully read from the circular buffer by the
+    // consumer. This may not be equal to |bytes_written| either in the middle
+    // of tracing, or if |chunks_overwritten| is non-zero. Note that this is the
+    // size of the chunks read from the buffer, including chunk headers, which
+    // will be different from the total size of packets returned to the
+    // consumer.
+    //
+    // The current utilization of the trace buffer (mid-tracing) can be obtained
+    // by subtracting |bytes_read| and |bytes_overwritten| from |bytes_written|,
+    // adding the difference of |padding_bytes_written| and
+    // |padding_bytes_cleared|, and comparing this sum to the |buffer_size|.
+    // Note that this represents the total size of buffered data in the buffer,
+    // yet this data may be spread non-contiguously through the buffer and may
+    // be overridden before the utilization reaches 100%.
+    optional uint64 bytes_read = 14;
+
+    // Num. bytes that were allocated as padding between chunks in the circular
+    // buffer.
+    optional uint64 padding_bytes_written = 15;
+
+    // Num. of padding bytes that were removed from the circular buffer when
+    // they were overwritten.
+    //
+    // The difference between |padding_bytes_written| and
+    // |padding_bytes_cleared| denotes the total size of padding currently
+    // present in the buffer.
+    optional uint64 padding_bytes_cleared = 16;
+
     // Num. chunks (!= packets) written into the buffer.
     optional uint64 chunks_written = 2;
 
@@ -38,6 +72,11 @@
     // Num. chunks overwritten before they have been read (i.e. loss of data).
     optional uint64 chunks_overwritten = 3;
 
+    // Num. chunks (!= packets) that were fully read from the circular buffer by
+    // the consumer. This may not be equal to |chunks_written| either in the
+    // middle of tracing, or if |chunks_overwritten| is non-zero.
+    optional uint64 chunks_read = 17;
+
     // Num. chunks that were committed out of order.
     optional uint64 chunks_committed_out_of_order = 11;
 
@@ -81,7 +120,7 @@
   // Num. data sources registered for all trace sessions.
   optional uint32 data_sources_registered = 4;
 
-  // Num. data sources ever seen for all trace sessions since startupb
+  // Num. data sources ever seen for all trace sessions since startup.
   optional uint64 data_sources_seen = 5;
 
   // Num. concurrently active tracing sessions.
diff --git a/protos/perfetto/ipc/consumer_port.proto b/protos/perfetto/ipc/consumer_port.proto
index 944090e..6e7affc 100644
--- a/protos/perfetto/ipc/consumer_port.proto
+++ b/protos/perfetto/ipc/consumer_port.proto
@@ -17,6 +17,7 @@
 syntax = "proto2";
 option optimize_for = LITE_RUNTIME;
 
+import "perfetto/common/trace_stats.proto";
 import "perfetto/config/trace_config.proto";
 
 package perfetto.protos;
@@ -85,6 +86,10 @@
   // in the standard non-detached case.
   rpc Attach(AttachRequest) returns (AttachResponse) {}
 
+  // Allows the consumer to obtain statistics about the current tracing session,
+  // such as buffer usage stats. Intended for debugging or UI use.
+  rpc GetTraceStats(GetTraceStatsRequest) returns (GetTraceStatsResponse) {}
+
   // TODO rpc ListDataSources(), for the UI.
 }
 
@@ -161,12 +166,22 @@
 message DetachRequest {
   optional string key = 1;
 }
+
 message DetachResponse {}
 
 // Arguments for rpc Attach.
 message AttachRequest {
   optional string key = 1;
 }
+
 message AttachResponse {
   optional protos.TraceConfig trace_config = 1;
 }
+
+// Arguments for rpc GetTraceStats.
+
+message GetTraceStatsRequest {}
+
+message GetTraceStatsResponse {
+  optional TraceStats trace_stats = 1;
+}
diff --git a/protos/perfetto/trace/BUILD.gn b/protos/perfetto/trace/BUILD.gn
index ad3a35b..3dbef21 100644
--- a/protos/perfetto/trace/BUILD.gn
+++ b/protos/perfetto/trace/BUILD.gn
@@ -18,10 +18,7 @@
 
 # Common protos used by both the ":minimal_lite" target (for the service) and
 # the generic ":lite" target
-proto_sources_minimal = [
-  "clock_snapshot.proto",
-  "trace_stats.proto",
-]
+proto_sources_minimal = [ "clock_snapshot.proto" ]
 
 proto_sources_trusted = [ "trusted_packet.proto" ]
 
diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto
index 6a118d1..9a25982 100644
--- a/protos/perfetto/trace/trace_packet.proto
+++ b/protos/perfetto/trace/trace_packet.proto
@@ -17,6 +17,7 @@
 syntax = "proto2";
 option optimize_for = LITE_RUNTIME;
 
+import "perfetto/common/trace_stats.proto";
 import "perfetto/config/trace_config.proto";
 import "perfetto/trace/android/android_log.proto";
 import "perfetto/trace/chrome/chrome_trace_event.proto";
@@ -30,7 +31,6 @@
 import "perfetto/trace/ps/process_tree.proto";
 import "perfetto/trace/sys_stats/sys_stats.proto";
 import "perfetto/trace/test_event.proto";
-import "perfetto/trace/trace_stats.proto";
 
 package perfetto.protos;
 
diff --git a/protos/perfetto/trace/trusted_packet.proto b/protos/perfetto/trace/trusted_packet.proto
index 758b3ca..84c6a52 100644
--- a/protos/perfetto/trace/trusted_packet.proto
+++ b/protos/perfetto/trace/trusted_packet.proto
@@ -26,9 +26,9 @@
 syntax = "proto3";
 option optimize_for = LITE_RUNTIME;
 
+import "perfetto/common/trace_stats.proto";
 import "perfetto/config/trace_config.proto";
 import "perfetto/trace/clock_snapshot.proto";
-import "perfetto/trace/trace_stats.proto";
 
 package perfetto.protos;
 
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index ede5c5b..bf890b7 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -706,6 +706,11 @@
   }
 }
 
+void PerfettoCmd::OnTraceStats(bool /*success*/,
+                               const TraceStats& /*trace_config*/) {
+  // TODO(eseckler): Support GetTraceStats().
+}
+
 int __attribute__((visibility("default")))
 PerfettoCmdMain(int argc, char** argv) {
   g_consumer_cmd = new perfetto::PerfettoCmd();
diff --git a/src/perfetto_cmd/perfetto_cmd.h b/src/perfetto_cmd/perfetto_cmd.h
index 943f204..8c6fbc7 100644
--- a/src/perfetto_cmd/perfetto_cmd.h
+++ b/src/perfetto_cmd/perfetto_cmd.h
@@ -60,6 +60,7 @@
   void OnTraceData(std::vector<TracePacket>, bool has_more) override;
   void OnDetach(bool) override;
   void OnAttach(bool, const TraceConfig&) override;
+  void OnTraceStats(bool, const TraceStats&) override;
 
   void SignalCtrlC() { ctrl_c_evt_.Notify(); }
 
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index ae49d44..4cf124e 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -61,6 +61,7 @@
     "core/trace_buffer.h",
     "core/trace_config.cc",
     "core/trace_packet.cc",
+    "core/trace_stats.cc",
     "core/trace_writer_impl.cc",
     "core/trace_writer_impl.h",
     "core/tracing_service_impl.cc",
diff --git a/src/tracing/api_impl/consumer_api.cc b/src/tracing/api_impl/consumer_api.cc
index 438f00d..544926e 100644
--- a/src/tracing/api_impl/consumer_api.cc
+++ b/src/tracing/api_impl/consumer_api.cc
@@ -91,6 +91,7 @@
   void OnTraceData(std::vector<TracePacket>, bool has_more) override;
   void OnDetach(bool) override;
   void OnAttach(bool, const TraceConfig&) override;
+  void OnTraceStats(bool, const TraceStats&) override;
 
  private:
   TracingSession(const TracingSession&) = delete;
@@ -235,6 +236,11 @@
   PERFETTO_DCHECK(false);  // Should never be called, Attach() is not used here.
 }
 
+void TracingSession::OnTraceStats(bool, const TraceStats&) {
+  // Should never be called, GetTraceStats() is not used here.
+  PERFETTO_DCHECK(false);
+}
+
 void TracingSession::DestroyConnection() {
   // Destroys the connection in a separate task. This is to avoid destroying
   // the IPC connection directly from within the IPC callback.
diff --git a/src/tracing/core/service_impl_unittest.cc b/src/tracing/core/service_impl_unittest.cc
index 2a23b15..1cf8ff6 100644
--- a/src/tracing/core/service_impl_unittest.cc
+++ b/src/tracing/core/service_impl_unittest.cc
@@ -1374,4 +1374,33 @@
   consumer->WaitForTracingDisabled(5000);
 }
 
+TEST_F(TracingServiceImplTest, GetTraceStats) {
+  std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+  consumer->Connect(svc.get());
+
+  consumer->GetTraceStats();
+  consumer->WaitForTraceStats(false);
+
+  std::unique_ptr<MockProducer> producer = CreateMockProducer();
+  producer->Connect(svc.get(), "mock_producer");
+  producer->RegisterDataSource("data_source");
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(128);
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("data_source");
+
+  consumer->EnableTracing(trace_config);
+  producer->WaitForTracingSetup();
+  producer->WaitForDataSourceSetup("data_source");
+  producer->WaitForDataSourceStart("data_source");
+
+  consumer->GetTraceStats();
+  consumer->WaitForTraceStats(true);
+
+  consumer->DisableTracing();
+  producer->WaitForDataSourceStop("data_source");
+  consumer->WaitForTracingDisabled();
+}
+
 }  // namespace perfetto
diff --git a/src/tracing/core/trace_buffer.cc b/src/tracing/core/trace_buffer.cc
index 4d990e1..5dfc62b 100644
--- a/src/tracing/core/trace_buffer.cc
+++ b/src/tracing/core/trace_buffer.cc
@@ -90,6 +90,7 @@
     return false;
   }
   size_ = size;
+  stats_.set_buffer_size(size);
   max_chunk_size_ = std::min(size, ChunkRecord::kMaxSize);
   wptr_ = begin();
   index_.clear();
@@ -115,7 +116,7 @@
   const size_t record_size =
       base::AlignUp<sizeof(ChunkRecord)>(size + sizeof(ChunkRecord));
   if (PERFETTO_UNLIKELY(record_size > max_chunk_size_)) {
-    stats_.abi_violations++;
+    stats_.set_abi_violations(stats_.abi_violations() + 1);
     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
     return;
   }
@@ -162,7 +163,7 @@
                           prev->size != record_size ||
                           prev->num_fragments > num_fragments ||
                           (prev->flags & chunk_flags) != prev->flags)) {
-      stats_.abi_violations++;
+      stats_.set_abi_violations(stats_.abi_violations() + 1);
       PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
       return;
     }
@@ -179,7 +180,7 @@
     const auto subsequent_it = index_.find(subsequent_key);
     if (subsequent_it != index_.end() &&
         subsequent_it->second.num_fragments_read > 0) {
-      stats_.abi_violations++;
+      stats_.set_abi_violations(stats_.abi_violations() + 1);
       PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
       return;
     }
@@ -217,7 +218,7 @@
                       wptr - begin() + record_size, record_size);
     WriteChunkRecord(wptr, record, src, size);
     TRACE_BUFFER_DLOG("Chunk raw: %s", HexDump(wptr, record_size).c_str());
-    stats_.chunks_rewritten++;
+    stats_.set_chunks_rewritten(stats_.chunks_rewritten() + 1);
     return;
   }
 
@@ -229,7 +230,7 @@
     PERFETTO_DCHECK(res <= cached_size_to_end);
     AddPaddingRecord(cached_size_to_end);
     wptr_ = begin();
-    stats_.write_wrap_count++;
+    stats_.set_write_wrap_count(stats_.write_wrap_count() + 1);
     PERFETTO_DCHECK(size_to_end() >= record_size);
   }
 
@@ -256,8 +257,8 @@
   size_t padding_size = DeleteNextChunksFor(record_size);
 
   // Now first insert the new chunk. At the end, if necessary, add the padding.
-  stats_.chunks_written++;
-  stats_.bytes_written += size;
+  stats_.set_chunks_written(stats_.chunks_written() + 1);
+  stats_.set_bytes_written(stats_.bytes_written() + record_size);
   auto it_and_inserted = index_.emplace(
       key, ChunkMeta(GetChunkRecordAt(wptr_), num_fragments, chunk_complete,
                      chunk_flags, producer_uid_trusted));
@@ -270,7 +271,7 @@
   if (wptr_ >= end()) {
     PERFETTO_DCHECK(padding_size == 0);
     wptr_ = begin();
-    stats_.write_wrap_count++;
+    stats_.set_write_wrap_count(stats_.write_wrap_count() + 1);
   }
   DcheckIsAlignedAndWithinBounds(wptr_);
 
@@ -293,7 +294,8 @@
   if (chunk_id - last_chunk_id < kMaxChunkID / 2) {
     last_chunk_id = chunk_id;
   } else {
-    stats_.chunks_committed_out_of_order++;
+    stats_.set_chunks_committed_out_of_order(
+        stats_.chunks_committed_out_of_order() + 1);
   }
 
   if (padding_size)
@@ -335,8 +337,11 @@
       bool removed = false;
       if (PERFETTO_LIKELY(it != index_.end())) {
         const ChunkMeta& meta = it->second;
-        if (PERFETTO_UNLIKELY(meta.num_fragments_read < meta.num_fragments))
-          stats_.chunks_overwritten++;
+        if (PERFETTO_UNLIKELY(meta.num_fragments_read < meta.num_fragments)) {
+          stats_.set_chunks_overwritten(stats_.chunks_overwritten() + 1);
+          stats_.set_bytes_overwritten(stats_.bytes_overwritten() +
+                                       next_chunk.size);
+        }
         index_.erase(it);
         removed = true;
       }
@@ -346,6 +351,9 @@
                         next_chunk_ptr - begin(),
                         next_chunk_ptr - begin() + next_chunk.size, removed);
       PERFETTO_DCHECK(removed);
+    } else {
+      stats_.set_padding_bytes_cleared(stats_.padding_bytes_cleared() +
+                                       next_chunk.size);
     }
 
     next_chunk_ptr += next_chunk.size;
@@ -367,6 +375,7 @@
   TRACE_BUFFER_DLOG("AddPaddingRecord @ [%lu - %lu] %zu", wptr_ - begin(),
                     wptr_ - begin() + size, size);
   WriteChunkRecord(wptr_, record, nullptr, size - sizeof(ChunkRecord));
+  stats_.set_padding_bytes_written(stats_.padding_bytes_written() + size);
   // |wptr_| is deliberately not advanced when writing a padding record.
 }
 
@@ -379,7 +388,7 @@
   ChunkMeta::Key key(producer_id, writer_id, chunk_id);
   auto it = index_.find(key);
   if (it == index_.end()) {
-    stats_.patches_failed++;
+    stats_.set_patches_failed(stats_.patches_failed() + 1);
     return false;
   }
   ChunkMeta& chunk_meta = it->second;
@@ -409,7 +418,7 @@
         ptr > chunk_end - Patch::kSize) {
       // Either the IPC was so slow and in the meantime the writer managed to
       // wrap over |chunk_id| or the producer sent a malicious IPC.
-      stats_.patches_failed++;
+      stats_.set_patches_failed(stats_.patches_failed() + 1);
       return false;
     }
 
@@ -425,7 +434,7 @@
       "Chunk raw (after patch): %s",
       HexDump(chunk_begin, chunk_meta.chunk_record->size).c_str());
 
-  stats_.patches_succeeded += patches_size;
+  stats_.set_patches_succeeded(stats_.patches_succeeded() + patches_size);
   if (!other_patches_pending) {
     chunk_meta.flags &= ~kChunkNeedsPatching;
     chunk_meta.chunk_record->flags = chunk_meta.flags;
@@ -617,7 +626,7 @@
         // In extremely rare cases (producer bugged / malicious) the chunk might
         // contain an invalid fragment. In such case we don't want to stall the
         // sequence but just skip the chunk and move on.
-        stats_.abi_violations++;
+        stats_.set_abi_violations(stats_.abi_violations() + 1);
         PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
         break;
       }
@@ -625,7 +634,7 @@
       PERFETTO_DCHECK(action == kTryReadAhead);
       ReadAheadResult ra_res = ReadAhead(packet);
       if (ra_res == ReadAheadResult::kSucceededReturnSlices) {
-        stats_.readaheads_succeeded++;
+        stats_.set_readaheads_succeeded(stats_.readaheads_succeeded() + 1);
         *producer_uid = trusted_uid;
         return true;
       }
@@ -633,7 +642,7 @@
       if (ra_res == ReadAheadResult::kFailedMoveToNextSequence) {
         // readahead didn't find a contigous packet sequence. We'll try again
         // on the next ReadPacket() call.
-        stats_.readaheads_failed++;
+        stats_.set_readaheads_failed(stats_.readaheads_failed() + 1);
 
         // TODO(primiano): optimization: this MoveToEnd() is the reason why
         // MoveNext() (that is called in the outer for(;;MoveNext)) needs to
@@ -723,7 +732,7 @@
     PERFETTO_DCHECK(read_iter_.cur == it.cur);
 
     if (PERFETTO_UNLIKELY(packet_corruption)) {
-      stats_.abi_violations++;
+      stats_.set_abi_violations(stats_.abi_violations() + 1);
       PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
       *packet = TracePacket();  // clear.
       return ReadAheadResult::kFailedStayOnSameSequence;
@@ -749,7 +758,7 @@
                         packet_begin >= record_end)) {
     // The producer has a bug or is malicious and did declare that the chunk
     // contains more packets beyond its boundaries.
-    stats_.abi_violations++;
+    stats_.set_abi_violations(stats_.abi_violations() + 1);
     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
     return false;
   }
@@ -767,18 +776,30 @@
   const uint8_t* next_packet = packet_data + packet_size;
   if (PERFETTO_UNLIKELY(next_packet <= packet_begin ||
                         next_packet > record_end)) {
-    stats_.abi_violations++;
+    stats_.set_abi_violations(stats_.abi_violations() + 1);
     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
     chunk_meta->cur_fragment_offset = 0;
     chunk_meta->num_fragments_read = chunk_meta->num_fragments;
+    if (PERFETTO_LIKELY(chunk_meta->is_complete)) {
+      stats_.set_chunks_read(stats_.chunks_read() + 1);
+      stats_.set_bytes_read(stats_.bytes_read() +
+                            chunk_meta->chunk_record->size);
+    }
     return false;
   }
   chunk_meta->cur_fragment_offset =
       static_cast<uint16_t>(next_packet - packets_begin);
   chunk_meta->num_fragments_read++;
 
+  if (PERFETTO_UNLIKELY(chunk_meta->num_fragments_read ==
+                            chunk_meta->num_fragments &&
+                        chunk_meta->is_complete)) {
+    stats_.set_chunks_read(stats_.chunks_read() + 1);
+    stats_.set_bytes_read(stats_.bytes_read() + chunk_meta->chunk_record->size);
+  }
+
   if (PERFETTO_UNLIKELY(packet_size == 0)) {
-    stats_.abi_violations++;
+    stats_.set_abi_violations(stats_.abi_violations() + 1);
     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
     return false;
   }
diff --git a/src/tracing/core/trace_buffer.h b/src/tracing/core/trace_buffer.h
index 7c33f13..8b17e1c 100644
--- a/src/tracing/core/trace_buffer.h
+++ b/src/tracing/core/trace_buffer.h
@@ -29,6 +29,7 @@
 #include "perfetto/base/paged_memory.h"
 #include "perfetto/tracing/core/basic_types.h"
 #include "perfetto/tracing/core/slice.h"
+#include "perfetto/tracing/core/trace_stats.h"
 
 namespace perfetto {
 
@@ -128,22 +129,6 @@
  public:
   static const size_t InlineChunkHeaderSize;  // For test/fake_packet.{cc,h}.
 
-  // Maintain these fields consistent with trace_stats.proto. See comments in
-  // the .proto for the semantic of these fields.
-  struct Stats {
-    uint64_t bytes_written = 0;
-    uint64_t chunks_written = 0;
-    uint64_t chunks_rewritten = 0;
-    uint64_t chunks_overwritten = 0;
-    uint64_t chunks_committed_out_of_order = 0;
-    uint64_t write_wrap_count = 0;
-    uint64_t patches_succeeded = 0;
-    uint64_t patches_failed = 0;
-    uint64_t readaheads_succeeded = 0;
-    uint64_t readaheads_failed = 0;
-    uint64_t abi_violations = 0;
-  };
-
   // Argument for out-of-band patches applied through TryPatchChunkContents().
   struct Patch {
     // From SharedMemoryABI::kPacketHeaderSize.
@@ -232,7 +217,7 @@
   //   P1, P5, P7, P4 (P4 cannot come after P5)
   bool ReadNextTracePacket(TracePacket*, uid_t* producer_uid);
 
-  const Stats& stats() const { return stats_; }
+  const TraceStats::BufferStats& stats() const { return stats_; }
   size_t size() const { return size_; }
 
  private:
@@ -559,7 +544,7 @@
   std::map<std::pair<ProducerID, WriterID>, ChunkID> last_chunk_id_written_;
 
   // Statistics about buffer usage.
-  Stats stats_;
+  TraceStats::BufferStats stats_;
 
 #if PERFETTO_DCHECK_IS_ON()
   bool changed_since_last_read_ = false;
diff --git a/src/tracing/core/trace_buffer_unittest.cc b/src/tracing/core/trace_buffer_unittest.cc
index 9f1c7a8..d6eef84 100644
--- a/src/tracing/core/trace_buffer_unittest.cc
+++ b/src/tracing/core/trace_buffer_unittest.cc
@@ -159,6 +159,14 @@
     trace_buffer()->BeginRead();
     ASSERT_THAT(ReadPacket(), ElementsAre(FakePacketFragment(42, seed)));
     ASSERT_THAT(ReadPacket(), IsEmpty());
+    EXPECT_EQ(chunk_id + 1u, trace_buffer()->stats().chunks_written());
+    EXPECT_EQ(trace_buffer()->stats().chunks_written(),
+              trace_buffer()->stats().chunks_read());
+    EXPECT_LT(0u, trace_buffer()->stats().bytes_written());
+    EXPECT_EQ(trace_buffer()->stats().bytes_written(),
+              trace_buffer()->stats().bytes_read());
+    EXPECT_EQ(0u, trace_buffer()->stats().padding_bytes_written());
+    EXPECT_EQ(0u, trace_buffer()->stats().padding_bytes_cleared());
   }
 }
 
@@ -250,6 +258,21 @@
   ASSERT_THAT(ReadPacket(), ElementsAre(FakePacketFragment(2048 - 16, 'e')));
   ASSERT_THAT(ReadPacket(), ElementsAre(FakePacketFragment(512 - 16, 'f')));
   ASSERT_THAT(ReadPacket(), IsEmpty());
+
+  EXPECT_EQ(6u, trace_buffer()->stats().chunks_written());
+  EXPECT_EQ(3u, trace_buffer()->stats().chunks_overwritten());
+  EXPECT_EQ(3u, trace_buffer()->stats().chunks_read());
+  EXPECT_EQ(4480u, trace_buffer()->stats().bytes_written());
+  EXPECT_EQ(896u, trace_buffer()->stats().bytes_overwritten());
+  EXPECT_EQ(3584u, trace_buffer()->stats().bytes_read());
+  EXPECT_EQ(512u, trace_buffer()->stats().padding_bytes_written());
+  EXPECT_EQ(0u, trace_buffer()->stats().padding_bytes_cleared());
+
+  // Adding another chunk should clear some of the padding.
+  ASSERT_EQ(128u, CreateChunk(ProducerID(1), WriterID(1), ChunkID(6))
+                      .AddPacket(128 - 16, 'g')
+                      .CopyIntoTraceBuffer());
+  EXPECT_EQ(384u, trace_buffer()->stats().padding_bytes_cleared());
 }
 
 // Like ReadWrite_Padding, but this time the padding introduced is the minimum
@@ -488,7 +511,7 @@
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(2))
       .AddPacket(30, 'c')
       .CopyIntoTraceBuffer();
-  EXPECT_EQ(0u, trace_buffer()->stats().chunks_committed_out_of_order);
+  EXPECT_EQ(0u, trace_buffer()->stats().chunks_committed_out_of_order());
   trace_buffer()->BeginRead();
   ASSERT_THAT(ReadPacket(), ElementsAre(FakePacketFragment(10, 'a')));
   ASSERT_THAT(ReadPacket(), IsEmpty());
@@ -496,7 +519,7 @@
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(1))
       .AddPacket(20, 'b')
       .CopyIntoTraceBuffer();
-  EXPECT_EQ(1u, trace_buffer()->stats().chunks_committed_out_of_order);
+  EXPECT_EQ(1u, trace_buffer()->stats().chunks_committed_out_of_order());
   trace_buffer()->BeginRead();
   ASSERT_THAT(ReadPacket(), ElementsAre(FakePacketFragment(20, 'b')));
   ASSERT_THAT(ReadPacket(), ElementsAre(FakePacketFragment(30, 'c')));
@@ -1285,7 +1308,7 @@
       .AddPacket(100, 'b')
       .PadTo(512)
       .CopyIntoTraceBuffer(/*chunk_complete=*/false);
-  EXPECT_EQ(0u, trace_buffer()->stats().chunks_rewritten);
+  EXPECT_EQ(0u, trace_buffer()->stats().chunks_rewritten());
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(100, 'a')
       .AddPacket(100, 'b')
@@ -1294,7 +1317,7 @@
       .PadTo(512)
       .CopyIntoTraceBuffer();
   trace_buffer()->BeginRead();
-  EXPECT_EQ(1u, trace_buffer()->stats().chunks_rewritten);
+  EXPECT_EQ(1u, trace_buffer()->stats().chunks_rewritten());
   ASSERT_THAT(ReadPacket(), ElementsAre(FakePacketFragment(100, 'a')));
   ASSERT_THAT(ReadPacket(), ElementsAre(FakePacketFragment(100, 'b')));
   ASSERT_THAT(ReadPacket(), ElementsAre(FakePacketFragment(100, 'c')));
diff --git a/src/tracing/core/trace_stats.cc b/src/tracing/core/trace_stats.cc
new file mode 100644
index 0000000..4350995
--- /dev/null
+++ b/src/tracing/core/trace_stats.cc
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+/*******************************************************************************
+ * AUTOGENERATED - DO NOT EDIT
+ *******************************************************************************
+ * This file has been generated from the protobuf message
+ * perfetto/common/trace_stats.proto
+ * by
+ * ../../tools/proto_to_cpp/proto_to_cpp.cc.
+ * If you need to make changes here, change the .proto file and then run
+ * ./tools/gen_tracing_cpp_headers_from_protos
+ */
+
+#include "perfetto/tracing/core/trace_stats.h"
+
+#include "perfetto/common/trace_stats.pb.h"
+
+namespace perfetto {
+
+TraceStats::TraceStats() = default;
+TraceStats::~TraceStats() = default;
+TraceStats::TraceStats(const TraceStats&) = default;
+TraceStats& TraceStats::operator=(const TraceStats&) = default;
+TraceStats::TraceStats(TraceStats&&) noexcept = default;
+TraceStats& TraceStats::operator=(TraceStats&&) = default;
+
+void TraceStats::FromProto(const perfetto::protos::TraceStats& proto) {
+  buffer_stats_.clear();
+  for (const auto& field : proto.buffer_stats()) {
+    buffer_stats_.emplace_back();
+    buffer_stats_.back().FromProto(field);
+  }
+
+  static_assert(
+      sizeof(producers_connected_) == sizeof(proto.producers_connected()),
+      "size mismatch");
+  producers_connected_ =
+      static_cast<decltype(producers_connected_)>(proto.producers_connected());
+
+  static_assert(sizeof(producers_seen_) == sizeof(proto.producers_seen()),
+                "size mismatch");
+  producers_seen_ =
+      static_cast<decltype(producers_seen_)>(proto.producers_seen());
+
+  static_assert(sizeof(data_sources_registered_) ==
+                    sizeof(proto.data_sources_registered()),
+                "size mismatch");
+  data_sources_registered_ = static_cast<decltype(data_sources_registered_)>(
+      proto.data_sources_registered());
+
+  static_assert(sizeof(data_sources_seen_) == sizeof(proto.data_sources_seen()),
+                "size mismatch");
+  data_sources_seen_ =
+      static_cast<decltype(data_sources_seen_)>(proto.data_sources_seen());
+
+  static_assert(sizeof(tracing_sessions_) == sizeof(proto.tracing_sessions()),
+                "size mismatch");
+  tracing_sessions_ =
+      static_cast<decltype(tracing_sessions_)>(proto.tracing_sessions());
+
+  static_assert(sizeof(total_buffers_) == sizeof(proto.total_buffers()),
+                "size mismatch");
+  total_buffers_ = static_cast<decltype(total_buffers_)>(proto.total_buffers());
+  unknown_fields_ = proto.unknown_fields();
+}
+
+void TraceStats::ToProto(perfetto::protos::TraceStats* proto) const {
+  proto->Clear();
+
+  for (const auto& it : buffer_stats_) {
+    auto* entry = proto->add_buffer_stats();
+    it.ToProto(entry);
+  }
+
+  static_assert(
+      sizeof(producers_connected_) == sizeof(proto->producers_connected()),
+      "size mismatch");
+  proto->set_producers_connected(
+      static_cast<decltype(proto->producers_connected())>(
+          producers_connected_));
+
+  static_assert(sizeof(producers_seen_) == sizeof(proto->producers_seen()),
+                "size mismatch");
+  proto->set_producers_seen(
+      static_cast<decltype(proto->producers_seen())>(producers_seen_));
+
+  static_assert(sizeof(data_sources_registered_) ==
+                    sizeof(proto->data_sources_registered()),
+                "size mismatch");
+  proto->set_data_sources_registered(
+      static_cast<decltype(proto->data_sources_registered())>(
+          data_sources_registered_));
+
+  static_assert(
+      sizeof(data_sources_seen_) == sizeof(proto->data_sources_seen()),
+      "size mismatch");
+  proto->set_data_sources_seen(
+      static_cast<decltype(proto->data_sources_seen())>(data_sources_seen_));
+
+  static_assert(sizeof(tracing_sessions_) == sizeof(proto->tracing_sessions()),
+                "size mismatch");
+  proto->set_tracing_sessions(
+      static_cast<decltype(proto->tracing_sessions())>(tracing_sessions_));
+
+  static_assert(sizeof(total_buffers_) == sizeof(proto->total_buffers()),
+                "size mismatch");
+  proto->set_total_buffers(
+      static_cast<decltype(proto->total_buffers())>(total_buffers_));
+  *(proto->mutable_unknown_fields()) = unknown_fields_;
+}
+
+TraceStats::BufferStats::BufferStats() = default;
+TraceStats::BufferStats::~BufferStats() = default;
+TraceStats::BufferStats::BufferStats(const TraceStats::BufferStats&) = default;
+TraceStats::BufferStats& TraceStats::BufferStats::operator=(
+    const TraceStats::BufferStats&) = default;
+TraceStats::BufferStats::BufferStats(TraceStats::BufferStats&&) noexcept =
+    default;
+TraceStats::BufferStats& TraceStats::BufferStats::operator=(
+    TraceStats::BufferStats&&) = default;
+
+void TraceStats::BufferStats::FromProto(
+    const perfetto::protos::TraceStats_BufferStats& proto) {
+  static_assert(sizeof(buffer_size_) == sizeof(proto.buffer_size()),
+                "size mismatch");
+  buffer_size_ = static_cast<decltype(buffer_size_)>(proto.buffer_size());
+
+  static_assert(sizeof(bytes_written_) == sizeof(proto.bytes_written()),
+                "size mismatch");
+  bytes_written_ = static_cast<decltype(bytes_written_)>(proto.bytes_written());
+
+  static_assert(sizeof(bytes_overwritten_) == sizeof(proto.bytes_overwritten()),
+                "size mismatch");
+  bytes_overwritten_ =
+      static_cast<decltype(bytes_overwritten_)>(proto.bytes_overwritten());
+
+  static_assert(sizeof(bytes_read_) == sizeof(proto.bytes_read()),
+                "size mismatch");
+  bytes_read_ = static_cast<decltype(bytes_read_)>(proto.bytes_read());
+
+  static_assert(
+      sizeof(padding_bytes_written_) == sizeof(proto.padding_bytes_written()),
+      "size mismatch");
+  padding_bytes_written_ = static_cast<decltype(padding_bytes_written_)>(
+      proto.padding_bytes_written());
+
+  static_assert(
+      sizeof(padding_bytes_cleared_) == sizeof(proto.padding_bytes_cleared()),
+      "size mismatch");
+  padding_bytes_cleared_ = static_cast<decltype(padding_bytes_cleared_)>(
+      proto.padding_bytes_cleared());
+
+  static_assert(sizeof(chunks_written_) == sizeof(proto.chunks_written()),
+                "size mismatch");
+  chunks_written_ =
+      static_cast<decltype(chunks_written_)>(proto.chunks_written());
+
+  static_assert(sizeof(chunks_rewritten_) == sizeof(proto.chunks_rewritten()),
+                "size mismatch");
+  chunks_rewritten_ =
+      static_cast<decltype(chunks_rewritten_)>(proto.chunks_rewritten());
+
+  static_assert(
+      sizeof(chunks_overwritten_) == sizeof(proto.chunks_overwritten()),
+      "size mismatch");
+  chunks_overwritten_ =
+      static_cast<decltype(chunks_overwritten_)>(proto.chunks_overwritten());
+
+  static_assert(sizeof(chunks_read_) == sizeof(proto.chunks_read()),
+                "size mismatch");
+  chunks_read_ = static_cast<decltype(chunks_read_)>(proto.chunks_read());
+
+  static_assert(sizeof(chunks_committed_out_of_order_) ==
+                    sizeof(proto.chunks_committed_out_of_order()),
+                "size mismatch");
+  chunks_committed_out_of_order_ =
+      static_cast<decltype(chunks_committed_out_of_order_)>(
+          proto.chunks_committed_out_of_order());
+
+  static_assert(sizeof(write_wrap_count_) == sizeof(proto.write_wrap_count()),
+                "size mismatch");
+  write_wrap_count_ =
+      static_cast<decltype(write_wrap_count_)>(proto.write_wrap_count());
+
+  static_assert(sizeof(patches_succeeded_) == sizeof(proto.patches_succeeded()),
+                "size mismatch");
+  patches_succeeded_ =
+      static_cast<decltype(patches_succeeded_)>(proto.patches_succeeded());
+
+  static_assert(sizeof(patches_failed_) == sizeof(proto.patches_failed()),
+                "size mismatch");
+  patches_failed_ =
+      static_cast<decltype(patches_failed_)>(proto.patches_failed());
+
+  static_assert(
+      sizeof(readaheads_succeeded_) == sizeof(proto.readaheads_succeeded()),
+      "size mismatch");
+  readaheads_succeeded_ = static_cast<decltype(readaheads_succeeded_)>(
+      proto.readaheads_succeeded());
+
+  static_assert(sizeof(readaheads_failed_) == sizeof(proto.readaheads_failed()),
+                "size mismatch");
+  readaheads_failed_ =
+      static_cast<decltype(readaheads_failed_)>(proto.readaheads_failed());
+
+  static_assert(sizeof(abi_violations_) == sizeof(proto.abi_violations()),
+                "size mismatch");
+  abi_violations_ =
+      static_cast<decltype(abi_violations_)>(proto.abi_violations());
+  unknown_fields_ = proto.unknown_fields();
+}
+
+void TraceStats::BufferStats::ToProto(
+    perfetto::protos::TraceStats_BufferStats* proto) const {
+  proto->Clear();
+
+  static_assert(sizeof(buffer_size_) == sizeof(proto->buffer_size()),
+                "size mismatch");
+  proto->set_buffer_size(
+      static_cast<decltype(proto->buffer_size())>(buffer_size_));
+
+  static_assert(sizeof(bytes_written_) == sizeof(proto->bytes_written()),
+                "size mismatch");
+  proto->set_bytes_written(
+      static_cast<decltype(proto->bytes_written())>(bytes_written_));
+
+  static_assert(
+      sizeof(bytes_overwritten_) == sizeof(proto->bytes_overwritten()),
+      "size mismatch");
+  proto->set_bytes_overwritten(
+      static_cast<decltype(proto->bytes_overwritten())>(bytes_overwritten_));
+
+  static_assert(sizeof(bytes_read_) == sizeof(proto->bytes_read()),
+                "size mismatch");
+  proto->set_bytes_read(
+      static_cast<decltype(proto->bytes_read())>(bytes_read_));
+
+  static_assert(
+      sizeof(padding_bytes_written_) == sizeof(proto->padding_bytes_written()),
+      "size mismatch");
+  proto->set_padding_bytes_written(
+      static_cast<decltype(proto->padding_bytes_written())>(
+          padding_bytes_written_));
+
+  static_assert(
+      sizeof(padding_bytes_cleared_) == sizeof(proto->padding_bytes_cleared()),
+      "size mismatch");
+  proto->set_padding_bytes_cleared(
+      static_cast<decltype(proto->padding_bytes_cleared())>(
+          padding_bytes_cleared_));
+
+  static_assert(sizeof(chunks_written_) == sizeof(proto->chunks_written()),
+                "size mismatch");
+  proto->set_chunks_written(
+      static_cast<decltype(proto->chunks_written())>(chunks_written_));
+
+  static_assert(sizeof(chunks_rewritten_) == sizeof(proto->chunks_rewritten()),
+                "size mismatch");
+  proto->set_chunks_rewritten(
+      static_cast<decltype(proto->chunks_rewritten())>(chunks_rewritten_));
+
+  static_assert(
+      sizeof(chunks_overwritten_) == sizeof(proto->chunks_overwritten()),
+      "size mismatch");
+  proto->set_chunks_overwritten(
+      static_cast<decltype(proto->chunks_overwritten())>(chunks_overwritten_));
+
+  static_assert(sizeof(chunks_read_) == sizeof(proto->chunks_read()),
+                "size mismatch");
+  proto->set_chunks_read(
+      static_cast<decltype(proto->chunks_read())>(chunks_read_));
+
+  static_assert(sizeof(chunks_committed_out_of_order_) ==
+                    sizeof(proto->chunks_committed_out_of_order()),
+                "size mismatch");
+  proto->set_chunks_committed_out_of_order(
+      static_cast<decltype(proto->chunks_committed_out_of_order())>(
+          chunks_committed_out_of_order_));
+
+  static_assert(sizeof(write_wrap_count_) == sizeof(proto->write_wrap_count()),
+                "size mismatch");
+  proto->set_write_wrap_count(
+      static_cast<decltype(proto->write_wrap_count())>(write_wrap_count_));
+
+  static_assert(
+      sizeof(patches_succeeded_) == sizeof(proto->patches_succeeded()),
+      "size mismatch");
+  proto->set_patches_succeeded(
+      static_cast<decltype(proto->patches_succeeded())>(patches_succeeded_));
+
+  static_assert(sizeof(patches_failed_) == sizeof(proto->patches_failed()),
+                "size mismatch");
+  proto->set_patches_failed(
+      static_cast<decltype(proto->patches_failed())>(patches_failed_));
+
+  static_assert(
+      sizeof(readaheads_succeeded_) == sizeof(proto->readaheads_succeeded()),
+      "size mismatch");
+  proto->set_readaheads_succeeded(
+      static_cast<decltype(proto->readaheads_succeeded())>(
+          readaheads_succeeded_));
+
+  static_assert(
+      sizeof(readaheads_failed_) == sizeof(proto->readaheads_failed()),
+      "size mismatch");
+  proto->set_readaheads_failed(
+      static_cast<decltype(proto->readaheads_failed())>(readaheads_failed_));
+
+  static_assert(sizeof(abi_violations_) == sizeof(proto->abi_violations()),
+                "size mismatch");
+  proto->set_abi_violations(
+      static_cast<decltype(proto->abi_violations())>(abi_violations_));
+  *(proto->mutable_unknown_fields()) = unknown_fields_;
+}
+
+}  // namespace perfetto
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index 9eb44d8..4a4be28 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -1598,15 +1598,23 @@
   packet.set_trusted_uid(static_cast<int32_t>(uid_));
 
   protos::TraceStats* trace_stats = packet.mutable_trace_stats();
-  trace_stats->set_producers_connected(
-      static_cast<uint32_t>(producers_.size()));
-  trace_stats->set_producers_seen(last_producer_id_);
-  trace_stats->set_data_sources_registered(
+  GetTraceStats(tracing_session).ToProto(trace_stats);
+  Slice slice = Slice::Allocate(static_cast<size_t>(packet.ByteSize()));
+  PERFETTO_CHECK(packet.SerializeWithCachedSizesToArray(slice.own_data()));
+  packets->emplace_back();
+  packets->back().AddSlice(std::move(slice));
+}
+
+TraceStats TracingServiceImpl::GetTraceStats(TracingSession* tracing_session) {
+  TraceStats trace_stats;
+  trace_stats.set_producers_connected(static_cast<uint32_t>(producers_.size()));
+  trace_stats.set_producers_seen(last_producer_id_);
+  trace_stats.set_data_sources_registered(
       static_cast<uint32_t>(data_sources_.size()));
-  trace_stats->set_data_sources_seen(last_data_source_instance_id_);
-  trace_stats->set_tracing_sessions(
+  trace_stats.set_data_sources_seen(last_data_source_instance_id_);
+  trace_stats.set_tracing_sessions(
       static_cast<uint32_t>(tracing_sessions_.size()));
-  trace_stats->set_total_buffers(static_cast<uint32_t>(buffers_.size()));
+  trace_stats.set_total_buffers(static_cast<uint32_t>(buffers_.size()));
 
   for (BufferID buf_id : tracing_session->buffers_index) {
     TraceBuffer* buf = GetBufferByID(buf_id);
@@ -1614,25 +1622,9 @@
       PERFETTO_DFATAL("Buffer not found.");
       continue;
     }
-    auto* buf_stats_proto = trace_stats->add_buffer_stats();
-    const TraceBuffer::Stats& buf_stats = buf->stats();
-    buf_stats_proto->set_bytes_written(buf_stats.bytes_written);
-    buf_stats_proto->set_chunks_written(buf_stats.chunks_written);
-    buf_stats_proto->set_chunks_rewritten(buf_stats.chunks_rewritten);
-    buf_stats_proto->set_chunks_overwritten(buf_stats.chunks_overwritten);
-    buf_stats_proto->set_chunks_committed_out_of_order(
-        buf_stats.chunks_committed_out_of_order);
-    buf_stats_proto->set_write_wrap_count(buf_stats.write_wrap_count);
-    buf_stats_proto->set_patches_succeeded(buf_stats.patches_succeeded);
-    buf_stats_proto->set_patches_failed(buf_stats.patches_failed);
-    buf_stats_proto->set_readaheads_succeeded(buf_stats.readaheads_succeeded);
-    buf_stats_proto->set_readaheads_failed(buf_stats.readaheads_failed);
-    buf_stats_proto->set_abi_violations(buf_stats.abi_violations);
+    *trace_stats.add_buffer_stats() = buf->stats();
   }  // for (buf in session).
-  Slice slice = Slice::Allocate(static_cast<size_t>(packet.ByteSize()));
-  PERFETTO_CHECK(packet.SerializeWithCachedSizesToArray(slice.own_data()));
-  packets->emplace_back();
-  packets->back().AddSlice(std::move(slice));
+  return trace_stats;
 }
 
 void TracingServiceImpl::MaybeEmitTraceConfig(
@@ -1762,6 +1754,22 @@
   });
 }
 
+void TracingServiceImpl::ConsumerEndpointImpl::GetTraceStats() {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  bool success = false;
+  TraceStats stats;
+  TracingSession* session = service_->GetTracingSession(tracing_session_id_);
+  if (session) {
+    success = true;
+    stats = service_->GetTraceStats(session);
+  }
+  auto weak_this = GetWeakPtr();
+  task_runner_->PostTask([weak_this, success, stats] {
+    if (weak_this)
+      weak_this->consumer_->OnTraceStats(success, stats);
+  });
+}
+
 base::WeakPtr<TracingServiceImpl::ConsumerEndpointImpl>
 TracingServiceImpl::ConsumerEndpointImpl::GetWeakPtr() {
   PERFETTO_DCHECK_THREAD(thread_checker_);
diff --git a/src/tracing/core/tracing_service_impl.h b/src/tracing/core/tracing_service_impl.h
index f16aaf9..7822b58 100644
--- a/src/tracing/core/tracing_service_impl.h
+++ b/src/tracing/core/tracing_service_impl.h
@@ -32,6 +32,7 @@
 #include "perfetto/tracing/core/data_source_descriptor.h"
 #include "perfetto/tracing/core/shared_memory_abi.h"
 #include "perfetto/tracing/core/trace_config.h"
+#include "perfetto/tracing/core/trace_stats.h"
 #include "perfetto/tracing/core/tracing_service.h"
 #include "src/tracing/core/id_allocator.h"
 
@@ -162,6 +163,7 @@
     void Flush(uint32_t timeout_ms, FlushCallback) override;
     void Detach(const std::string& key) override;
     void Attach(const std::string& key) override;
+    void GetTraceStats() override;
 
    private:
     friend class TracingServiceImpl;
@@ -377,6 +379,7 @@
   void SnapshotSyncMarker(std::vector<TracePacket>*);
   void SnapshotClocks(std::vector<TracePacket>*);
   void SnapshotStats(TracingSession*, std::vector<TracePacket>*);
+  TraceStats GetTraceStats(TracingSession* tracing_session);
   void MaybeEmitTraceConfig(TracingSession*, std::vector<TracePacket>*);
   void OnFlushTimeout(TracingSessionID, FlushRequestID);
   void OnDisableTracingTimeout(TracingSessionID);
diff --git a/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc b/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc
index a37bacd..f7db5a6 100644
--- a/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc
+++ b/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc
@@ -23,6 +23,7 @@
 #include "perfetto/ipc/client.h"
 #include "perfetto/tracing/core/consumer.h"
 #include "perfetto/tracing/core/trace_config.h"
+#include "perfetto/tracing/core/trace_stats.h"
 
 // TODO(fmayer): Add a test to check to what happens when ConsumerIPCClientImpl
 // gets destroyed w.r.t. the Consumer pointer. Also think to lifetime of the
@@ -253,4 +254,29 @@
   }
 }
 
+void ConsumerIPCClientImpl::GetTraceStats() {
+  if (!connected_) {
+    PERFETTO_DLOG("Cannot GetTraceStats(), not connected to tracing service");
+    return;
+  }
+
+  protos::GetTraceStatsRequest req;
+  ipc::Deferred<protos::GetTraceStatsResponse> async_response;
+  auto weak_this = weak_ptr_factory_.GetWeakPtr();
+
+  async_response.Bind(
+      [weak_this](ipc::AsyncResult<protos::GetTraceStatsResponse> response) {
+        if (!weak_this)
+          return;
+        TraceStats trace_stats;
+        if (!response) {
+          weak_this->consumer_->OnTraceStats(/*success=*/false, trace_stats);
+          return;
+        }
+        trace_stats.FromProto(response->trace_stats());
+        weak_this->consumer_->OnTraceStats(/*success=*/true, trace_stats);
+      });
+  consumer_port_.GetTraceStats(req, std::move(async_response));
+}
+
 }  // namespace perfetto
diff --git a/src/tracing/ipc/consumer/consumer_ipc_client_impl.h b/src/tracing/ipc/consumer/consumer_ipc_client_impl.h
index a41b200..0210dc3 100644
--- a/src/tracing/ipc/consumer/consumer_ipc_client_impl.h
+++ b/src/tracing/ipc/consumer/consumer_ipc_client_impl.h
@@ -67,6 +67,7 @@
   void Flush(uint32_t timeout_ms, FlushCallback) override;
   void Detach(const std::string& key) override;
   void Attach(const std::string& key) override;
+  void GetTraceStats() override;
 
   // ipc::ServiceProxy::EventListener implementation.
   // These methods are invoked by the IPC layer, which knows nothing about
diff --git a/src/tracing/ipc/service/consumer_ipc_service.cc b/src/tracing/ipc/service/consumer_ipc_service.cc
index 125b0da..5eade92 100644
--- a/src/tracing/ipc/service/consumer_ipc_service.cc
+++ b/src/tracing/ipc/service/consumer_ipc_service.cc
@@ -27,6 +27,7 @@
 #include "perfetto/tracing/core/slice.h"
 #include "perfetto/tracing/core/trace_config.h"
 #include "perfetto/tracing/core/trace_packet.h"
+#include "perfetto/tracing/core/trace_stats.h"
 #include "perfetto/tracing/core/tracing_service.h"
 
 namespace perfetto {
@@ -137,6 +138,15 @@
   remote_consumer->service_endpoint->Attach(req.key());
 }
 
+// Called by the IPC layer.
+void ConsumerIPCService::GetTraceStats(const protos::GetTraceStatsRequest&,
+                                       DeferredGetTraceStatsResponse resp) {
+  // OnTraceStats() will resolve the |get_trace_stats_response|.
+  RemoteConsumer* remote_consumer = GetConsumerForCurrentRequest();
+  remote_consumer->get_trace_stats_response = std::move(resp);
+  remote_consumer->service_endpoint->GetTraceStats();
+}
+
 // Called by the service in response to a service_endpoint->Flush() request.
 void ConsumerIPCService::OnFlushCallback(
     bool success,
@@ -247,4 +257,15 @@
   std::move(attach_response).Resolve(std::move(response));
 }
 
+void ConsumerIPCService::RemoteConsumer::OnTraceStats(bool success,
+                                                      const TraceStats& stats) {
+  if (!success) {
+    std::move(get_trace_stats_response).Reject();
+    return;
+  }
+  auto response = ipc::AsyncResult<protos::GetTraceStatsResponse>::Create();
+  stats.ToProto(response->mutable_trace_stats());
+  std::move(get_trace_stats_response).Resolve(std::move(response));
+}
+
 }  // namespace perfetto
diff --git a/src/tracing/ipc/service/consumer_ipc_service.h b/src/tracing/ipc/service/consumer_ipc_service.h
index a3fa030..6a3782b 100644
--- a/src/tracing/ipc/service/consumer_ipc_service.h
+++ b/src/tracing/ipc/service/consumer_ipc_service.h
@@ -57,6 +57,8 @@
   void Flush(const protos::FlushRequest&, DeferredFlushResponse) override;
   void Detach(const protos::DetachRequest&, DeferredDetachResponse) override;
   void Attach(const protos::AttachRequest&, DeferredAttachResponse) override;
+  void GetTraceStats(const protos::GetTraceStatsRequest&,
+                     DeferredGetTraceStatsResponse) override;
   void OnClientDisconnected() override;
 
  private:
@@ -76,6 +78,7 @@
     void OnTraceData(std::vector<TracePacket>, bool has_more) override;
     void OnDetach(bool) override;
     void OnAttach(bool, const TraceConfig&) override;
+    void OnTraceStats(bool, const TraceStats&) override;
 
     // The interface obtained from the core service business logic through
     // TracingService::ConnectConsumer(this). This allows to invoke methods for
@@ -96,6 +99,9 @@
 
     // As above, but for the Attach() case.
     DeferredAttachResponse attach_response;
+
+    // As above, but for GetTraceStats().
+    DeferredGetTraceStatsResponse get_trace_stats_response;
   };
 
   // This has to be a container that doesn't invalidate iterators.
diff --git a/src/tracing/test/mock_consumer.cc b/src/tracing/test/mock_consumer.cc
index 3822ebe..b92b274 100644
--- a/src/tracing/test/mock_consumer.cc
+++ b/src/tracing/test/mock_consumer.cc
@@ -17,6 +17,7 @@
 #include "src/tracing/test/mock_consumer.h"
 
 #include "perfetto/tracing/core/trace_config.h"
+#include "perfetto/tracing/core/trace_stats.h"
 #include "src/base/test/test_task_runner.h"
 
 using ::testing::_;
@@ -114,4 +115,27 @@
   return decoded_packets;
 }
 
+void MockConsumer::GetTraceStats() {
+  service_endpoint_->GetTraceStats();
+}
+
+void MockConsumer::WaitForTraceStats(bool success) {
+  static int i = 0;
+  auto checkpoint_name = "on_trace_stats_" + std::to_string(i++);
+  auto on_trace_stats = task_runner_->CreateCheckpoint(checkpoint_name);
+  auto result_callback = [on_trace_stats](bool, const TraceStats&) {
+    on_trace_stats();
+  };
+  if (success) {
+    EXPECT_CALL(*this,
+                OnTraceStats(true, testing::Property(&TraceStats::total_buffers,
+                                                     testing::Gt(0))))
+        .WillOnce(Invoke(result_callback));
+  } else {
+    EXPECT_CALL(*this, OnTraceStats(false, _))
+        .WillOnce(Invoke(result_callback));
+  }
+  task_runner_->RunUntilCheckpoint(checkpoint_name);
+}
+
 }  // namespace perfetto
diff --git a/src/tracing/test/mock_consumer.h b/src/tracing/test/mock_consumer.h
index e19729d..184e8da 100644
--- a/src/tracing/test/mock_consumer.h
+++ b/src/tracing/test/mock_consumer.h
@@ -54,6 +54,8 @@
   void WaitForTracingDisabled(uint32_t timeout_ms = 3000);
   FlushRequest Flush(uint32_t timeout_ms = 10000);
   std::vector<protos::TracePacket> ReadBuffers();
+  void GetTraceStats();
+  void WaitForTraceStats(bool success);
 
   TracingService::ConsumerEndpoint* endpoint() {
     return service_endpoint_.get();
@@ -67,6 +69,7 @@
                void(std::vector<TracePacket>* /*packets*/, bool /*has_more*/));
   MOCK_METHOD1(OnDetach, void(bool));
   MOCK_METHOD2(OnAttach, void(bool, const TraceConfig&));
+  MOCK_METHOD2(OnTraceStats, void(bool, const TraceStats&));
 
   // gtest doesn't support move-only types. This wrapper is here jut to pass
   // a pointer to the vector (rather than the vector itself) to the mock method.
diff --git a/src/tracing/test/tracing_integration_test.cc b/src/tracing/test/tracing_integration_test.cc
index 5478dcd..fdac291 100644
--- a/src/tracing/test/tracing_integration_test.cc
+++ b/src/tracing/test/tracing_integration_test.cc
@@ -26,6 +26,7 @@
 #include "perfetto/tracing/core/producer.h"
 #include "perfetto/tracing/core/trace_config.h"
 #include "perfetto/tracing/core/trace_packet.h"
+#include "perfetto/tracing/core/trace_stats.h"
 #include "perfetto/tracing/core/trace_writer.h"
 #include "perfetto/tracing/ipc/consumer_ipc_client.h"
 #include "perfetto/tracing/ipc/producer_ipc_client.h"
@@ -78,6 +79,7 @@
   MOCK_METHOD2(OnTracePackets, void(std::vector<TracePacket>*, bool));
   MOCK_METHOD1(OnDetach, void(bool));
   MOCK_METHOD2(OnAttach, void(bool, const TraceConfig&));
+  MOCK_METHOD2(OnTraceStats, void(bool, const TraceStats&));
 
   // Workaround, gmock doesn't support yet move-only types, passing a pointer.
   void OnTraceData(std::vector<TracePacket> packets, bool has_more) {
diff --git a/test/test_helper.cc b/test/test_helper.cc
index 46572e8..afdd8eb 100644
--- a/test/test_helper.cc
+++ b/test/test_helper.cc
@@ -175,6 +175,8 @@
     std::move(on_attach_callback_)(success);
 }
 
+void TestHelper::OnTraceStats(bool, const TraceStats&) {}
+
 // static
 const char* TestHelper::GetConsumerSocketName() {
   return TEST_CONSUMER_SOCK_NAME;
diff --git a/test/test_helper.h b/test/test_helper.h
index a5a1301..f2fe7dd 100644
--- a/test/test_helper.h
+++ b/test/test_helper.h
@@ -43,6 +43,7 @@
   void OnTraceData(std::vector<TracePacket> packets, bool has_more) override;
   void OnDetach(bool) override;
   void OnAttach(bool, const TraceConfig&) override;
+  void OnTraceStats(bool, const TraceStats&) override;
 
   void StartServiceIfRequired();
   FakeProducer* ConnectFakeProducer();
diff --git a/tools/gen_tracing_cpp_headers_from_protos b/tools/gen_tracing_cpp_headers_from_protos
index ce1cbe4..8c14b6f 100755
--- a/tools/gen_tracing_cpp_headers_from_protos
+++ b/tools/gen_tracing_cpp_headers_from_protos
@@ -21,6 +21,7 @@
   'perfetto/common/android_log_constants.proto',
   'perfetto/common/commit_data_request.proto',
   'perfetto/common/sys_stats_counters.proto',
+  'perfetto/common/trace_stats.proto',
   'perfetto/config/android/android_log_config.proto',
   'perfetto/config/chrome/chrome_config.proto',
   'perfetto/config/data_source_config.proto',