Add stats about dropped events to the trace

Bug: 78765090
Change-Id: I673ce7bd6bb671ffa73faaf7f72a651b7e160901
diff --git a/Android.bp b/Android.bp
index 5ed3bff..65684ee 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2931,6 +2931,7 @@
   name: "perfetto_protos_perfetto_trace_minimal_lite_gen",
   srcs: [
     "protos/perfetto/trace/clock_snapshot.proto",
+    "protos/perfetto/trace/trace_stats.proto",
     "protos/perfetto/trace/trusted_packet.proto",
   ],
   tools: [
@@ -2939,6 +2940,7 @@
   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",
     "external/perfetto/protos/perfetto/trace/trusted_packet.pb.cc",
   ],
 }
@@ -2948,6 +2950,7 @@
   name: "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
   srcs: [
     "protos/perfetto/trace/clock_snapshot.proto",
+    "protos/perfetto/trace/trace_stats.proto",
     "protos/perfetto/trace/trusted_packet.proto",
   ],
   tools: [
@@ -2956,6 +2959,7 @@
   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",
     "external/perfetto/protos/perfetto/trace/trusted_packet.pb.h",
   ],
   export_include_dirs: [
@@ -3039,6 +3043,7 @@
     "protos/perfetto/trace/test_event.proto",
     "protos/perfetto/trace/trace.proto",
     "protos/perfetto/trace/trace_packet.proto",
+    "protos/perfetto/trace/trace_stats.proto",
     "protos/perfetto/trace/trusted_packet.proto",
   ],
   tools: [
@@ -3051,6 +3056,7 @@
     "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",
     "external/perfetto/protos/perfetto/trace/trusted_packet.pbzero.cc",
   ],
 }
@@ -3063,6 +3069,7 @@
     "protos/perfetto/trace/test_event.proto",
     "protos/perfetto/trace/trace.proto",
     "protos/perfetto/trace/trace_packet.proto",
+    "protos/perfetto/trace/trace_stats.proto",
     "protos/perfetto/trace/trusted_packet.proto",
   ],
   tools: [
@@ -3075,6 +3082,7 @@
     "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",
     "external/perfetto/protos/perfetto/trace/trusted_packet.pbzero.h",
   ],
   export_include_dirs: [
diff --git a/protos/perfetto/trace/BUILD.gn b/protos/perfetto/trace/BUILD.gn
index e71f4bf..64f2489 100644
--- a/protos/perfetto/trace/BUILD.gn
+++ b/protos/perfetto/trace/BUILD.gn
@@ -20,6 +20,7 @@
 # the generic ":lite" target
 proto_sources_minimal = [
   "clock_snapshot.proto",
+  "trace_stats.proto",
   "trusted_packet.proto",
 ]
 
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 5dc7c0a..942e8c5 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -41,8 +41,10 @@
 
     // 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
+    // removed field with id 35
 
     // 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 6fcee5a..9a5f494 100644
--- a/protos/perfetto/trace/trace_packet.proto
+++ b/protos/perfetto/trace/trace_packet.proto
@@ -25,6 +25,7 @@
 import "perfetto/trace/ftrace/ftrace_stats.proto";
 import "perfetto/trace/ps/process_tree.proto";
 import "perfetto/trace/test_event.proto";
+import "perfetto/trace/trace_stats.proto";
 
 package perfetto.protos;
 
@@ -42,8 +43,10 @@
 
     // 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;
+    TraceStats trace_stats = 35;
 
     // This field is only used for testing.
     TestEvent for_testing = 536870911;  // 2^29 - 1, max field id for protos.
diff --git a/protos/perfetto/trace/trace_stats.proto b/protos/perfetto/trace/trace_stats.proto
new file mode 100644
index 0000000..40cb599
--- /dev/null
+++ b/protos/perfetto/trace/trace_stats.proto
@@ -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.
+ */
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+package perfetto.protos;
+
+// Statistics for the internals of the tracing service.
+message TraceStats {
+  // From TraceBuffer::Stats.
+  message BufferStats {
+    // Num. bytes written into the circular buffer.
+    optional uint64 bytes_written = 1;
+
+    // Num. chunks (!= packets) written into the buffer.
+    optional uint64 chunks_written = 2;
+
+    // Num. chunks overwritten before they have been read (i.e. loss of data).
+    optional uint64 chunks_overwritten = 3;
+
+    // Num. times the ring buffer wrapped around.
+    optional uint64 write_wrap_count = 4;
+
+    // Num. out-of-band (OOB) patches that succeeded.
+    optional uint64 patches_succeeded = 5;
+
+    // Num. OOB patches that failed (e.g., the chunk to patch was gone).
+    optional uint64 patches_failed = 6;
+
+    // Num. readaheads (for large multi-chunk packet reads) that ended up in a
+    // successful packet read.
+    optional uint64 readaheads_succeeded = 7;
+
+    // Num. readaheads aborted because of missing chunks in the sequence stream.
+    // Note that a small number > 0 is totally expected: occasionally, when
+    // issuing a read, the very last packet in a sequence might be incomplete
+    // (because the producer is still writing it while we read). The read will
+    // stop at that point, for that sequence, increasing this counter.
+    optional uint64 readaheads_failed = 8;
+
+    // Num. of violations of the SharedMemoryABI found while writing or reading
+    // the buffer. This is an indication of either a bug in the producer(s) or
+    // malicious producer(s).
+    optional uint64 abi_violations = 9;
+  }
+
+  // Stats for the TraceBuffer(s) of the current trace session.
+  repeated BufferStats buffer_stats = 1;
+
+  // Num. producers connected (whether they are involved in the current tracing
+  // session or not).
+  optional uint32 producers_connected = 2;
+
+  // Num. producers ever seen for all trace sessions since startup (it's a good
+  // proxy for inferring num. producers crashed / killed).
+  optional uint64 producers_seen = 3;
+
+  // 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
+  optional uint64 data_sources_seen = 5;
+
+  // Num. concurrently active tracing sessions.
+  optional uint32 tracing_sessions = 6;
+
+  // Num. buffers for all tracing session (not just the current one). This will
+  // be >= buffer_stats.size(), because the latter is only about the current
+  // session.
+  optional uint32 total_buffers = 7;
+}
diff --git a/protos/perfetto/trace/trusted_packet.proto b/protos/perfetto/trace/trusted_packet.proto
index 072cfd4..36262c8 100644
--- a/protos/perfetto/trace/trusted_packet.proto
+++ b/protos/perfetto/trace/trusted_packet.proto
@@ -28,6 +28,7 @@
 
 import "perfetto/config/trace_config.proto";
 import "perfetto/trace/clock_snapshot.proto";
+import "perfetto/trace/trace_stats.proto";
 
 package perfetto.protos;
 
@@ -45,8 +46,7 @@
   // uid == 0 and uid not set (the writer uses proto2).
   oneof optional_trusted_uid { int32 trusted_uid = 3; };
 
-  // The original trace config.
-  TraceConfig trace_config = 33;
-
   ClockSnapshot clock_snapshot = 6;
+  TraceConfig trace_config = 33;
+  TraceStats trace_stats = 35;
 }
diff --git a/src/tracing/core/packet_stream_validator.cc b/src/tracing/core/packet_stream_validator.cc
index 8f6f21e..5368999 100644
--- a/src/tracing/core/packet_stream_validator.cc
+++ b/src/tracing/core/packet_stream_validator.cc
@@ -46,6 +46,10 @@
   if (packet.has_trace_config())
     return false;
 
+  // Only the service is allowed to fill in the TraceStats.
+  if (packet.has_trace_stats())
+    return false;
+
   // We are deliberately not checking for clock_snapshot for the moment. It's
   // unclear if we want to allow producers to snapshot their clocks. Ideally we
   // want a security model where producers can only snapshot their own clocks
diff --git a/src/tracing/core/service_impl.cc b/src/tracing/core/service_impl.cc
index 3bb107f..7697e86 100644
--- a/src/tracing/core/service_impl.cc
+++ b/src/tracing/core/service_impl.cc
@@ -53,6 +53,7 @@
 constexpr size_t kDefaultShmPageSize = base::kPageSize;
 constexpr int kMaxBuffersPerConsumer = 128;
 constexpr base::TimeMillis kClockSnapshotInterval(10 * 1000);
+constexpr base::TimeMillis kStatsSnapshotInterval(10 * 1000);
 constexpr int kMinWriteIntoFilePeriodMs = 100;
 constexpr int kDefaultWriteIntoFilePeriodMs = 5000;
 constexpr int kFlushTimeoutMs = 1000;
@@ -80,6 +81,7 @@
                          base::TaskRunner* task_runner)
     : task_runner_(task_runner),
       shm_factory_(std::move(shm_factory)),
+      uid_(getuid()),
       buffer_ids_(kMaxTraceBufferID),
       weak_ptr_factory_(this) {
   PERFETTO_DCHECK(task_runner_);
@@ -520,7 +522,9 @@
   }
 
   std::vector<TracePacket> packets;
+  packets.reserve(1024);  // Just an educated guess to avoid trivial expansions.
   MaybeSnapshotClocks(tracing_session, &packets);
+  MaybeSnapshotStats(tracing_session, &packets);
   MaybeEmitTraceConfig(tracing_session, &packets);
 
   size_t packets_bytes = 0;  // SUM(slice.size() for each slice in |packets|).
@@ -798,7 +802,7 @@
   PERFETTO_DCHECK(producer);
   // An existing producer that is not ftrace could have registered itself as
   // ftrace, we must not enable it in that case.
-  if (lockdown_mode_ && producer->uid_ != getuid()) {
+  if (lockdown_mode_ && producer->uid_ != uid_) {
     PERFETTO_DLOG("Lockdown mode: not enabling producer %hu", producer->id_);
     return;
   }
@@ -1053,7 +1057,52 @@
   c->set_timestamp(static_cast<uint64_t>(base::GetWallTimeNs().count()));
 #endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
 
-  packet.set_trusted_uid(static_cast<int32_t>(getuid()));
+  packet.set_trusted_uid(static_cast<int32_t>(uid_));
+  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));
+}
+
+void ServiceImpl::MaybeSnapshotStats(TracingSession* tracing_session,
+                                     std::vector<TracePacket>* packets) {
+  base::TimeMillis now = base::GetWallTimeMs();
+  if (now < tracing_session->last_stats_snapshot + kStatsSnapshotInterval)
+    return;
+  tracing_session->last_stats_snapshot = now;
+
+  protos::TrustedPacket packet;
+  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(
+      static_cast<uint32_t>(data_sources_.size()));
+  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()));
+
+  for (BufferID buf_id : tracing_session->buffers_index) {
+    TraceBuffer* buf = GetBufferByID(buf_id);
+    if (!buf) {
+      PERFETTO_DCHECK(false);
+      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_overwritten(buf_stats.chunks_overwritten);
+    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);
+  }  // for (buf in session).
   Slice slice = Slice::Allocate(static_cast<size_t>(packet.ByteSize()));
   PERFETTO_CHECK(packet.SerializeWithCachedSizesToArray(slice.own_data()));
   packets->emplace_back();
@@ -1067,7 +1116,7 @@
   tracing_session->did_emit_config = true;
   protos::TrustedPacket packet;
   tracing_session->config.ToProto(packet.mutable_trace_config());
-  packet.set_trusted_uid(static_cast<int32_t>(getuid()));
+  packet.set_trusted_uid(static_cast<int32_t>(uid_));
   Slice slice = Slice::Allocate(static_cast<size_t>(packet.ByteSize()));
   PERFETTO_CHECK(packet.SerializeWithCachedSizesToArray(slice.own_data()));
   packets->emplace_back();
diff --git a/src/tracing/core/service_impl.h b/src/tracing/core/service_impl.h
index c6f7936..b657a4a 100644
--- a/src/tracing/core/service_impl.h
+++ b/src/tracing/core/service_impl.h
@@ -240,6 +240,9 @@
     // When the last clock snapshot was emitted into the output stream.
     base::TimeMillis last_clock_snapshot = {};
 
+    // When the last TraceStats snapshot was emitted into the output stream.
+    base::TimeMillis last_stats_snapshot = {};
+
     // Whether we mirrored the trace config back to the trace output yet.
     bool did_emit_config = false;
 
@@ -276,6 +279,7 @@
 
   void MaybeSnapshotClocks(TracingSession*, std::vector<TracePacket>*);
   void MaybeEmitTraceConfig(TracingSession*, std::vector<TracePacket>*);
+  void MaybeSnapshotStats(TracingSession*, std::vector<TracePacket>*);
   void OnFlushTimeout(TracingSessionID, FlushRequestID);
   TraceBuffer* GetBufferByID(BufferID);
 
@@ -285,6 +289,7 @@
   DataSourceInstanceID last_data_source_instance_id_ = 0;
   TracingSessionID last_tracing_session_id_ = 0;
   FlushRequestID last_flush_request_id_ = 0;
+  uid_t uid_ = 0;
 
   // Buffer IDs are global across all consumers (because a Producer can produce
   // data for more than one trace session, hence more than one consumer).
diff --git a/src/tracing/core/trace_buffer.cc b/src/tracing/core/trace_buffer.cc
index 8ec272f..167b6b5 100644
--- a/src/tracing/core/trace_buffer.cc
+++ b/src/tracing/core/trace_buffer.cc
@@ -114,6 +114,7 @@
   const size_t record_size =
       base::AlignUp<sizeof(ChunkRecord)>(size + sizeof(ChunkRecord));
   if (PERFETTO_UNLIKELY(record_size > max_chunk_size_)) {
+    stats_.abi_violations++;
     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
     return;
   }
@@ -167,11 +168,14 @@
 
   // Now first insert the new chunk. At the end, if necessary, add the padding.
   ChunkMeta::Key key(record);
+  stats_.chunks_written++;
+  stats_.bytes_written += size;
   auto it_and_inserted =
       index_.emplace(key, ChunkMeta(GetChunkRecordAt(wptr_), num_fragments,
                                     chunk_flags, producer_uid_trusted));
   if (PERFETTO_UNLIKELY(!it_and_inserted.second)) {
     // More likely a producer bug, but could also be a malicious producer.
+    stats_.abi_violations++;
     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
     index_.erase(it_and_inserted.first);
     index_.emplace(key, ChunkMeta(GetChunkRecordAt(wptr_), num_fragments,
@@ -226,7 +230,15 @@
     // records are not part of the index).
     if (PERFETTO_LIKELY(!next_chunk.is_padding)) {
       ChunkMeta::Key key(next_chunk);
-      const size_t removed = index_.erase(key);
+      auto it = index_.find(key);
+      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++;
+        index_.erase(it);
+        removed = true;
+      }
       TRACE_BUFFER_DLOG("  del index {%" PRIu32 ",%" PRIu32
                         ",%u} @ [%lu - %lu] %zu",
                         key.producer_id, key.writer_id, key.chunk_id,
@@ -266,7 +278,7 @@
   ChunkMeta::Key key(producer_id, writer_id, chunk_id);
   auto it = index_.find(key);
   if (it == index_.end()) {
-    stats_.failed_patches++;
+    stats_.patches_failed++;
     return false;
   }
   ChunkMeta& chunk_meta = it->second;
@@ -296,7 +308,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_.failed_patches++;
+      stats_.patches_failed++;
       return false;
     }
 
@@ -312,7 +324,7 @@
       "Chunk raw (after patch): %s",
       HexDump(chunk_begin, chunk_meta.chunk_record->size).c_str());
 
-  stats_.succeeded_patches += patches_size;
+  stats_.patches_succeeded += patches_size;
   if (!other_patches_pending) {
     chunk_meta.flags &= ~kChunkNeedsPatching;
     chunk_meta.chunk_record->flags = chunk_meta.flags;
@@ -488,6 +500,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++;
         PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
         break;
       }
@@ -495,7 +508,7 @@
       PERFETTO_DCHECK(action == kTryReadAhead);
       ReadAheadResult ra_res = ReadAhead(packet);
       if (ra_res == ReadAheadResult::kSucceededReturnSlices) {
-        stats_.fragment_readahead_successes++;
+        stats_.readaheads_succeeded++;
         *producer_uid = trusted_uid;
         return true;
       }
@@ -503,7 +516,7 @@
       if (ra_res == ReadAheadResult::kFailedMoveToNextSequence) {
         // readahead didn't find a contigous packet sequence. We'll try again
         // on the next ReadPacket() call.
-        stats_.fragment_readahead_failures++;
+        stats_.readaheads_failed++;
 
         // TODO(primiano): optimization: this MoveToEnd() is the reason why
         // MoveNext() (that is called in the outer for(;;MoveNext)) needs to
@@ -593,6 +606,7 @@
     PERFETTO_DCHECK(read_iter_.cur == it.cur);
 
     if (PERFETTO_UNLIKELY(packet_corruption)) {
+      stats_.abi_violations++;
       PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
       *packet = TracePacket();  // clear.
       return ReadAheadResult::kFailedStayOnSameSequence;
@@ -618,6 +632,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++;
     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
     return false;
   }
@@ -635,6 +650,7 @@
   const uint8_t* next_packet = packet_data + packet_size;
   if (PERFETTO_UNLIKELY(next_packet <= packet_begin ||
                         next_packet > record_end)) {
+    stats_.abi_violations++;
     PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
     chunk_meta->cur_fragment_offset = 0;
     chunk_meta->num_fragments_read = chunk_meta->num_fragments;
@@ -645,6 +661,7 @@
   chunk_meta->num_fragments_read++;
 
   if (PERFETTO_UNLIKELY(packet_size == 0)) {
+    stats_.abi_violations++;
     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 1998be5..445c7a8 100644
--- a/src/tracing/core/trace_buffer.h
+++ b/src/tracing/core/trace_buffer.h
@@ -112,7 +112,7 @@
 // -----------------------
 // This class supports one reader only (the consumer). Reads are NOT idempotent
 // as they move the read cursors around. Reading back the buffer is the most
-// conceptually complex part. The ReadPacket() method, in fact, operates with
+// conceptually complex part. The ReadNextTracePacket() method operates with
 // whole packet granularity. Packets are returned only when all their fragments
 // are available.
 // This class takes care of:
@@ -122,19 +122,24 @@
 // - Detecting holes in packet fragments (because of loss of chunks).
 // Reads guarantee that packets for the same sequence are read in FIFO order
 // (according to their ChunkID), but don't give any guarantee about the read
-// order of packets from different sequences (see ReadPacket() comments below).
+// order of packets from different sequences, see comments in
+// ReadNextTracePacket() below.
 class TraceBuffer {
  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 {
-    size_t failed_patches = 0;
-    size_t succeeded_patches = 0;
-    size_t fragment_readahead_successes = 0;
-    size_t fragment_readahead_failures = 0;
-    size_t write_wrap_count = 0;
-    // TODO(primiano): add packets_{read,written}.
-    // TODO(primiano): add bytes_{read,written}.
+    uint64_t bytes_written = 0;
+    uint64_t chunks_written = 0;
+    uint64_t chunks_overwritten = 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;
     // TODO(primiano): add bytes_lost_for_padding.
   };
 
@@ -336,8 +341,8 @@
 
   // Allows to iterate over a sub-sequence of |index_| for all keys belonging to
   // the same {ProducerID,WriterID}. Furthermore takes into account the wrapping
-  // of ChunkID. Instances are valid only as long as the |index_| is not
-  // altered (can be used safely only between adjacent ReadPacket() calls).
+  // of ChunkID. Instances are valid only as long as the |index_| is not altered
+  // (can be used safely only between adjacent ReadNextTracePacket() calls).
   // The order of the iteration will proceed in the following order:
   // |wrapping_id| + 1 -> |seq_end|, |seq_begin| -> |wrapping_id|.
   // Practical example:
diff --git a/src/tracing/core/trace_packet_unittest.cc b/src/tracing/core/trace_packet_unittest.cc
index 8aab213..61bc001 100644
--- a/src/tracing/core/trace_packet_unittest.cc
+++ b/src/tracing/core/trace_packet_unittest.cc
@@ -39,6 +39,10 @@
                   protos::TrustedPacket::kTraceConfigFieldNumber,
               "trace_config field id mismatch");
 
+static_assert(protos::TracePacket::kTraceStatsFieldNumber ==
+                  protos::TrustedPacket::kTraceStatsFieldNumber,
+              "trace_stats field id mismatch");
+
 static_assert(protos::TracePacket::kClockSnapshotFieldNumber ==
                   protos::TrustedPacket::kClockSnapshotFieldNumber,
               "clock_snapshot field id mismatch");
diff --git a/src/tracing/test/tracing_integration_test.cc b/src/tracing/test/tracing_integration_test.cc
index 3b7f906..f90c1b1 100644
--- a/src/tracing/test/tracing_integration_test.cc
+++ b/src/tracing/test/tracing_integration_test.cc
@@ -81,6 +81,25 @@
   }
 };
 
+void CheckTraceStats(const protos::TracePacket& packet) {
+  EXPECT_TRUE(packet.has_trace_stats());
+  EXPECT_GE(packet.trace_stats().producers_seen(), 1);
+  EXPECT_EQ(1, packet.trace_stats().producers_connected());
+  EXPECT_EQ(1, packet.trace_stats().data_sources_registered());
+  EXPECT_EQ(1, packet.trace_stats().tracing_sessions());
+  EXPECT_EQ(1, packet.trace_stats().total_buffers());
+  EXPECT_EQ(1, packet.trace_stats().buffer_stats_size());
+
+  const auto& buf_stats = packet.trace_stats().buffer_stats(0);
+  EXPECT_GT(buf_stats.bytes_written(), 0);
+  EXPECT_GT(buf_stats.chunks_written(), 0);
+  EXPECT_EQ(0, buf_stats.chunks_overwritten());
+  EXPECT_EQ(0, buf_stats.write_wrap_count());
+  EXPECT_EQ(0, buf_stats.patches_failed());
+  EXPECT_EQ(0, buf_stats.readaheads_failed());
+  EXPECT_EQ(0, buf_stats.abi_violations());
+}
+
 class TracingIntegrationTest : public ::testing::Test {
  public:
   void SetUp() override {
@@ -206,57 +225,61 @@
   size_t num_pack_rx = 0;
   bool saw_clock_snapshot = false;
   bool saw_trace_config = false;
+  bool saw_trace_stats = false;
   auto all_packets_rx = task_runner_->CreateCheckpoint("all_packets_rx");
   EXPECT_CALL(consumer_, OnTracePackets(_, _))
-      .WillRepeatedly(Invoke([&num_pack_rx, all_packets_rx, &trace_config,
-                              &saw_clock_snapshot, &saw_trace_config](
-                                 std::vector<TracePacket>* packets,
-                                 bool has_more) {
+      .WillRepeatedly(
+          Invoke([&num_pack_rx, all_packets_rx, &trace_config,
+                  &saw_clock_snapshot, &saw_trace_config, &saw_trace_stats](
+                     std::vector<TracePacket>* packets, bool has_more) {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
-        const int kExpectedMinNumberOfClocks = 1;
+            const int kExpectedMinNumberOfClocks = 1;
 #else
-        const int kExpectedMinNumberOfClocks = 6;
+            const int kExpectedMinNumberOfClocks = 6;
 #endif
 
-        for (auto& encoded_packet : *packets) {
-          protos::TracePacket packet;
-          ASSERT_TRUE(encoded_packet.Decode(&packet));
-          if (packet.has_for_testing()) {
-            char buf[8];
-            sprintf(buf, "evt_%zu", num_pack_rx++);
-            EXPECT_EQ(std::string(buf), packet.for_testing().str());
-          } else if (packet.has_clock_snapshot()) {
-            EXPECT_GE(packet.clock_snapshot().clocks_size(),
-                      kExpectedMinNumberOfClocks);
-            saw_clock_snapshot = true;
-          } else if (packet.has_trace_config()) {
-            protos::TraceConfig config_proto;
-            trace_config.ToProto(&config_proto);
-            Slice expected_slice =
-                Slice::Allocate(static_cast<size_t>(config_proto.ByteSize()));
-            config_proto.SerializeWithCachedSizesToArray(
-                expected_slice.own_data());
-            Slice actual_slice = Slice::Allocate(
-                static_cast<size_t>(packet.trace_config().ByteSize()));
-            packet.trace_config().SerializeWithCachedSizesToArray(
-                actual_slice.own_data());
-            EXPECT_EQ(
-                std::string(
-                    reinterpret_cast<const char*>(expected_slice.own_data()),
-                    expected_slice.size),
-                std::string(
-                    reinterpret_cast<const char*>(actual_slice.own_data()),
-                    actual_slice.size));
-            saw_trace_config = true;
-          }
-        }
-        if (!has_more)
-          all_packets_rx();
-      }));
+            for (auto& encoded_packet : *packets) {
+              protos::TracePacket packet;
+              ASSERT_TRUE(encoded_packet.Decode(&packet));
+              if (packet.has_for_testing()) {
+                char buf[8];
+                sprintf(buf, "evt_%zu", num_pack_rx++);
+                EXPECT_EQ(std::string(buf), packet.for_testing().str());
+              } else if (packet.has_clock_snapshot()) {
+                EXPECT_GE(packet.clock_snapshot().clocks_size(),
+                          kExpectedMinNumberOfClocks);
+                saw_clock_snapshot = true;
+              } else if (packet.has_trace_config()) {
+                protos::TraceConfig config_proto;
+                trace_config.ToProto(&config_proto);
+                Slice expected_slice = Slice::Allocate(
+                    static_cast<size_t>(config_proto.ByteSize()));
+                config_proto.SerializeWithCachedSizesToArray(
+                    expected_slice.own_data());
+                Slice actual_slice = Slice::Allocate(
+                    static_cast<size_t>(packet.trace_config().ByteSize()));
+                packet.trace_config().SerializeWithCachedSizesToArray(
+                    actual_slice.own_data());
+                EXPECT_EQ(std::string(reinterpret_cast<const char*>(
+                                          expected_slice.own_data()),
+                                      expected_slice.size),
+                          std::string(reinterpret_cast<const char*>(
+                                          actual_slice.own_data()),
+                                      actual_slice.size));
+                saw_trace_config = true;
+              } else if (packet.has_trace_stats()) {
+                saw_trace_stats = true;
+                CheckTraceStats(packet);
+              }
+            }
+            if (!has_more)
+              all_packets_rx();
+          }));
   task_runner_->RunUntilCheckpoint("all_packets_rx");
   ASSERT_EQ(kNumPackets, num_pack_rx);
   EXPECT_TRUE(saw_clock_snapshot);
   EXPECT_TRUE(saw_trace_config);
+  EXPECT_TRUE(saw_trace_stats);
 
   // Disable tracing.
   consumer_endpoint_->DisableTracing();
@@ -327,13 +350,18 @@
   protos::Trace tmp_trace;
   ASSERT_TRUE(tmp_trace.ParseFromArray(tmp_buf, static_cast<int>(rsize)));
   size_t num_test_packet = 0;
+  bool saw_trace_stats = false;
   for (int i = 0; i < tmp_trace.packet_size(); i++) {
     const protos::TracePacket& packet = tmp_trace.packet(i);
     if (packet.has_for_testing()) {
       ASSERT_EQ("evt_" + std::to_string(num_test_packet++),
                 packet.for_testing().str());
+    } else if (packet.has_trace_stats()) {
+      saw_trace_stats = true;
+      CheckTraceStats(packet);
     }
   }
+  ASSERT_TRUE(saw_trace_stats);
 }
 
 // TODO(primiano): add tests to cover:
diff --git a/test/test_helper.cc b/test/test_helper.cc
index 55fa082..ad01ced 100644
--- a/test/test_helper.cc
+++ b/test/test_helper.cc
@@ -60,8 +60,10 @@
   for (auto& encoded_packet : packets) {
     protos::TracePacket packet;
     ASSERT_TRUE(encoded_packet.Decode(&packet));
-    if (packet.has_clock_snapshot() || packet.has_trace_config())
+    if (packet.has_clock_snapshot() || packet.has_trace_config() ||
+        packet.has_trace_stats()) {
       continue;
+    }
     ASSERT_EQ(protos::TracePacket::kTrustedUid,
               packet.optional_trusted_uid_case());
     trace_.push_back(std::move(packet));
diff --git a/tools/trace_to_text/main.cc b/tools/trace_to_text/main.cc
index 30bca4b..a31259b 100644
--- a/tools/trace_to_text/main.cc
+++ b/tools/trace_to_text/main.cc
@@ -425,6 +425,33 @@
     *output << "\n";
 }
 
+void PrintTraceStats(std::ostream* output,
+                     const protos::TraceStats& stats,
+                     bool compact_output) {
+  if (compact_output)
+    return;
+  *output << "--------------------Trace Stats-------------------\n";
+  size_t buf_num = 0;
+  for (const auto& buf : stats.buffer_stats()) {
+    *output << "Buffer " << buf_num++ << "\n"
+            << "  bytes_written: " << buf.bytes_written() << "\n"
+            << "  chunks_written: " << buf.chunks_written() << "\n"
+            << "  chunks_overwritten: " << buf.chunks_overwritten() << "\n"
+            << "  write_wrap_count: " << buf.write_wrap_count() << "\n"
+            << "  patches_succeeded: " << buf.patches_succeeded() << "\n"
+            << "  patches_failed: " << buf.patches_failed() << "\n"
+            << "  readaheads_succeeded: " << buf.readaheads_succeeded() << "\n"
+            << "  readaheads_failed: " << buf.readaheads_failed() << "\n"
+            << "  abi_violations: " << buf.abi_violations() << "\n";
+  }
+  *output << "producers_connected: " << stats.producers_connected() << "\n"
+          << "producers_seen: " << stats.producers_seen() << "\n"
+          << "data_sources_reg: " << stats.data_sources_registered() << "\n"
+          << "data_sources_seen: " << stats.data_sources_seen() << "\n"
+          << "tracing_sessions: " << stats.tracing_sessions() << "\n"
+          << "total_buffers: " << stats.total_buffers() << "\n";
+}
+
 int TraceToSummary(std::istream* input,
                    std::ostream* output,
                    bool compact_output) {
@@ -439,12 +466,14 @@
   uint64_t ftrace_inode_count = 0;
   std::set<uint64_t> resolved_map_inodes;
   std::set<uint64_t> resolved_scan_inodes;
+  protos::TraceStats last_stats;
 
   ForEachPacketInTrace(
-      input, [&start, &end, &ftrace_overwrites, &ftrace_event_counts,
-              &ftrace_timestamps, &tids_in_tree, &tids_in_events,
-              &ftrace_inodes, &ftrace_inode_count, &resolved_map_inodes,
-              &resolved_scan_inodes](const protos::TracePacket& packet) {
+      input,
+      [&start, &end, &ftrace_overwrites, &ftrace_event_counts,
+       &ftrace_timestamps, &tids_in_tree, &tids_in_events, &ftrace_inodes,
+       &ftrace_inode_count, &resolved_map_inodes, &resolved_scan_inodes,
+       &last_stats](const protos::TracePacket& packet) {
         if (packet.has_process_tree()) {
           const ProcessTree& tree = packet.process_tree();
           for (Process process : tree.processes()) {
@@ -468,6 +497,9 @@
               resolved_map_inodes.insert(entry.inode_number());
         }
 
+        if (packet.has_trace_stats())
+          last_stats = packet.trace_stats();
+
         if (!packet.has_ftrace_events())
           return;
 
@@ -511,6 +543,7 @@
   PrintProcessStats(output, tids_in_tree, tids_in_events, compact_output);
   PrintInodeStats(output, ftrace_inodes, ftrace_inode_count,
                   resolved_map_inodes, resolved_scan_inodes, compact_output);
+  PrintTraceStats(output, last_stats, compact_output);
 
   return 0;
 }