processor: Export thread instruction counts to JSON

Adds support for exporting thread instruction counts from imported
TrackEvent traces.

Bug: 130786269
Change-Id: I5367af57632e1efbb5951a5dccb59b340b5d4ad6
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index 1cc5b27..ba505c3 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -284,6 +284,8 @@
       const auto& virtual_track_slices = storage->virtual_track_slices();
       int64_t thread_ts_ns = 0;
       int64_t thread_duration_ns = 0;
+      int64_t thread_instruction_count = 0;
+      int64_t thread_instruction_delta = 0;
       base::Optional<uint32_t> vtrack_slice_row =
           virtual_track_slices.FindRowForSliceId(i);
       if (vtrack_slice_row) {
@@ -291,12 +293,22 @@
             virtual_track_slices.thread_timestamp_ns()[*vtrack_slice_row];
         thread_duration_ns =
             virtual_track_slices.thread_duration_ns()[*vtrack_slice_row];
+        thread_ts_ns =
+            virtual_track_slices.thread_timestamp_ns()[*vtrack_slice_row];
+        thread_instruction_count =
+            virtual_track_slices.thread_instruction_counts()[*vtrack_slice_row];
+        thread_instruction_delta =
+            virtual_track_slices.thread_instruction_deltas()[*vtrack_slice_row];
       }
 
       if (thread_ts_ns > 0) {
         event["tts"] = Json::Int64(thread_ts_ns / 1000);
         event["use_async_tts"] = Json::Int(1);
       }
+      if (thread_instruction_count > 0) {
+        event["ticount"] = Json::Int64(thread_instruction_count);
+        event["use_async_tts"] = Json::Int(1);
+      }
 
       int64_t duration_ns = slices.durations()[i];
       if (duration_ns == 0) {  // Instant async event.
@@ -315,6 +327,10 @@
             event["tts"] =
                 Json::Int64((thread_ts_ns + thread_duration_ns) / 1000);
           }
+          if (thread_instruction_count > 0) {
+            event["ticount"] = Json::Int64(
+                (thread_instruction_count + thread_instruction_delta));
+          }
           event.removeMember("args");
           writer->WriteCommonEvent(event);
         }
@@ -323,12 +339,18 @@
       const auto& thread_slices = storage->thread_slices();
       int64_t thread_ts_ns = 0;
       int64_t thread_duration_ns = 0;
+      int64_t thread_instruction_count = 0;
+      int64_t thread_instruction_delta = 0;
       base::Optional<uint32_t> thread_slice_row =
           thread_slices.FindRowForSliceId(i);
       if (thread_slice_row) {
         thread_ts_ns = thread_slices.thread_timestamp_ns()[*thread_slice_row];
         thread_duration_ns =
             thread_slices.thread_duration_ns()[*thread_slice_row];
+        thread_instruction_count =
+            thread_slices.thread_instruction_counts()[*thread_slice_row];
+        thread_instruction_delta =
+            thread_slices.thread_instruction_deltas()[*thread_slice_row];
       }
       int64_t duration_ns = slices.durations()[i];
       if (duration_ns == 0) {  // Instant event.
@@ -342,6 +364,9 @@
           if (thread_ts_ns > 0) {
             event["tts"] = Json::Int64(thread_ts_ns / 1000);
           }
+          if (thread_instruction_count > 0) {
+            event["ticount"] = Json::Int64(thread_instruction_count);
+          }
           event["tid"] = thread.tid;
           event["s"] = "t";
         } else if (slices.types()[i] == RefType::kRefUpid) {
@@ -374,10 +399,16 @@
         }
         if (thread_ts_ns > 0) {
           event["tts"] = Json::Int64(thread_ts_ns / 1000);
-          // Only write duration for complete events.
+          // Only write thread duration for completed events.
           if (duration_ns > 0)
             event["tdur"] = Json::Int64(thread_duration_ns / 1000);
         }
+        if (thread_instruction_count > 0) {
+          event["ticount"] = Json::Int64(thread_instruction_count);
+          // Only write thread instruction delta for completed events.
+          if (duration_ns > 0)
+            event["tidelta"] = Json::Int64(thread_instruction_delta);
+        }
         writer->WriteCommonEvent(event);
       }
     }
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index e79e722..fef2f3f 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -61,6 +61,8 @@
   const int64_t kDuration = 10000;
   const int64_t kThreadTimestamp = 20000000;
   const int64_t kThreadDuration = 20000;
+  const int64_t kThreadInstructionCount = 30000000;
+  const int64_t kThreadInstructionDelta = 30000;
   const int64_t kThreadID = 100;
   const char* kCategory = "cat";
   const char* kName = "name";
@@ -71,8 +73,9 @@
   StringId name_id = storage.InternString(base::StringView(kName));
   storage.mutable_nestable_slices()->AddSlice(
       kTimestamp, kDuration, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
-  storage.mutable_thread_slices()->AddThreadSlice(0, kThreadTimestamp,
-                                                  kThreadDuration, 0, 0);
+  storage.mutable_thread_slices()->AddThreadSlice(
+      0, kThreadTimestamp, kThreadDuration, kThreadInstructionCount,
+      kThreadInstructionDelta);
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -91,6 +94,8 @@
   EXPECT_EQ(event["dur"].asInt64(), kDuration / 1000);
   EXPECT_EQ(event["tts"].asInt64(), kThreadTimestamp / 1000);
   EXPECT_EQ(event["tdur"].asInt64(), kThreadDuration / 1000);
+  EXPECT_EQ(event["ticount"].asInt64(), kThreadInstructionCount);
+  EXPECT_EQ(event["tidelta"].asInt64(), kThreadInstructionDelta);
   EXPECT_EQ(event["tid"].asUInt(), kThreadID);
   EXPECT_EQ(event["cat"].asString(), kCategory);
   EXPECT_EQ(event["name"].asString(), kName);
@@ -101,6 +106,8 @@
   const int64_t kDuration = -1;
   const int64_t kThreadTimestamp = 20000000;
   const int64_t kThreadDuration = -1;
+  const int64_t kThreadInstructionCount = 30000000;
+  const int64_t kThreadInstructionDelta = -1;
   const int64_t kThreadID = 100;
   const char* kCategory = "cat";
   const char* kName = "name";
@@ -111,8 +118,9 @@
   StringId name_id = storage.InternString(base::StringView(kName));
   storage.mutable_nestable_slices()->AddSlice(
       kTimestamp, kDuration, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
-  storage.mutable_thread_slices()->AddThreadSlice(0, kThreadTimestamp,
-                                                  kThreadDuration, 0, 0);
+  storage.mutable_thread_slices()->AddThreadSlice(
+      0, kThreadTimestamp, kThreadDuration, kThreadInstructionCount,
+      kThreadInstructionDelta);
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -131,6 +139,8 @@
   EXPECT_FALSE(event.isMember("dur"));
   EXPECT_EQ(event["tts"].asInt64(), kThreadTimestamp / 1000);
   EXPECT_FALSE(event.isMember("tdur"));
+  EXPECT_EQ(event["ticount"].asInt64(), kThreadInstructionCount);
+  EXPECT_FALSE(event.isMember("tidelta"));
   EXPECT_EQ(event["tid"].asUInt(), kThreadID);
   EXPECT_EQ(event["cat"].asString(), kCategory);
   EXPECT_EQ(event["name"].asString(), kName);