Merge "record_android_trace: allow temporary ADB failures"
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/json/json_trace_tokenizer.cc b/src/trace_processor/importers/json/json_trace_tokenizer.cc
index 695e692..aa2b259 100644
--- a/src/trace_processor/importers/json/json_trace_tokenizer.cc
+++ b/src/trace_processor/importers/json/json_trace_tokenizer.cc
@@ -33,8 +33,6 @@
namespace {
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
-
util::Status AppendUnescapedCharacter(char c,
bool is_escaping,
std::string* key) {
@@ -103,11 +101,8 @@
return ReadStringRes::kNeedsMoreData;
}
-#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
-
} // namespace
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
ReadDictRes ReadOneJsonDict(const char* start,
const char* end,
base::StringView* value,
@@ -365,7 +360,54 @@
}
return ReadSystemLineRes::kNeedsMoreData;
}
-#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
+base::Optional<json::TimeUnit> MaybeParseDisplayTimeUnit(
+ base::StringView buffer) {
+ size_t idx = buffer.find("\"displayTimeUnit\"");
+ if (idx == base::StringView::npos)
+ return base::nullopt;
+
+ base::StringView time_unit = buffer.substr(idx);
+ std::string key;
+ const char* value_start = nullptr;
+ ReadKeyRes res =
+ ReadOneJsonKey(time_unit.data(), time_unit.end(), &key, &value_start);
+
+ // If we didn't read the key successfully, it could have been that
+ // displayTimeUnit is used somewhere else in the dict (maybe as a value?) so
+ // just ignore it.
+ if (res == ReadKeyRes::kEndOfDictionary || res == ReadKeyRes::kFatalError ||
+ res == ReadKeyRes::kNeedsMoreData) {
+ return base::nullopt;
+ }
+
+ // Double check that we did actually get the displayTimeUnit key.
+ if (key != "displayTimeUnit")
+ return base::nullopt;
+
+ // If the value isn't a string, we can do little except bail.
+ if (value_start[0] != '"')
+ return base::nullopt;
+
+ std::string value;
+ const char* unused;
+ ReadStringRes value_res =
+ ReadOneJsonString(value_start + 1, time_unit.end(), &value, &unused);
+
+ // If we didn't read the value successfully, we could be in another key
+ // in a nested dictionary. Just ignore the error.
+ if (value_res == ReadStringRes::kFatalError ||
+ value_res == ReadStringRes::kNeedsMoreData) {
+ return base::nullopt;
+ }
+
+ if (value == "ns") {
+ return json::TimeUnit::kNs;
+ } else if (value == "ms") {
+ return json::TimeUnit::kMs;
+ }
+ return base::nullopt;
+}
JsonTraceTokenizer::JsonTraceTokenizer(TraceProcessorContext* ctx)
: context_(ctx) {}
@@ -375,28 +417,23 @@
size_t size) {
PERFETTO_DCHECK(json::IsJsonSupported());
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
buffer_.insert(buffer_.end(), data.get(), data.get() + size);
const char* buf = buffer_.data();
const char* next = buf;
const char* end = buf + buffer_.size();
- JsonTracker* json_tracker = JsonTracker::GetOrCreate(context_);
-
- // It's possible the displayTimeUnit key is at the end of the json
- // file so to be correct we ought to parse the whole file looking
- // for this key before parsing any events however this would require
- // two passes on the file so for now we only handle displayTimeUnit
- // correctly if it is at the beginning of the file.
- const base::StringView view(buf, size);
- if (view.find("\"displayTimeUnit\":\"ns\"") != base::StringView::npos) {
- json_tracker->SetTimeUnit(json::TimeUnit::kNs);
- } else if (view.find("\"displayTimeUnit\":\"ms\"") !=
- base::StringView::npos) {
- json_tracker->SetTimeUnit(json::TimeUnit::kMs);
- }
-
if (offset_ == 0) {
+ // It's possible the displayTimeUnit key is at the end of the json
+ // file so to be correct we ought to parse the whole file looking
+ // for this key before parsing any events however this would require
+ // two passes on the file so for now we only handle displayTimeUnit
+ // correctly if it is at the beginning of the file.
+ base::Optional<json::TimeUnit> timeunit =
+ MaybeParseDisplayTimeUnit(base::StringView(buf, size));
+ if (timeunit) {
+ JsonTracker::GetOrCreate(context_)->SetTimeUnit(*timeunit);
+ }
+
// Strip leading whitespace.
while (next != end && isspace(*next)) {
next++;
@@ -436,18 +473,8 @@
offset_ += static_cast<uint64_t>(next - buf);
buffer_.erase(buffer_.begin(), buffer_.begin() + (next - buf));
return util::OkStatus();
-#else
- perfetto::base::ignore_result(data);
- perfetto::base::ignore_result(size);
- perfetto::base::ignore_result(context_);
- perfetto::base::ignore_result(format_);
- perfetto::base::ignore_result(position_);
- perfetto::base::ignore_result(offset_);
- return util::ErrStatus("Cannot parse JSON trace due to missing JSON support");
-#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
}
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
util::Status JsonTraceTokenizer::ParseInternal(const char* start,
const char* end,
const char** out) {
@@ -491,6 +518,12 @@
return util::ErrStatus("displayTimeUnit too large");
if (time_unit != "ms" && time_unit != "ns")
return util::ErrStatus("displayTimeUnit unknown");
+ // If the displayTimeUnit came after the first chunk, the increment the
+ // too late stat.
+ if (offset_ != 0) {
+ context_->storage->IncrementStats(
+ stats::json_display_time_unit_too_late);
+ }
return ParseInternal(next, end, out);
} else {
// If we don't recognize the key, just ignore the rest of the trace and
@@ -595,7 +628,6 @@
*out = next;
return util::OkStatus();
}
-#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
void JsonTraceTokenizer::NotifyEndOfFile() {}
diff --git a/src/trace_processor/importers/json/json_trace_tokenizer.h b/src/trace_processor/importers/json/json_trace_tokenizer.h
index 86f5731..4daf661 100644
--- a/src/trace_processor/importers/json/json_trace_tokenizer.h
+++ b/src/trace_processor/importers/json/json_trace_tokenizer.h
@@ -20,6 +20,7 @@
#include <stdint.h>
#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/importers/json/json_utils.h"
#include "src/trace_processor/importers/systrace/systrace_line_tokenizer.h"
#include "src/trace_processor/storage/trace_storage.h"
@@ -32,7 +33,6 @@
class TraceProcessorContext;
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
// Visible for testing.
enum class ReadDictRes {
kFoundDict,
@@ -93,7 +93,11 @@
const char* end,
std::string* line,
const char** next);
-#endif
+
+// Parses the "displayTimeUnit" key from the given trace buffer
+// and returns the associated time unit if one exists.
+base::Optional<json::TimeUnit> MaybeParseDisplayTimeUnit(
+ base::StringView buffer);
// Reads a JSON trace in chunks and extracts top level json objects.
class JsonTraceTokenizer : public ChunkedTraceReader {
@@ -139,11 +143,9 @@
kEof,
};
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
util::Status ParseInternal(const char* start,
const char* end,
const char** next);
-#endif
TraceProcessorContext* const context_;
diff --git a/src/trace_processor/importers/json/json_trace_tokenizer_unittest.cc b/src/trace_processor/importers/json/json_trace_tokenizer_unittest.cc
index 051f52a..6a11a44 100644
--- a/src/trace_processor/importers/json/json_trace_tokenizer_unittest.cc
+++ b/src/trace_processor/importers/json/json_trace_tokenizer_unittest.cc
@@ -272,6 +272,58 @@
ASSERT_EQ(*line, R"({"ts": 149029, "foo": "bar"})");
}
+TEST(JsonTraceTokenizerTest, DisplayTimeUnit) {
+ ASSERT_EQ(MaybeParseDisplayTimeUnit(R"(
+ {
+ }
+ )"),
+ base::nullopt);
+ ASSERT_EQ(MaybeParseDisplayTimeUnit(R"(
+ {
+ "displayTimeUnit": 0
+ }
+ )"),
+ base::nullopt);
+ ASSERT_EQ(MaybeParseDisplayTimeUnit(R"(
+ {
+ "str": "displayTimeUnit"
+ }
+ )"),
+ base::nullopt);
+
+ ASSERT_EQ(MaybeParseDisplayTimeUnit(R"(
+ {
+ "traceEvents": [
+ {
+ "pid": 1, "tid": 1, "name": "test",
+ "ts": 1, "dur": 1000, "ph": "X", "cat": "fee"
+ }
+ ],
+ "displayTimeUnit": "ms"
+ }
+ )"),
+ json::TimeUnit::kMs);
+ ASSERT_EQ(MaybeParseDisplayTimeUnit(R"(
+ {
+ "traceEvents": [
+ {
+ "pid": 1, "tid": 1, "name": "test",
+ "ts": 1, "dur": 1000, "ph": "X", "cat": "fee"
+ }
+ ],
+ "displayTimeUnit": "ns"
+ }
+ )"),
+ json::TimeUnit::kNs);
+
+ ASSERT_EQ(MaybeParseDisplayTimeUnit(R"(
+ {
+ "displayTimeUnit":"ms"
+ }
+ )"),
+ json::TimeUnit::kMs);
+}
+
} // namespace
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/json/json_tracker.h b/src/trace_processor/importers/json/json_tracker.h
index d786bf1..167881e 100644
--- a/src/trace_processor/importers/json/json_tracker.h
+++ b/src/trace_processor/importers/json/json_tracker.h
@@ -47,6 +47,9 @@
base::Optional<int64_t> CoerceToTs(const Json::Value& value) {
return json::CoerceToTs(time_unit_, value);
}
+ base::Optional<int64_t> CoerceToTs(const std::string& value) {
+ return json::CoerceToTs(time_unit_, value);
+ }
private:
json::TimeUnit time_unit_ = json::TimeUnit::kUs;
diff --git a/src/trace_processor/importers/json/json_utils.cc b/src/trace_processor/importers/json/json_utils.cc
index 5d0b2d1..c44efa1 100644
--- a/src/trace_processor/importers/json/json_utils.cc
+++ b/src/trace_processor/importers/json/json_utils.cc
@@ -57,22 +57,8 @@
case Json::uintValue:
case Json::intValue:
return value.asInt64() * TimeUnitToNs(unit);
- case Json::stringValue: {
- std::string s = value.asString();
- size_t lhs_end = std::min<size_t>(s.find('.'), s.size());
- size_t rhs_start = std::min<size_t>(lhs_end + 1, s.size());
- base::Optional<int64_t> lhs = base::StringToInt64(s.substr(0, lhs_end));
- base::Optional<double> rhs =
- base::StringToDouble("0." + s.substr(rhs_start, std::string::npos));
- if ((!lhs.has_value() && lhs_end > 0) ||
- (!rhs.has_value() && rhs_start < s.size())) {
- return base::nullopt;
- }
- int64_t factor = TimeUnitToNs(unit);
- return lhs.value_or(0) * factor +
- static_cast<int64_t>(rhs.value_or(0) *
- static_cast<double>(factor));
- }
+ case Json::stringValue:
+ return CoerceToTs(unit, value.asString());
default:
return base::nullopt;
}
@@ -83,6 +69,29 @@
#endif
}
+base::Optional<int64_t> CoerceToTs(TimeUnit unit, const std::string& s) {
+ PERFETTO_DCHECK(IsJsonSupported());
+
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+ size_t lhs_end = std::min<size_t>(s.find('.'), s.size());
+ size_t rhs_start = std::min<size_t>(lhs_end + 1, s.size());
+ base::Optional<int64_t> lhs = base::StringToInt64(s.substr(0, lhs_end));
+ base::Optional<double> rhs =
+ base::StringToDouble("0." + s.substr(rhs_start, std::string::npos));
+ if ((!lhs.has_value() && lhs_end > 0) ||
+ (!rhs.has_value() && rhs_start < s.size())) {
+ return base::nullopt;
+ }
+ int64_t factor = TimeUnitToNs(unit);
+ return lhs.value_or(0) * factor +
+ static_cast<int64_t>(rhs.value_or(0) * static_cast<double>(factor));
+#else
+ perfetto::base::ignore_result(unit);
+ perfetto::base::ignore_result(s);
+ return base::nullopt;
+#endif
+}
+
base::Optional<int64_t> CoerceToInt64(const Json::Value& value) {
PERFETTO_DCHECK(IsJsonSupported());
diff --git a/src/trace_processor/importers/json/json_utils.h b/src/trace_processor/importers/json/json_utils.h
index f4a70b3..74a2c3a 100644
--- a/src/trace_processor/importers/json/json_utils.h
+++ b/src/trace_processor/importers/json/json_utils.h
@@ -42,6 +42,7 @@
enum class TimeUnit { kNs = 1, kUs = 1000, kMs = 1000000 };
base::Optional<int64_t> CoerceToTs(TimeUnit unit, const Json::Value& value);
+base::Optional<int64_t> CoerceToTs(TimeUnit unit, const std::string& value);
base::Optional<int64_t> CoerceToInt64(const Json::Value& value);
base::Optional<uint32_t> CoerceToUint32(const Json::Value& value);
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);
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index bc44fef..4a05a74 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -124,6 +124,10 @@
F(process_tracker_errors, kSingle, kError, kAnalysis, ""), \
F(json_tokenizer_failure, kSingle, kError, kTrace, ""), \
F(json_parser_failure, kSingle, kError, kTrace, ""), \
+ F(json_display_time_unit_too_late, kSingle, kError, kTrace, \
+ "The displayTimeUnit key came too late in the JSON trace so was " \
+ "ignored. Trace processor only supports displayTimeUnit appearing " \
+ "at the start of JSON traces"), \
F(heap_graph_invalid_string_id, kIndexed, kError, kTrace, ""), \
F(heap_graph_non_finalized_graph, kSingle, kError, kTrace, ""), \
F(heap_graph_malformed_packet, kIndexed, kError, kTrace, ""), \
diff --git a/ui/src/common/proto_ring_buffer.ts b/ui/src/common/proto_ring_buffer.ts
new file mode 100644
index 0000000..b03c1c9
--- /dev/null
+++ b/ui/src/common/proto_ring_buffer.ts
@@ -0,0 +1,156 @@
+// Copyright (C) 2021 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.
+
+import {assertTrue} from '../base/logging';
+
+// This class is the TypeScript equivalent of the identically-named C++ class in
+// //protozero/proto_ring_buffer.h. See comments in that header for a detailed
+// description. The architecture is identical.
+
+const kGrowBytes = 128 * 1024;
+const kMaxMsgSize = 64 * 1024 * 1024;
+
+export class ProtoRingBuffer {
+ private buf = new Uint8Array(kGrowBytes);
+ private fastpath?: Uint8Array;
+ private rd = 0;
+ private wr = 0;
+
+ // The caller must call ReadMessage() after each append() call.
+ // The |data| might be either copied in the internal ring buffer or returned
+ // (% subarray()) to the next ReadMessage() call.
+ append(data: Uint8Array) {
+ assertTrue(this.wr <= this.buf.length);
+ assertTrue(this.rd <= this.wr);
+
+ // If the last call to ReadMessage() consumed all the data in the buffer and
+ // there are no incomplete messages pending, restart from the beginning
+ // rather than keep ringing. This is the most common case.
+ if (this.rd === this.wr) {
+ this.rd = this.wr = 0;
+ }
+
+ // The caller is expected to issue a ReadMessage() after each append().
+ const dataLen = data.length;
+ if (dataLen === 0) return;
+ assertTrue(this.fastpath === undefined);
+ if (this.rd === this.wr) {
+ const msg = ProtoRingBuffer.tryReadMessage(data, 0, dataLen);
+ if (msg !== undefined &&
+ ((msg.byteOffset + msg.length) === (data.byteOffset + dataLen))) {
+ // Fastpath: in many cases, the underlying stream will effectively
+ // preserve the atomicity of messages for most small messages.
+ // In this case we can avoid the extra buffer roundtrip and return the
+ // original array (actually a subarray that skips the proto header).
+ // The next call to ReadMessage() will return this.
+ this.fastpath = msg;
+ return;
+ }
+ }
+
+ let avail = this.buf.length - this.wr;
+ if (dataLen > avail) {
+ // This whole section should be hit extremely rarely.
+
+ // Try first just recompacting the buffer by moving everything to the
+ // left. This can happen if we received "a message and a bit" on each
+ // append() call.
+ this.buf.copyWithin(0, this.rd, this.wr);
+ avail += this.rd;
+ this.wr -= this.rd;
+ this.rd = 0;
+ if (dataLen > avail) {
+ // Still not enough, expand the buffer.
+ let newSize = this.buf.length;
+ while (dataLen > newSize - this.wr) {
+ newSize += kGrowBytes;
+ }
+ assertTrue(newSize <= kMaxMsgSize * 2);
+ const newBuf = new Uint8Array(newSize);
+ newBuf.set(this.buf);
+ this.buf = newBuf;
+ // No need to touch rd / wr.
+ }
+ }
+
+ // Append the received data at the end of the ring buffer.
+ this.buf.set(data, this.wr);
+ this.wr += dataLen;
+ }
+
+ // Tries to extract a message from the ring buffer. If there is no message,
+ // or if the current message is still incomplete, returns undefined.
+ // The caller is expected to call this in a loop until it returns undefined.
+ // Note that a single write to Append() can yield more than one message
+ // (see ProtoRingBufferTest.CoalescingStream in the unittest).
+ readMessage(): Uint8Array|undefined {
+ if (this.fastpath !== undefined) {
+ assertTrue(this.rd === this.wr);
+ const msg = this.fastpath;
+ this.fastpath = undefined;
+ return msg;
+ }
+ assertTrue(this.rd <= this.wr);
+ if (this.rd >= this.wr) {
+ return undefined; // Completely empty.
+ }
+ const msg = ProtoRingBuffer.tryReadMessage(this.buf, this.rd, this.wr);
+ if (msg === undefined) return undefined;
+ assertTrue(msg.buffer === this.buf.buffer);
+ assertTrue(this.buf.byteOffset === 0);
+ this.rd = msg.byteOffset + msg.length;
+
+ // Deliberately returning a copy of the data with slice(). In various cases
+ // (streaming query response) the caller will hold onto the returned buffer.
+ // If we get to this point, |msg| is a view of the circular buffer that we
+ // will overwrite on the next calls to append().
+ return msg.slice();
+ }
+
+ private static tryReadMessage(
+ data: Uint8Array, dataStart: number, dataEnd: number): Uint8Array
+ |undefined {
+ assertTrue(dataEnd <= data.length);
+ let pos = dataStart;
+ if (pos >= dataEnd) return undefined;
+ const tag = data[pos++]; // Assume one-byte tag.
+ if (tag >= 0x80 || (tag & 0x07) !== 2 /* len delimited */) {
+ throw new Error(
+ `RPC framing error, unexpected tag ${tag} @ offset ${pos - 1}`);
+ }
+
+ let len = 0;
+ for (let shift = 0;; shift += 7) {
+ if (pos >= dataEnd) {
+ return undefined; // Not enough data to read varint.
+ }
+ const val = data[pos++];
+ len |= ((val & 0x7f) << shift) >>> 0;
+ if (val < 0x80) break;
+ }
+
+ if (len >= kMaxMsgSize) {
+ throw new Error(
+ `RPC framing error, message too large (${len} > ${kMaxMsgSize}`);
+ }
+ const end = pos + len;
+ if (end > dataEnd) return undefined;
+
+ // This is a subarray() and not a slice() because in the |fastpath| case
+ // we want to just return the original buffer pushed by append().
+ // In the slow-path (ring-buffer) case, the readMessage() above will create
+ // a copy via slice() before returning it.
+ return data.subarray(pos, end);
+ }
+}
diff --git a/ui/src/common/proto_ring_buffer_unittest.ts b/ui/src/common/proto_ring_buffer_unittest.ts
new file mode 100644
index 0000000..6c06ae0
--- /dev/null
+++ b/ui/src/common/proto_ring_buffer_unittest.ts
@@ -0,0 +1,140 @@
+// Copyright (C) 2021 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.
+
+import * as protobuf from 'protobufjs/minimal';
+import {assertTrue} from '../base/logging';
+
+import {ProtoRingBuffer} from './proto_ring_buffer';
+
+let seed = 1;
+
+// For reproducibility.
+function Rnd(max: number) {
+ seed = seed * 16807 % 2147483647;
+ return seed % max;
+}
+
+function MakeProtoMessage(fieldId: number, len: number) {
+ const writer = protobuf.Writer.create();
+ const tag = (fieldId << 3) | 2 /*length-delimited*/;
+ assertTrue(tag < 0x80 && (tag & 7) === 2);
+ writer.uint32(tag);
+ const data = new Uint8Array(len);
+ for (let i = 0; i < len; i++) {
+ data[i] = 48 + ((fieldId + i) % 73);
+ }
+ writer.bytes(data);
+ const res = writer.finish();
+ // For whatever reason the object returned by protobufjs' Writer cannot be
+ // directly .toEqual()-ed with Uint8Arrays.
+ const buf = new Uint8Array(res.length);
+ buf.set(res);
+ return buf;
+}
+
+test('ProtoRingBufferTest.Fastpath', () => {
+ const buf = new ProtoRingBuffer();
+
+ for (let rep = 0; rep < 3; rep++) {
+ let inputBuf = MakeProtoMessage(1, 32);
+ buf.append(inputBuf);
+ let msg = buf.readMessage();
+ expect(msg).toBeDefined();
+ expect(msg).toBeInstanceOf(Uint8Array);
+ expect(msg!.length).toBe(32);
+
+ // subarray(2) is to strip the proto preamble. The returned buffer starts at
+ // the start of the payload.
+ expect(msg).toEqual(inputBuf.subarray(2));
+
+ // When we hit the fastpath, the returned message should be a subarray of
+ // the same ArrayBuffer passed to append.
+ expect(msg!.buffer).toBe(inputBuf.buffer);
+
+ inputBuf = MakeProtoMessage(2, 32);
+ buf.append(inputBuf.subarray(0, 13));
+ expect(buf.readMessage()).toBeUndefined();
+ buf.append(inputBuf.subarray(13));
+ msg = buf.readMessage();
+ expect(msg).toBeDefined();
+ expect(msg).toBeInstanceOf(Uint8Array);
+ expect(msg).toEqual(inputBuf.subarray(2));
+ expect(msg!.buffer !== inputBuf.buffer).toBeTruthy();
+ }
+});
+
+test('ProtoRingBufferTest.CoalescingStream', () => {
+ const buf = new ProtoRingBuffer();
+
+ const mergedBuf = new Uint8Array(612);
+ const expected = new Array<Uint8Array>();
+ for (let i = 1, pos = 0; i <= 6; i++) {
+ const msg = MakeProtoMessage(i, 100);
+ expected.push(msg);
+ mergedBuf.set(msg, pos);
+ pos += msg.length;
+ }
+
+ const fragLens = [120, 20, 471, 1];
+ let fragSum = 0;
+ fragLens.map(fragLen => {
+ buf.append(mergedBuf.subarray(fragSum, fragSum + fragLen));
+ fragSum += fragLen;
+ for (;;) {
+ const msg = buf.readMessage();
+ if (msg === undefined) break;
+ const exp = expected.shift();
+ expect(exp).toBeDefined();
+ expect(msg).toEqual(exp!.subarray(-1 * msg.length));
+ }
+ });
+ expect(expected.length).toEqual(0);
+});
+
+
+test('ProtoRingBufferTest.RandomSizes', () => {
+ const buf = new ProtoRingBuffer();
+ const kNumMsg = 100;
+ const mergedBuf = new Uint8Array(1024 * 1024 * 32);
+ const expectedLengths = [];
+ let mergedLen = 0;
+ for (let i = 0; i < kNumMsg; i++) {
+ const fieldId = 1 + Rnd(15); // We support only one byte tag.
+ const rndVal = Rnd(1024);
+ let len = 1 + rndVal;
+ if ((rndVal % 100) < 5) {
+ len *= 1000;
+ }
+ const msg = MakeProtoMessage(fieldId, len);
+ assertTrue(mergedBuf.length >= mergedLen + msg.length);
+ expectedLengths.push(len);
+ mergedBuf.set(msg, mergedLen);
+ mergedLen += msg.length;
+ }
+
+ for (let fragSum = 0; fragSum < mergedLen;) {
+ let fragLen = 1 + Rnd(1024 * 32);
+ fragLen = Math.min(fragLen, mergedLen - fragSum);
+ buf.append(mergedBuf.subarray(fragSum, fragSum + fragLen));
+ fragSum += fragLen;
+ for (;;) {
+ const msg = buf.readMessage();
+ if (msg === undefined) break;
+ const expLen = expectedLengths.shift();
+ expect(expLen).toBeDefined();
+ expect(msg.length).toEqual(expLen);
+ }
+ }
+ expect(expectedLengths.length).toEqual(0);
+});