Merge "Project import generated by Copybara."
diff --git a/protos/perfetto/metrics/android/startup_metric.proto b/protos/perfetto/metrics/android/startup_metric.proto
index a93a6a5..61b8203 100644
--- a/protos/perfetto/metrics/android/startup_metric.proto
+++ b/protos/perfetto/metrics/android/startup_metric.proto
@@ -88,7 +88,7 @@
optional Slice full_startup = 1;
}
- // Next id: 8
+ // Next id: 11
message Startup {
// Random id uniquely identifying an app startup in this trace.
optional uint32 startup_id = 1;
@@ -99,6 +99,9 @@
// Name of the process launched
optional string process_name = 3;
+ // Name of the activity launched
+ optional string activity_name = 10;
+
// Did we ask the zygote for a new process
optional bool zygote_new_process = 4;
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 496ad35..21fa4ad 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -744,7 +744,7 @@
optional Slice full_startup = 1;
}
- // Next id: 8
+ // Next id: 11
message Startup {
// Random id uniquely identifying an app startup in this trace.
optional uint32 startup_id = 1;
@@ -755,6 +755,9 @@
// Name of the process launched
optional string process_name = 3;
+ // Name of the activity launched
+ optional string activity_name = 10;
+
// Did we ask the zygote for a new process
optional bool zygote_new_process = 4;
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index a836392..740fb1a 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -8042,7 +8042,7 @@
// Describes a process's attributes. Emitted as part of a TrackDescriptor,
// usually by the process's main thread.
//
-// Next id: 8.
+// Next id: 9.
message ProcessDescriptor {
optional int32 pid = 1;
repeated string cmdline = 2;
@@ -8054,6 +8054,19 @@
// provided in TracePacket are not supported.
optional int64 start_timestamp_ns = 7;
+ // The ID to link crashes to trace.
+ // Notes:
+ // * The ID is per process. So, each trace may contain many IDs, and you need
+ // to look for the ID from crashed process to find the crash report.
+ // * Having a "chrome-trace-id" in crash doesn't necessarily mean we can
+ // get an uploaded trace, since uploads could have failed.
+ // * On the other hand, if there was a crash during the session and trace was
+ // uploaded, it is very likely to find a crash report with the trace ID.
+ // * This is not crash ID or trace ID. It is just a random 64-bit number
+ // recorded in both traces and crashes. It is possible to have collisions,
+ // though very rare.
+ optional uint64 chrome_crash_trace_id = 8;
+
// ---------------------------------------------------------------------------
// Deprecated / legacy fields, which will be removed in the future:
// ---------------------------------------------------------------------------
diff --git a/protos/perfetto/trace/track_event/process_descriptor.proto b/protos/perfetto/trace/track_event/process_descriptor.proto
index 1a6388d..e6082cc 100644
--- a/protos/perfetto/trace/track_event/process_descriptor.proto
+++ b/protos/perfetto/trace/track_event/process_descriptor.proto
@@ -21,7 +21,7 @@
// Describes a process's attributes. Emitted as part of a TrackDescriptor,
// usually by the process's main thread.
//
-// Next id: 8.
+// Next id: 9.
message ProcessDescriptor {
optional int32 pid = 1;
repeated string cmdline = 2;
@@ -33,6 +33,19 @@
// provided in TracePacket are not supported.
optional int64 start_timestamp_ns = 7;
+ // The ID to link crashes to trace.
+ // Notes:
+ // * The ID is per process. So, each trace may contain many IDs, and you need
+ // to look for the ID from crashed process to find the crash report.
+ // * Having a "chrome-trace-id" in crash doesn't necessarily mean we can
+ // get an uploaded trace, since uploads could have failed.
+ // * On the other hand, if there was a crash during the session and trace was
+ // uploaded, it is very likely to find a crash report with the trace ID.
+ // * This is not crash ID or trace ID. It is just a random 64-bit number
+ // recorded in both traces and crashes. It is possible to have collisions,
+ // though very rare.
+ optional uint64 chrome_crash_trace_id = 8;
+
// ---------------------------------------------------------------------------
// Deprecated / legacy fields, which will be removed in the future:
// ---------------------------------------------------------------------------
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index 63a481a..ae85294 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -1234,7 +1234,128 @@
return util::OkStatus();
}
+ class MergedProfileSamplesEmitter {
+ public:
+ // The TraceFormatWriter must outlive this instance.
+ MergedProfileSamplesEmitter(TraceFormatWriter& writer) : writer_(writer) {}
+
+ uint64_t AddEventForUtid(UniqueTid utid,
+ int64_t ts,
+ CallsiteId callsite_id,
+ const Json::Value& event) {
+ auto current_sample = current_events_.find(utid);
+
+ // If there's a current entry for our thread and it matches the callsite
+ // of the new sample, update the entry with the new timestamp. Otherwise
+ // create a new entry.
+ if (current_sample != current_events_.end() &&
+ current_sample->second.callsite_id() == callsite_id) {
+ current_sample->second.UpdateWithNewSample(ts);
+ return current_sample->second.event_id();
+ } else {
+ if (current_sample != current_events_.end())
+ current_events_.erase(current_sample);
+
+ auto new_entry = current_events_.emplace(
+ std::piecewise_construct, std::forward_as_tuple(utid),
+ std::forward_as_tuple(writer_, callsite_id, ts, event));
+ return new_entry.first->second.event_id();
+ }
+ }
+
+ static uint64_t GenerateNewEventId() {
+ // "n"-phase events are nestable async events which get tied together
+ // with their id, so we need to give each one a unique ID as we only
+ // want the samples to show up on their own track in the trace-viewer
+ // but not nested together (unless they're nested under a merged event).
+ static size_t g_id_counter = 0;
+ return ++g_id_counter;
+ }
+
+ private:
+ class Sample {
+ public:
+ Sample(TraceFormatWriter& writer,
+ CallsiteId callsite_id,
+ int64_t ts,
+ const Json::Value& event)
+ : writer_(writer),
+ callsite_id_(callsite_id),
+ begin_ts_(ts),
+ end_ts_(ts),
+ event_(event),
+ event_id_(MergedProfileSamplesEmitter::GenerateNewEventId()),
+ sample_count_(1) {}
+
+ ~Sample() {
+ // No point writing a merged event if we only got a single sample
+ // as ExportCpuProfileSamples will already be writing the instant event.
+ if (sample_count_ == 1)
+ return;
+
+ event_["id"] = base::Uint64ToHexString(event_id_);
+
+ // Write the BEGIN event.
+ event_["ph"] = "b";
+ // We subtract 1us as a workaround for the first async event not
+ // nesting underneath the parent event if the timestamp is identical.
+ int64_t begin_in_us_ = begin_ts_ / 1000;
+ event_["ts"] = Json::Int64(std::min(begin_in_us_ - 1, begin_in_us_));
+ writer_.WriteCommonEvent(event_);
+
+ // Write the END event.
+ event_["ph"] = "e";
+ event_["ts"] = Json::Int64(end_ts_ / 1000);
+ // No need for args for the end event; remove them to save some space.
+ event_["args"].clear();
+ writer_.WriteCommonEvent(event_);
+ }
+
+ void UpdateWithNewSample(int64_t ts) {
+ // We assume samples for a given thread will appear in timestamp
+ // order; if this assumption stops holding true, we'll have to sort the
+ // samples first.
+ if (ts < end_ts_ || begin_ts_ > ts) {
+ PERFETTO_ELOG(
+ "Got an timestamp out of sequence while merging stack samples "
+ "during JSON export!\n");
+ PERFETTO_DCHECK(false);
+ }
+
+ end_ts_ = ts;
+ sample_count_++;
+ }
+
+ uint64_t event_id() const { return event_id_; }
+ CallsiteId callsite_id() const { return callsite_id_; }
+
+ public:
+ Sample(const Sample&) = delete;
+ Sample& operator=(const Sample&) = delete;
+ Sample& operator=(Sample&& value) = delete;
+
+ TraceFormatWriter& writer_;
+ CallsiteId callsite_id_;
+ int64_t begin_ts_;
+ int64_t end_ts_;
+ Json::Value event_;
+ uint64_t event_id_;
+ size_t sample_count_;
+ };
+
+ MergedProfileSamplesEmitter(const MergedProfileSamplesEmitter&) = delete;
+ MergedProfileSamplesEmitter& operator=(const MergedProfileSamplesEmitter&) =
+ delete;
+ MergedProfileSamplesEmitter& operator=(
+ MergedProfileSamplesEmitter&& value) = delete;
+
+ std::unordered_map<UniqueTid, Sample> current_events_;
+ TraceFormatWriter& writer_;
+ };
+
util::Status ExportCpuProfileSamples() {
+ MergedProfileSamplesEmitter merged_sample_emitter(writer_);
+
const tables::CpuProfileStackSampleTable& samples =
storage_->cpu_profile_stack_sample_table();
for (uint32_t i = 0; i < samples.row_count(); ++i) {
@@ -1256,13 +1377,6 @@
// samples.
event["tts"] = Json::Int64(1);
- // "n"-phase events are nestable async events which get tied together with
- // their id, so we need to give each one a unique ID as we only
- // want the samples to show up on their own track in the trace-viewer but
- // not nested together.
- static size_t g_id_counter = 0;
- event["id"] = base::Uint64ToHexString(++g_id_counter);
-
const auto& callsites = storage_->stack_profile_callsite_table();
const auto& frames = storage_->stack_profile_frame_table();
const auto& mappings = storage_->stack_profile_mapping_table();
@@ -1316,6 +1430,22 @@
// tid.
event["args"]["thread_id"] = Json::Int(pid_and_tid.second);
+ // Emit duration events for adjacent samples with the same callsite.
+ // For now, only do this when the trace has already been symbolized i.e.
+ // are not directly output by Chrome, to avoid interfering with other
+ // processing pipelines.
+ base::Optional<CallsiteId> opt_current_callsite_id =
+ samples.callsite_id()[i];
+
+ if (opt_current_callsite_id && storage_->symbol_table().row_count() > 0) {
+ uint64_t parent_event_id = merged_sample_emitter.AddEventForUtid(
+ utid, samples.ts()[i], *opt_current_callsite_id, event);
+ event["id"] = base::Uint64ToHexString(parent_event_id);
+ } else {
+ event["id"] = base::Uint64ToHexString(
+ MergedProfileSamplesEmitter::GenerateNewEventId());
+ }
+
writer_.WriteCommonEvent(event);
}
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index ea837e7..3694e9b 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -1540,6 +1540,12 @@
storage->mutable_cpu_profile_stack_sample_table()->Insert(
{kTimestamp, frame_callsite_2.id, utid, kProcessPriority});
+ storage->mutable_cpu_profile_stack_sample_table()->Insert(
+ {kTimestamp + 10000, frame_callsite_1.id, utid, kProcessPriority});
+
+ storage->mutable_cpu_profile_stack_sample_table()->Insert(
+ {kTimestamp + 20000, frame_callsite_1.id, utid, kProcessPriority});
+
base::TempFile temp_file = base::TempFile::Create();
FILE* output = fopen(temp_file.path().c_str(), "w+");
util::Status status = ExportJson(storage, output);
@@ -1548,7 +1554,10 @@
Json::Value result = ToJsonValue(ReadFile(output));
- EXPECT_EQ(result["traceEvents"].size(), 1u);
+ // The first sample should generate only a single instant event;
+ // the two following samples should also generate an additional [b, e] pair
+ // (the async duration event).
+ EXPECT_EQ(result["traceEvents"].size(), 5u);
Json::Value event = result["traceEvents"][0];
EXPECT_EQ(event["ph"].asString(), "n");
EXPECT_EQ(event["id"].asString(), "0x1");
@@ -1561,6 +1570,29 @@
"foo_func - foo_module_name [foo_module_id]\nbar_func - "
"bar_module_name [bar_module_id]\n");
EXPECT_EQ(event["args"]["process_priority"].asInt(), kProcessPriority);
+
+ event = result["traceEvents"][1];
+ EXPECT_EQ(event["ph"].asString(), "n");
+ EXPECT_EQ(event["id"].asString(), "0x2");
+ EXPECT_EQ(event["ts"].asInt64(), (kTimestamp + 10000) / 1000);
+
+ event = result["traceEvents"][2];
+ EXPECT_EQ(event["ph"].asString(), "n");
+ EXPECT_EQ(event["id"].asString(), "0x2");
+ EXPECT_EQ(event["ts"].asInt64(), (kTimestamp + 20000) / 1000);
+ Json::String second_callstack_ = event["args"]["frames"].asString();
+ EXPECT_EQ(second_callstack_, "foo_func - foo_module_name [foo_module_id]\n");
+
+ event = result["traceEvents"][3];
+ EXPECT_EQ(event["ph"].asString(), "b");
+ EXPECT_EQ(event["id"].asString(), "0x2");
+ EXPECT_EQ(event["ts"].asInt64(), (kTimestamp + 10000) / 1000 - 1);
+ EXPECT_EQ(event["args"]["frames"].asString(), second_callstack_);
+
+ event = result["traceEvents"][4];
+ EXPECT_EQ(event["ph"].asString(), "e");
+ EXPECT_EQ(event["id"].asString(), "0x2");
+ EXPECT_EQ(event["ts"].asInt64(), (kTimestamp + 20000) / 1000);
}
TEST_F(ExportJsonTest, ArgumentFilter) {
diff --git a/src/trace_processor/metrics/android/android_startup.sql b/src/trace_processor/metrics/android/android_startup.sql
index 18be8ee..78236dc 100644
--- a/src/trace_processor/metrics/android/android_startup.sql
+++ b/src/trace_processor/metrics/android/android_startup.sql
@@ -92,6 +92,8 @@
'activityResume',
'Choreographer#doFrame',
'inflate')
+ OR slice.name LIKE 'performResume:%'
+ OR slice.name LIKE 'performCreate:%'
GROUP BY 1, 2;
DROP TABLE IF EXISTS report_fully_drawn_per_launch;
@@ -153,6 +155,13 @@
LIMIT 1
)
),
+ 'activity_name', (
+ SELECT STR_SPLIT(name, ':', 1)
+ FROM main_process_slice s
+ WHERE s.launch_id = launches.id
+ AND (name LIKE 'performResume:%' OR name LIKE 'performCreate:%')
+ LIMIT 1
+ ),
'zygote_new_process', EXISTS(SELECT TRUE FROM zygote_forks_by_id WHERE id = launches.id),
'activity_hosting_process_count', (
SELECT COUNT(1) FROM launch_processes p
diff --git a/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor b/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor
index 3b231ca..854eaef 100644
--- a/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor
+++ b/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/test/trace_processor/startup/android_startup_breakdown.out b/test/trace_processor/startup/android_startup_breakdown.out
index e26b0b3..80e2c79 100644
--- a/test/trace_processor/startup/android_startup_breakdown.out
+++ b/test/trace_processor/startup/android_startup_breakdown.out
@@ -6,6 +6,7 @@
process: {
name: "com.google.android.calendar"
}
+ activity_name: "com.google.android.calendar.MainActivity"
zygote_new_process: true
to_first_frame {
dur_ns: 108
diff --git a/test/trace_processor/startup/android_startup_breakdown.py b/test/trace_processor/startup/android_startup_breakdown.py
index b7d2d7b..22b93f9 100644
--- a/test/trace_processor/startup/android_startup_breakdown.py
+++ b/test/trace_processor/startup/android_startup_breakdown.py
@@ -42,6 +42,12 @@
trace.add_atrace_end(ts=195, tid=1, pid=1)
trace.add_atrace_begin(ts=185, tid=3, pid=3, buf='bindApplication')
+trace.add_atrace_begin(
+ ts=188,
+ tid=3,
+ pid=3,
+ buf='performCreate:com.google.android.calendar.MainActivity')
+trace.add_atrace_end(ts=192, tid=3, pid=3)
trace.add_atrace_end(ts=195, tid=3, pid=3)
trace.add_atrace_async_end(