Merge "trace_processor: clean up SystraceParser"
diff --git a/include/perfetto/ext/base/string_view.h b/include/perfetto/ext/base/string_view.h
index dce2fdc..4f16f90 100644
--- a/include/perfetto/ext/base/string_view.h
+++ b/include/perfetto/ext/base/string_view.h
@@ -119,7 +119,7 @@
   }
 
   std::string ToStdString() const {
-    return data_ == nullptr ? "" : std::string(data_, size_);
+    return size_ == 0 ? "" : std::string(data_, size_);
   }
 
   uint64_t Hash() const {
diff --git a/src/base/string_view_unittest.cc b/src/base/string_view_unittest.cc
index bb00035..5bda159 100644
--- a/src/base/string_view_unittest.cc
+++ b/src/base/string_view_unittest.cc
@@ -34,6 +34,8 @@
   EXPECT_EQ(StringView("ax", 1), StringView("ay", 1));
   EXPECT_EQ(StringView("ax", 1), StringView("a"));
   EXPECT_EQ(StringView("ax", 1), "a");
+  EXPECT_EQ(StringView(reinterpret_cast<const char*>(0x100), 0).ToStdString(),
+            std::string(""));
   EXPECT_EQ(StringView("foo|", 3).ToStdString(), std::string("foo"));
   EXPECT_TRUE(StringView("x") != StringView(""));
   EXPECT_TRUE(StringView("") != StringView("y"));
diff --git a/src/trace_processor/importers/systrace/systrace_parser.cc b/src/trace_processor/importers/systrace/systrace_parser.cc
index 607b2ba..b9655d4 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser.cc
@@ -59,7 +59,7 @@
                                     int64_t value) {
   systrace_utils::SystraceTracePoint point{};
   point.name = name;
-  point.value = static_cast<double>(value);
+  point.value = value;
 
   // Hardcode the tgid to 0 (i.e. no tgid available) because zero events can
   // come from kernel threads and as we group kernel threads into the kthreadd
@@ -108,7 +108,7 @@
   // the UI.
   point.tgid = 0;
 
-  point.value = static_cast<double>(value);
+  point.value = value;
   // Some versions of this trace point fill trace_type with one of (B/E/C),
   // others use the trace_begin boolean and only support begin/end events:
   if (trace_type == 0) {
@@ -164,7 +164,7 @@
     case 'S':
     case 'F': {
       StringId name_id = context_->storage->InternString(point.name);
-      int64_t cookie = static_cast<int64_t>(point.value);
+      int64_t cookie = point.value;
       UniquePid upid =
           context_->process_tracker->GetOrCreateProcess(point.tgid);
 
@@ -223,7 +223,8 @@
         // Promote ScreenState to its own top level counter.
         TrackId track =
             context_->track_tracker->InternGlobalCounterTrack(screen_state_id_);
-        context_->event_tracker->PushCounter(ts, point.value, track);
+        context_->event_tracker->PushCounter(
+            ts, static_cast<double>(point.value), track);
         return;
       }
 
@@ -243,7 +244,8 @@
         track_id =
             context_->track_tracker->InternProcessCounterTrack(name_id, upid);
       }
-      context_->event_tracker->PushCounter(ts, point.value, track_id);
+      context_->event_tracker->PushCounter(ts, static_cast<double>(point.value),
+                                           track_id);
     }
   }
 }
diff --git a/src/trace_processor/importers/systrace/systrace_parser.h b/src/trace_processor/importers/systrace/systrace_parser.h
index 1c0b9e2..a369af9 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.h
+++ b/src/trace_processor/importers/systrace/systrace_parser.h
@@ -48,30 +48,24 @@
 
   static SystraceTracePoint C(uint32_t tgid,
                               base::StringView name,
-                              double value,
-                              base::StringView category_group = "") {
-    return SystraceTracePoint('C', tgid, std::move(name), value,
-                              category_group);
+                              int64_t value) {
+    return SystraceTracePoint('C', tgid, std::move(name), value);
   }
 
   static SystraceTracePoint S(uint32_t tgid,
                               base::StringView name,
-                              double value) {
-    return SystraceTracePoint('S', tgid, std::move(name), value);
+                              int64_t cookie) {
+    return SystraceTracePoint('S', tgid, std::move(name), cookie);
   }
 
   static SystraceTracePoint F(uint32_t tgid,
                               base::StringView name,
-                              double value) {
-    return SystraceTracePoint('F', tgid, std::move(name), value);
+                              int64_t cookie) {
+    return SystraceTracePoint('F', tgid, std::move(name), cookie);
   }
 
-  SystraceTracePoint(char p,
-                     uint32_t tg,
-                     base::StringView n,
-                     double v,
-                     base::StringView c = "")
-      : phase(p), tgid(tg), name(std::move(n)), value(v), category_group(c) {}
+  SystraceTracePoint(char p, uint32_t tg, base::StringView n, int64_t v)
+      : phase(p), tgid(tg), name(std::move(n)), value(v) {}
 
   // Phase can be one of B, E, C, S, F.
   char phase = '\0';
@@ -81,11 +75,8 @@
   // For phase = 'B' and phase = 'C' only.
   base::StringView name;
 
-  // For phase = 'C' only.
-  double value = 0;
-
-  // For phase = 'C' only (from Chrome).
-  base::StringView category_group;
+  // For phase = 'C' (counter value) and 'B', 'F' (async cookie).
+  int64_t value = 0;
 
   // Visible for unittesting.
   friend std::ostream& operator<<(std::ostream& os,
@@ -103,112 +94,94 @@
 // 4. C|1636|wq:monitor|0
 // 5. S|1636|frame_capture|123
 // 6. F|1636|frame_capture|456
+// Counters emitted by chromium can have a further "category group" appended
+// ("Blob" in the example below). We ignore the category group.
 // 7. C|3209|TransfersBytesPendingOnDisk-value|0|Blob
-// Visible for unittesting.
-inline SystraceParseResult ParseSystraceTracePoint(base::StringView str,
-                                                   SystraceTracePoint* out) {
-  const char* s = str.data();
-  size_t len = str.size();
+inline SystraceParseResult ParseSystraceTracePoint(
+    base::StringView str_untrimmed,
+    SystraceTracePoint* out) {
   *out = {};
 
-  // We don't support empty events.
-  if (len == 0)
-    return SystraceParseResult::kFailure;
-
-  constexpr const char* kClockSyncPrefix = "trace_event_clock_sync:";
-  if (len >= strlen(kClockSyncPrefix) &&
-      strncmp(kClockSyncPrefix, s, strlen(kClockSyncPrefix)) == 0)
-    return SystraceParseResult::kUnsupported;
-
-  char ph = s[0];
-  if (ph != 'B' && ph != 'E' && ph != 'C' && ph != 'S' && ph != 'F')
-    return SystraceParseResult::kFailure;
-
-  out->phase = ph;
-
-  // We only support E events with no arguments.
-  if (len == 1 && ph != 'E')
-    return SystraceParseResult::kFailure;
-
-  // If str matches '[BEC]\|[0-9]+[\|\n]?' set tgid_length to the length of
-  // the number. Otherwise return kFailure.
-  if (len >= 2 && s[1] != '|' && s[1] != '\n')
-    return SystraceParseResult::kFailure;
-
-  size_t tgid_length = 0;
-  for (size_t i = 2; i < len; i++) {
-    if (s[i] == '|' || s[i] == '\n') {
+  // Strip trailing \n and \0. StringViews are not null-terminated, but the
+  // writer could have appended a stray \0 depending on where the trace comes
+  // from.
+  size_t len = str_untrimmed.size();
+  for (; len > 0; --len) {
+    char last_char = str_untrimmed.at(len - 1);
+    if (last_char != '\n' && last_char != '\0')
       break;
-    }
-    if (s[i] < '0' || s[i] > '9')
-      return SystraceParseResult::kFailure;
-    tgid_length++;
   }
+  base::StringView str = str_untrimmed.substr(0, len);
 
-  // If len == 1, tgid_length will be 0 which will ensure we don't do
-  // an out of bounds read.
-  std::string tgid_str(s + 2, tgid_length);
-  out->tgid = base::StringToUInt32(tgid_str).value_or(0);
+  size_t off = 0;
 
-  switch (ph) {
-    case 'B': {
-      size_t name_index = 2 + tgid_length + 1;
-      if (name_index > len || s[name_index - 1] != '|')
+  // This function reads the next field up to the next '|', '\0' or end(). It
+  // advances |off| as it goes through fields.
+  auto read_next_field = [&off, &str, len]() {
+    for (size_t field_start = off;; ++off) {
+      char c = off >= len ? '\0' : str.at(off);
+      if (c == '|' || c == '\0') {
+        auto res = str.substr(field_start, off - field_start);
+        ++off;  // Eat the separator.
+        return res;
+      }
+    }
+  };
+
+  auto f0_phase = read_next_field();
+  if (PERFETTO_UNLIKELY(f0_phase.empty()))
+    return SystraceParseResult::kFailure;
+  out->phase = f0_phase.at(0);
+
+  auto f1_tgid = read_next_field();
+  auto opt_tgid = base::StringToUInt32(f1_tgid.ToStdString());
+  out->tgid = opt_tgid.value_or(0);
+  const bool has_tgid = opt_tgid.has_value();
+
+  switch (out->phase) {
+    case 'B': {  // Begin thread-scoped synchronous slice.
+      if (!has_tgid)
         return SystraceParseResult::kFailure;
-      out->name = base::StringView(
-          s + name_index, len - name_index - (s[len - 1] == '\n' ? 1 : 0));
-      if (out->name.empty()) {
-        static const char kEmptySliceName[] = "[empty slice name]";
-        out->name = base::StringView(kEmptySliceName);
+      auto f2_name = str.substr(off);  // It's fine even if |off| >= end().
+      if (f2_name.empty()) {
+        out->name = base::StringView("[empty slice name]");
+      } else {
+        out->name = f2_name;
       }
       return SystraceParseResult::kSuccess;
     }
-    case 'E': {
+    case 'E':  // End thread-scoped synchronous slice.
+      // Some non-Android traces (Flutter) use just "E" (aosp/1244409). Allow
+      // empty TGID on end slices. By design they are thread-scoped anyways.
+      return SystraceParseResult::kSuccess;
+    case 'S':    // Begin of async slice.
+    case 'F': {  // End of async slice.
+      auto f2_name = read_next_field();
+      auto f3_cookie = read_next_field();
+      auto maybe_cookie = base::StringToInt64(f3_cookie.ToStdString());
+      if (PERFETTO_UNLIKELY(!has_tgid || f2_name.empty() || f3_cookie.empty() ||
+                            !maybe_cookie)) {
+        return SystraceParseResult::kFailure;
+      }
+      out->name = f2_name;
+      out->value = *maybe_cookie;
       return SystraceParseResult::kSuccess;
     }
-    case 'S':
-    case 'F':
-    case 'C': {
-      size_t name_index = 2 + tgid_length + 1;
-      base::Optional<size_t> name_length;
-      for (size_t i = name_index; i < len; i++) {
-        if (s[i] == '|') {
-          name_length = i - name_index;
-          break;
-        }
-      }
-      if (!name_length.has_value())
-        return SystraceParseResult::kFailure;
-      out->name = base::StringView(s + name_index, name_length.value());
-
-      size_t value_index = name_index + name_length.value() + 1;
-      size_t value_pipe = str.find('|', value_index);
-      size_t value_len = value_pipe == base::StringView::npos
-                             ? len - value_index
-                             : value_pipe - value_index;
-      if (value_len == 0)
-        return SystraceParseResult::kFailure;
-      if (s[value_index + value_len - 1] == '\n')
-        value_len--;
-      std::string value_str(s + value_index, value_len);
-      base::Optional<double> maybe_value = base::StringToDouble(value_str);
-      if (!maybe_value.has_value()) {
+    case 'C': {  // Counter.
+      auto f2_name = read_next_field();
+      auto f3_value = read_next_field();
+      auto maybe_value = base::StringToInt64(f3_value.ToStdString());
+      if (PERFETTO_UNLIKELY(!has_tgid || f2_name.empty() || f3_value.empty() ||
+                            !maybe_value)) {
         return SystraceParseResult::kFailure;
       }
-      out->value = maybe_value.value();
-
-      if (value_pipe != base::StringView::npos) {
-        size_t group_len = len - value_pipe - 1;
-        if (group_len == 0)
-          return SystraceParseResult::kFailure;
-        if (s[len - 1] == '\n')
-          group_len--;
-        out->category_group = base::StringView(s + value_pipe + 1, group_len);
-      }
-
+      out->name = f2_name;
+      out->value = *maybe_value;
       return SystraceParseResult::kSuccess;
     }
     default:
+      if (str.find("trace_event_clock_sync:") == 0)
+        return SystraceParseResult::kUnsupported;
       return SystraceParseResult::kFailure;
   }
 }
diff --git a/src/trace_processor/importers/systrace/systrace_parser_unittest.cc b/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
index 563df7f..d0c89cf 100644
--- a/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
@@ -39,6 +39,9 @@
   ASSERT_EQ(ParseSystraceTracePoint("||\n", &result), Result::kFailure);
   ASSERT_EQ(ParseSystraceTracePoint("B", &result), Result::kFailure);
   ASSERT_EQ(ParseSystraceTracePoint("B\n", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("C\n", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("S\n", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("F\n", &result), Result::kFailure);
   ASSERT_EQ(ParseSystraceTracePoint("C", &result), Result::kFailure);
   ASSERT_EQ(ParseSystraceTracePoint("S", &result), Result::kFailure);
   ASSERT_EQ(ParseSystraceTracePoint("F", &result), Result::kFailure);
@@ -64,14 +67,13 @@
   ASSERT_EQ(ParseSystraceTracePoint("E|42", &result), Result::kSuccess);
   EXPECT_EQ(result, SystraceTracePoint::E(42));
 
-  ASSERT_EQ(ParseSystraceTracePoint("C|543|foo|", &result), Result::kFailure);
   ASSERT_EQ(ParseSystraceTracePoint("C|543|foo|8", &result), Result::kSuccess);
   EXPECT_EQ(result, SystraceTracePoint::C(543, "foo", 8));
 
-  ASSERT_EQ(ParseSystraceTracePoint("C|543|foo|8|", &result), Result::kFailure);
-  ASSERT_EQ(ParseSystraceTracePoint("C|543|foo|8|group", &result),
-            Result::kSuccess);
-  EXPECT_EQ(result, SystraceTracePoint::C(543, "foo", 8, "group"));
+  ASSERT_EQ(
+      ParseSystraceTracePoint("C|543|foo|8|chromium_group_ignored", &result),
+      Result::kSuccess);
+  EXPECT_EQ(result, SystraceTracePoint::C(543, "foo", 8));
 
   ASSERT_EQ(ParseSystraceTracePoint("S|", &result), Result::kFailure);