trace_processor: add code for parsing a trace

This CL utilises the previously introduced ProtoDecoder class to parse a
Trace into its important components. It doesn't do anything with the
parsed data yet but it lays the skeleton for writing to TraceStorage in
a subsequent CL.

Bug: 80416541
Change-Id: Id0422968adea16a98ec46b583057e440cd5274cf
diff --git a/include/perfetto/protozero/proto_decoder.h b/include/perfetto/protozero/proto_decoder.h
index 24d36f6..ce57b91 100644
--- a/include/perfetto/protozero/proto_decoder.h
+++ b/include/perfetto/protozero/proto_decoder.h
@@ -20,6 +20,7 @@
 #include <stdint.h>
 #include <memory>
 
+#include "perfetto/base/logging.h"
 #include "perfetto/protozero/proto_utils.h"
 
 namespace protozero {
@@ -41,8 +42,14 @@
     protozero::proto_utils::FieldType type;
     union {
       uint64_t int_value;
-      LengthDelimited length_value;
+      LengthDelimited length_limited;
     };
+
+    inline uint32_t as_uint32() const {
+      PERFETTO_DCHECK(type == proto_utils::FieldType::kFieldTypeVarInt ||
+                      type == proto_utils::FieldType::kFieldTypeFixed32);
+      return static_cast<uint32_t>(int_value);
+    }
   };
 
   // Creates a ProtoDecoder using the given |buffer| with size |length| bytes.
@@ -56,6 +63,9 @@
   // otherwise.
   bool IsEndOfBuffer();
 
+  // Resets the current position to the start of the buffer.
+  void Reset();
+
  private:
   const uint8_t* const buffer_;
   const uint64_t length_;
diff --git a/src/protozero/proto_decoder.cc b/src/protozero/proto_decoder.cc
index 64eda6f..3f38a3c 100644
--- a/src/protozero/proto_decoder.cc
+++ b/src/protozero/proto_decoder.cc
@@ -49,6 +49,11 @@
   PERFETTO_DCHECK(pos >= buffer_);
   PERFETTO_DCHECK(pos <= end);
 
+  // If we've already hit the end, just return an invalid field.
+  if (pos >= end) {
+    return field;
+  }
+
   uint64_t raw_field_id = 0;
   pos = ParseVarInt(pos, end, &raw_field_id);
 
@@ -102,8 +107,8 @@
       // We need to explicity check for zero to ensure that ParseVarInt doesn't
       // return zero because of running out of space in the buffer.
       if (*pos == 0) {
-        field.length_value.data = ++pos;
-        field.length_value.length = 0;
+        field.length_limited.data = ++pos;
+        field.length_limited.length = 0;
       } else {
         pos = ParseVarInt(pos, end, &field_intvalue);
 
@@ -115,8 +120,8 @@
         if (field_intvalue == 0 || pos + field_intvalue > end) {
           return field;
         }
-        field.length_value.data = pos;
-        field.length_value.length = field_intvalue;
+        field.length_limited.data = pos;
+        field.length_limited.length = field_intvalue;
         pos += field_intvalue;
       }
       break;
@@ -134,4 +139,8 @@
   return length_ == static_cast<uint64_t>(current_position_ - buffer_);
 }
 
+void ProtoDecoder::Reset() {
+  current_position_ = buffer_;
+}
+
 }  // namespace protozero
diff --git a/src/protozero/proto_decoder_unittest.cc b/src/protozero/proto_decoder_unittest.cc
index fff9dba..a3b5b69 100644
--- a/src/protozero/proto_decoder_unittest.cc
+++ b/src/protozero/proto_decoder_unittest.cc
@@ -49,9 +49,9 @@
 
   ASSERT_EQ(field.id, 1);
   ASSERT_EQ(field.type, proto_utils::FieldType::kFieldTypeLengthDelimited);
-  ASSERT_EQ(field.length_value.length, sizeof(kTestString) - 1);
+  ASSERT_EQ(field.length_limited.length, sizeof(kTestString) - 1);
   for (size_t i = 0; i < sizeof(kTestString) - 1; i++) {
-    ASSERT_EQ(field.length_value.data[i], kTestString[i]);
+    ASSERT_EQ(field.length_limited.data[i], kTestString[i]);
   }
 }
 
@@ -93,7 +93,7 @@
     ASSERT_EQ(exp.type, field.type);
 
     if (field.type == kFieldTypeLengthDelimited) {
-      ASSERT_EQ(exp.int_value, field.length_value.length);
+      ASSERT_EQ(exp.int_value, field.length_limited.length);
     } else {
       ASSERT_EQ(exp.int_value, field.int_value);
     }
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 6419157..c5605f6 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -35,10 +35,13 @@
     "trace_parser.h",
     "trace_storage.cc",
     "trace_storage.h",
+    "virtual_destructors.cc",
   ]
   deps = [
     "../../gn:default_deps",
+    "../../protos/perfetto/trace:lite",
     "../base",
+    "../protozero",
   ]
 }
 
@@ -52,5 +55,7 @@
     ":lib",
     "../../gn:default_deps",
     "../../gn:gtest_deps",
+    "../../protos/perfetto/trace:lite",
+    "../base",
   ]
 }
diff --git a/src/trace_processor/trace_parser.cc b/src/trace_processor/trace_parser.cc
index 355c6f1..be45cee 100644
--- a/src/trace_processor/trace_parser.cc
+++ b/src/trace_processor/trace_parser.cc
@@ -16,17 +16,39 @@
 
 #include "src/trace_processor/trace_parser.h"
 
+#include <string>
+
+#include "perfetto/base/logging.h"
 #include "perfetto/base/utils.h"
+#include "perfetto/trace/trace.pb.h"
+#include "perfetto/trace/trace_packet.pb.h"
 
 namespace perfetto {
 namespace trace_processor {
 
+using protozero::ProtoDecoder;
+using protozero::proto_utils::kFieldTypeLengthDelimited;
+
+namespace {
+bool FindIntField(ProtoDecoder* decoder,
+                  uint32_t field_id,
+                  uint64_t* field_value) {
+  for (auto f = decoder->ReadField(); f.id != 0; f = decoder->ReadField()) {
+    if (f.id == field_id) {
+      *field_value = f.int_value;
+      return true;
+    }
+  }
+  return false;
+}
+}  // namespace
+
 TraceParser::TraceParser(BlobReader* reader,
                          TraceStorage* trace,
                          uint32_t chunk_size_b)
     : reader_(reader), trace_(trace), chunk_size_b_(chunk_size_b) {}
 
-void TraceParser::LoadNextChunk() {
+void TraceParser::ParseNextChunk() {
   if (!buffer_)
     buffer_.reset(new uint8_t[chunk_size_b_]);
 
@@ -34,11 +56,121 @@
   if (read == 0)
     return;
 
-  // TODO(lalitm): actually parse the data read here.
-  base::ignore_result(trace_);
+  ProtoDecoder decoder(buffer_.get(), read);
+  for (auto fld = decoder.ReadField(); fld.id != 0; fld = decoder.ReadField()) {
+    if (fld.id != protos::Trace::kPacketFieldNumber) {
+      PERFETTO_ELOG("Non-trace packet field found in root Trace proto");
+      continue;
+    }
+    ParsePacket(fld.length_limited.data,
+                static_cast<uint32_t>(fld.length_limited.length));
+  }
 
   offset_ += read;
 }
 
+void TraceParser::ParsePacket(const uint8_t* data, uint32_t length) {
+  ProtoDecoder decoder(data, length);
+  for (auto fld = decoder.ReadField(); fld.id != 0; fld = decoder.ReadField()) {
+    switch (fld.id) {
+      case protos::TracePacket::kFtraceEventsFieldNumber:
+        ParseFtraceEventBundle(
+            fld.length_limited.data,
+            static_cast<uint32_t>(fld.length_limited.length));
+        break;
+      default:
+        break;
+    }
+  }
+  PERFETTO_DCHECK(decoder.IsEndOfBuffer());
+}
+
+void TraceParser::ParseFtraceEventBundle(const uint8_t* data, uint32_t length) {
+  ProtoDecoder decoder(data, length);
+  uint64_t cpu = 0;
+  if (!FindIntField(&decoder, protos::FtraceEventBundle::kCpuFieldNumber,
+                    &cpu)) {
+    PERFETTO_ELOG("CPU field not found in FtraceEventBundle");
+    return;
+  }
+  decoder.Reset();
+
+  for (auto fld = decoder.ReadField(); fld.id != 0; fld = decoder.ReadField()) {
+    switch (fld.id) {
+      case protos::FtraceEventBundle::kEventFieldNumber:
+        ParseFtraceEvent(static_cast<uint32_t>(cpu), fld.length_limited.data,
+                         static_cast<uint32_t>(fld.length_limited.length));
+        break;
+      default:
+        break;
+    }
+  }
+  PERFETTO_DCHECK(decoder.IsEndOfBuffer());
+}
+
+void TraceParser::ParseFtraceEvent(uint32_t cpu,
+                                   const uint8_t* data,
+                                   uint32_t length) {
+  ProtoDecoder decoder(data, length);
+  uint64_t timestamp = 0;
+  if (!FindIntField(&decoder, protos::FtraceEvent::kTimestampFieldNumber,
+                    &timestamp)) {
+    PERFETTO_ELOG("Timestamp field not found in FtraceEvent");
+    return;
+  }
+  decoder.Reset();
+
+  for (auto fld = decoder.ReadField(); fld.id != 0; fld = decoder.ReadField()) {
+    switch (fld.id) {
+      case protos::FtraceEvent::kSchedSwitchFieldNumber:
+        PERFETTO_DCHECK(timestamp > 0);
+        ParseSchedSwitch(cpu, timestamp, fld.length_limited.data,
+                         static_cast<uint32_t>(fld.length_limited.length));
+        break;
+      default:
+        break;
+    }
+  }
+  PERFETTO_DCHECK(decoder.IsEndOfBuffer());
+}
+
+void TraceParser::ParseSchedSwitch(uint32_t cpu,
+                                   uint64_t timestamp,
+                                   const uint8_t* data,
+                                   uint32_t length) {
+  ProtoDecoder decoder(data, length);
+
+  uint32_t prev_pid = 0;
+  uint32_t next_pid = 0;
+  std::string next_comm;
+  for (auto fld = decoder.ReadField(); fld.id != 0; fld = decoder.ReadField()) {
+    switch (fld.id) {
+      case protos::SchedSwitchFtraceEvent::kPrevPidFieldNumber:
+        prev_pid = fld.as_uint32();
+        break;
+      case protos::SchedSwitchFtraceEvent::kNextPidFieldNumber:
+        next_pid = fld.as_uint32();
+        break;
+      case protos::SchedSwitchFtraceEvent::kNextCommFieldNumber:
+        next_comm =
+            std::string(reinterpret_cast<const char*>(fld.length_limited.data),
+                        static_cast<size_t>(fld.length_limited.length));
+        break;
+      default:
+        break;
+    }
+  }
+
+  // TODO(lalitm): store these fields inside the TraceStorage class.
+  base::ignore_result(cpu);
+  base::ignore_result(timestamp);
+  base::ignore_result(trace_);
+  base::ignore_result(prev_pid);
+  base::ignore_result(next_pid);
+  base::ignore_result(next_comm);
+
+  PERFETTO_DCHECK(decoder.IsEndOfBuffer());
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/trace_parser.h b/src/trace_processor/trace_parser.h
index 309c892..e4cb818 100644
--- a/src/trace_processor/trace_parser.h
+++ b/src/trace_processor/trace_parser.h
@@ -20,6 +20,7 @@
 #include <stdint.h>
 #include <memory>
 
+#include "perfetto/protozero/proto_decoder.h"
 #include "src/trace_processor/blob_reader.h"
 #include "src/trace_processor/trace_storage.h"
 
@@ -34,9 +35,17 @@
   // from a trace file with these chunks parsed into |trace|.
   TraceParser(BlobReader* reader, TraceStorage* trace, uint32_t chunk_size_b);
 
-  void LoadNextChunk();
+  void ParseNextChunk();
 
  private:
+  void ParsePacket(const uint8_t* data, uint32_t length);
+  void ParseFtraceEventBundle(const uint8_t* data, uint32_t length);
+  void ParseFtraceEvent(uint32_t cpu, const uint8_t* data, uint32_t length);
+  void ParseSchedSwitch(uint32_t cpu,
+                        uint64_t timestamp,
+                        const uint8_t* data,
+                        uint32_t length);
+
   BlobReader* const reader_;
   TraceStorage* const trace_;
   const uint32_t chunk_size_b_;
diff --git a/src/trace_processor/trace_parser_unittest.cc b/src/trace_processor/trace_parser_unittest.cc
index 3f454a2..5e4d2a7 100644
--- a/src/trace_processor/trace_parser_unittest.cc
+++ b/src/trace_processor/trace_parser_unittest.cc
@@ -18,6 +18,9 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/trace/trace.pb.h"
+#include "perfetto/trace/trace_packet.pb.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -27,8 +30,40 @@
 using ::testing::InSequence;
 using ::testing::Invoke;
 
+class FakeStringBlobReader : public BlobReader {
+ public:
+  FakeStringBlobReader(const std::string& data) : data_(data) {}
+  ~FakeStringBlobReader() override {}
+
+  uint32_t Read(uint64_t offset, uint32_t len, uint8_t* dst) override {
+    PERFETTO_CHECK(offset <= data_.size());
+    uint32_t rsize =
+        std::min(static_cast<uint32_t>(data_.size() - offset), len);
+    memcpy(dst, data_.c_str() + offset, rsize);
+    return rsize;
+  }
+
+ private:
+  std::string data_;
+};
+
 TEST(TraceParser, LoadSinglePacket) {
-  // TODO(lalitm): write this test.
+  protos::Trace trace;
+
+  auto* bundle = trace.add_packet()->mutable_ftrace_events();
+  bundle->set_cpu(10);
+
+  auto* event = bundle->add_event();
+  event->set_timestamp(1000);
+
+  auto* sched_switch = event->mutable_sched_switch();
+  sched_switch->set_prev_pid(10);
+  sched_switch->set_next_prio(100);
+
+  FakeStringBlobReader reader(trace.SerializeAsString());
+  TraceStorage storage;
+  TraceParser parser(&reader, &storage, 1024);
+  parser.ParseNextChunk();
 }
 
 TEST(TraceParser, LoadMultiplePacket) {
diff --git a/src/trace_processor/virtual_destructors.cc b/src/trace_processor/virtual_destructors.cc
new file mode 100644
index 0000000..26c6a0c
--- /dev/null
+++ b/src/trace_processor/virtual_destructors.cc
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open foo Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/blob_reader.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+BlobReader::~BlobReader() {}
+
+}  // namespace trace_processor
+}  // namespace perfetto