ftrace_reader: Add dynamic parser (part 5/5)

In the fifth and final part of this change we start inferring the ftrace
types from the types listed in the format files. This makes us robust
against changes to the width of fields in the raw binary format.

To summarise the overall state after this CL:
- We have auto-generated protos for a small number ftrace events.
  These live in protos/ftrace/
- We hardcoded information (proto field ids, proto field types, and the name of the field
  in the ftrace format files) about these protos into event_info.cc where we can read it
  at runtime.
- We combine this information with information (field size, field offset)
  acquired at initialization to build a ProtoTranslationTable.
- We use this table to parse the raw trace buffer in cpu_reader.

We are robust against the following kinds of changes:
The version of Linux we happen to be running on:
- has new fields (we ignore them)
- has missing fields, compared to the proto (we ignore them)
- has a field who's offset has changed (transparent to consumer)
- has a field who's size has changed (consumer will cast)
- has a field who's type has changed (we ignore it)
- the fields have been reordered (transparent to consumer)

Bug: 70328826
Change-Id: I8b3442bf1da9305909b9977a656d9f29b631ccf4
diff --git a/src/ftrace_reader/cpu_reader.cc b/src/ftrace_reader/cpu_reader.cc
index cdcd9f1..842b5eb 100644
--- a/src/ftrace_reader/cpu_reader.cc
+++ b/src/ftrace_reader/cpu_reader.cc
@@ -289,6 +289,13 @@
     case kUint64ToUint64:
       ReadIntoVarInt<uint64_t>(field_start, field_id, message);
       return true;
+    case kInt32ToInt32:
+    case kInt32ToInt64:
+      ReadIntoVarInt<int32_t>(field_start, field_id, message);
+      return true;
+    case kInt64ToInt64:
+      ReadIntoVarInt<int64_t>(field_start, field_id, message);
+      return true;
     case kFixedCStringToString:
       // TODO(hjd): Add AppendMaxLength string to protozero.
       return ReadIntoString(field_start, field_start + field.ftrace_size,
@@ -297,6 +304,9 @@
       // TODO(hjd): Kernel-dive to check this how size:0 char fields work.
       return ReadIntoString(field_start, end, field.proto_field_id, message);
   }
+  // Not reached, for gcc.
+  PERFETTO_CHECK(false);
+  return false;
 }
 
 }  // namespace perfetto
diff --git a/src/ftrace_reader/event_info.cc b/src/ftrace_reader/event_info.cc
index fe24d61..350dc52 100644
--- a/src/ftrace_reader/event_info.cc
+++ b/src/ftrace_reader/event_info.cc
@@ -19,13 +19,9 @@
 namespace perfetto {
 namespace {
 
-Field FieldFromNameIdType(const char* name,
-                          size_t id,
-                          ProtoFieldType type,
-                          FtraceFieldType ftrace_type) {
+Field FieldFromNameIdType(const char* name, size_t id, ProtoFieldType type) {
   Field field{};
   field.ftrace_name = name;
-  field.ftrace_type = ftrace_type;
   field.proto_field_id = id;
   field.proto_field_type = type;
   return field;
@@ -44,8 +40,7 @@
     event->name = "print";
     event->group = "ftrace";
     event->proto_field_id = 3;
-    event->fields.push_back(
-        FieldFromNameIdType("buf", 2, kProtoString, kFtraceCString));
+    event->fields.push_back(FieldFromNameIdType("buf", 2, kProtoString));
   }
 
   {
@@ -54,20 +49,13 @@
     event->name = "sched_switch";
     event->group = "sched";
     event->proto_field_id = 4;
-    event->fields.push_back(
-        FieldFromNameIdType("prev_comm", 1, kProtoString, kFtraceFixedCString));
-    event->fields.push_back(
-        FieldFromNameIdType("prev_pid", 2, kProtoUint32, kFtraceUint32));
-    event->fields.push_back(
-        FieldFromNameIdType("prev_prio", 3, kProtoUint32, kFtraceUint32));
-    event->fields.push_back(
-        FieldFromNameIdType("prev_state", 4, kProtoUint64, kFtraceUint64));
-    event->fields.push_back(
-        FieldFromNameIdType("next_comm", 5, kProtoString, kFtraceFixedCString));
-    event->fields.push_back(
-        FieldFromNameIdType("next_pid", 6, kProtoUint32, kFtraceUint32));
-    event->fields.push_back(
-        FieldFromNameIdType("next_prio", 7, kProtoUint32, kFtraceUint32));
+    event->fields.push_back(FieldFromNameIdType("prev_comm", 1, kProtoString));
+    event->fields.push_back(FieldFromNameIdType("prev_pid", 2, kProtoInt32));
+    event->fields.push_back(FieldFromNameIdType("prev_prio", 3, kProtoInt32));
+    event->fields.push_back(FieldFromNameIdType("prev_state", 4, kProtoInt64));
+    event->fields.push_back(FieldFromNameIdType("next_comm", 5, kProtoString));
+    event->fields.push_back(FieldFromNameIdType("next_pid", 6, kProtoInt32));
+    event->fields.push_back(FieldFromNameIdType("next_prio", 7, kProtoInt32));
   }
 
   return events;
@@ -76,8 +64,7 @@
 std::vector<Field> GetStaticCommonFieldsInfo() {
   std::vector<Field> fields;
 
-  fields.push_back(
-      FieldFromNameIdType("common_pid", 2, kProtoUint32, kFtraceUint32));
+  fields.push_back(FieldFromNameIdType("common_pid", 2, kProtoInt32));
 
   return fields;
 }
@@ -91,11 +78,19 @@
     *out = kUint32ToUint64;
   } else if (ftrace == kFtraceUint64 && proto == kProtoUint64) {
     *out = kUint64ToUint64;
+  } else if (ftrace == kFtraceInt32 && proto == kProtoInt32) {
+    *out = kInt32ToInt32;
+  } else if (ftrace == kFtraceInt32 && proto == kProtoInt64) {
+    *out = kInt32ToInt64;
+  } else if (ftrace == kFtraceInt64 && proto == kProtoInt64) {
+    *out = kInt64ToInt64;
   } else if (ftrace == kFtraceFixedCString && proto == kProtoString) {
     *out = kFixedCStringToString;
   } else if (ftrace == kFtraceCString && proto == kProtoString) {
     *out = kCStringToString;
   } else {
+    PERFETTO_DLOG("No translation strategy for '%s' -> '%s'", ToString(ftrace),
+                  ToString(proto));
     return false;
   }
   return true;
diff --git a/src/ftrace_reader/event_info.h b/src/ftrace_reader/event_info.h
index 4bc3a89..65303b0 100644
--- a/src/ftrace_reader/event_info.h
+++ b/src/ftrace_reader/event_info.h
@@ -47,6 +47,8 @@
 enum FtraceFieldType {
   kFtraceUint32 = 1,
   kFtraceUint64,
+  kFtraceInt32,
+  kFtraceInt64,
   kFtraceFixedCString,
   kFtraceCString,
 };
@@ -58,6 +60,9 @@
   kUint32ToUint32 = 1,
   kUint32ToUint64,
   kUint64ToUint64,
+  kInt32ToInt32,
+  kInt32ToInt64,
+  kInt64ToInt64,
   kFixedCStringToString,
   kCStringToString,
 };
@@ -106,8 +111,12 @@
       return "uint32";
     case kFtraceUint64:
       return "uint64";
+    case kFtraceInt32:
+      return "int32";
+    case kFtraceInt64:
+      return "int64";
     case kFtraceFixedCString:
-      return "char[16]";
+      return "fixed length null terminated string";
     case kFtraceCString:
       return "null terminated string";
   }
diff --git a/src/ftrace_reader/event_info_unittest.cc b/src/ftrace_reader/event_info_unittest.cc
index 50fe103..fb02d1b 100644
--- a/src/ftrace_reader/event_info_unittest.cc
+++ b/src/ftrace_reader/event_info_unittest.cc
@@ -46,8 +46,7 @@
       ASSERT_FALSE(field.ftrace_offset);
       ASSERT_FALSE(field.ftrace_size);
       ASSERT_FALSE(field.strategy);
-      // TODO(hjd): Re-instate this after we decide this at runtime.
-      // ASSERT_FALSE(field.ftrace_type);
+      ASSERT_FALSE(field.ftrace_type);
     }
   }
 }
@@ -64,8 +63,7 @@
     ASSERT_FALSE(field.ftrace_offset);
     ASSERT_FALSE(field.ftrace_size);
     ASSERT_FALSE(field.strategy);
-    // TODO(hjd): Re-instate this after we decide this at runtime.
-    // ASSERT_FALSE(field.ftrace_type);
+    ASSERT_FALSE(field.ftrace_type);
   }
 }
 
diff --git a/src/ftrace_reader/proto_translation_table.cc b/src/ftrace_reader/proto_translation_table.cc
index f0c30eb..9a88888 100644
--- a/src/ftrace_reader/proto_translation_table.cc
+++ b/src/ftrace_reader/proto_translation_table.cc
@@ -17,6 +17,7 @@
 #include "proto_translation_table.h"
 
 #include <algorithm>
+#include <regex>
 
 #include "event_info.h"
 #include "ftrace_procfs.h"
@@ -53,17 +54,17 @@
   PERFETTO_DCHECK(field->proto_field_type);
   PERFETTO_DCHECK(!field->ftrace_offset);
   PERFETTO_DCHECK(!field->ftrace_size);
-  // TODO(hjd): Re-instate this after we decide this at runtime.
-  // PERFETTO_DCHECK(!field.ftrace_type);
+  PERFETTO_DCHECK(!field->ftrace_type);
 
-  // TODO(hjd): Set field.ftrace_type here.
+  bool success = InferFtraceType(ftrace_field.type_and_name, ftrace_field.size,
+                                 ftrace_field.is_signed, &field->ftrace_type);
   field->ftrace_offset = ftrace_field.offset;
   field->ftrace_size = ftrace_field.size;
 
-  bool can_consume = SetTranslationStrategy(
-      field->ftrace_type, field->proto_field_type, &field->strategy);
+  success &= SetTranslationStrategy(field->ftrace_type, field->proto_field_type,
+                                    &field->strategy);
 
-  return can_consume;
+  return success;
 }
 
 // For each field in |fields| find the matching field from |ftrace_fields| (by
@@ -101,8 +102,53 @@
   return fields_end;
 }
 
+bool StartsWith(const std::string& str, const std::string& prefix) {
+  return str.compare(0, prefix.length(), prefix) == 0;
+}
+
 }  // namespace
 
+// This is similar but different from InferProtoType (see ftrace_to_proto.cc).
+// TODO(hjd): Fold FtraceEvent(::Field) into Event.
+bool InferFtraceType(const std::string& type_and_name,
+                     size_t size,
+                     bool is_signed,
+                     FtraceFieldType* out) {
+  // Fixed length strings: e.g. "char foo[16]" we don't care about the number
+  // since we get the size as it's own field. Somewhat awkwardly these fields
+  // are both fixed size and null terminated meaning that we can't just drop
+  // them directly into the protobuf (since if the string is shorter than 15
+  // charatcors we).
+  if (std::regex_match(type_and_name, std::regex(R"(char \w+\[\d+\])"))) {
+    *out = kFtraceFixedCString;
+    return true;
+  }
+
+  // Variable length strings: "char foo" + size: 0 (as in 'print').
+  if (StartsWith(type_and_name, "char ") && size == 0) {
+    *out = kFtraceCString;
+    return true;
+  }
+
+  // Ints of various sizes:
+  if (size == 4 && is_signed) {
+    *out = kFtraceInt32;
+    return true;
+  } else if (size == 4 && !is_signed) {
+    *out = kFtraceUint32;
+    return true;
+  } else if (size == 8 && is_signed) {
+    *out = kFtraceInt64;
+    return true;
+  } else if (size == 8 && !is_signed) {
+    *out = kFtraceUint64;
+    return true;
+  }
+
+  PERFETTO_DLOG("Could not infer ftrace type for '%s'", type_and_name.c_str());
+  return false;
+}
+
 // static
 std::unique_ptr<ProtoTranslationTable> ProtoTranslationTable::Create(
     const FtraceProcfs* ftrace_procfs,
diff --git a/src/ftrace_reader/proto_translation_table.h b/src/ftrace_reader/proto_translation_table.h
index f3a726d..53a4fc1 100644
--- a/src/ftrace_reader/proto_translation_table.h
+++ b/src/ftrace_reader/proto_translation_table.h
@@ -38,6 +38,11 @@
 }  // namespace pbzero
 }  // namespace protos
 
+bool InferFtraceType(const std::string& type_and_name,
+                     size_t size,
+                     bool is_signed,
+                     FtraceFieldType* out);
+
 class ProtoTranslationTable {
  public:
   // This method mutates the |events| and |common_fields| vectors to
diff --git a/src/ftrace_reader/proto_translation_table_unittest.cc b/src/ftrace_reader/proto_translation_table_unittest.cc
index 0978a15..b35aa58 100644
--- a/src/ftrace_reader/proto_translation_table_unittest.cc
+++ b/src/ftrace_reader/proto_translation_table_unittest.cc
@@ -134,9 +134,7 @@
       event->fields.emplace_back(Field{});
       Field* field = &event->fields.back();
       field->proto_field_id = 501;
-      // TODO(hjd): Remove.
       field->proto_field_type = kProtoString;
-      field->ftrace_type = kFtraceFixedCString;
       field->ftrace_name = "field_a";
     }
 
@@ -146,8 +144,6 @@
       Field* field = &event->fields.back();
       field->proto_field_id = 502;
       field->proto_field_type = kProtoString;
-      // TODO(hjd): Remove.
-      field->ftrace_type = kFtraceUint32;
       field->ftrace_name = "field_b";
     }
 
@@ -157,8 +153,6 @@
       Field* field = &event->fields.back();
       field->proto_field_id = 503;
       field->proto_field_type = kProtoString;
-      // TODO(hjd): Remove.
-      field->ftrace_type = kFtraceCString;
       field->ftrace_name = "field_c";
     }
 
@@ -167,9 +161,7 @@
       event->fields.emplace_back(Field{});
       Field* field = &event->fields.back();
       field->proto_field_id = 504;
-      // TODO(hjd): Remove.
       field->proto_field_type = kProtoUint64;
-      field->ftrace_type = kFtraceUint32;
       field->ftrace_name = "field_e";
     }
   }
@@ -207,6 +199,24 @@
   EXPECT_EQ(field_e.strategy, kUint32ToUint64);
 }
 
+TEST(TranslationTable, InferFtraceType) {
+  FtraceFieldType type;
+
+  ASSERT_TRUE(InferFtraceType("char * foo", 0, false, &type));
+  EXPECT_EQ(type, kFtraceCString);
+
+  ASSERT_TRUE(InferFtraceType("char foo[16]", 16, false, &type));
+  EXPECT_EQ(type, kFtraceFixedCString);
+
+  ASSERT_TRUE(InferFtraceType("char foo[64]", 64, false, &type));
+  EXPECT_EQ(type, kFtraceFixedCString);
+
+  ASSERT_TRUE(InferFtraceType("u32 foo", 4, false, &type));
+  EXPECT_EQ(type, kFtraceUint32);
+
+  EXPECT_FALSE(InferFtraceType("foo", 64, false, &type));
+}
+
 TEST(TranslationTable, Getters) {
   std::vector<Field> common_fields;
   std::vector<Event> events;