Merge "Revert "Add new fields with fixed64 type for flow ids in TrackEvent.""
diff --git a/.gitignore b/.gitignore
index bb3f624..3bd8337 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,4 @@
perf.data*
TAGS
/*.pftrace
+examples/sdk/build/
diff --git a/Android.bp b/Android.bp
index 30b7d3a..c45934a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1936,6 +1936,7 @@
":perfetto_src_trace_processor_util_interned_message_view",
":perfetto_src_trace_processor_util_proto_to_args_parser",
":perfetto_src_trace_processor_util_protozero_to_text",
+ ":perfetto_src_trace_processor_util_stack_traces_util",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_processor_views_views",
":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
@@ -9045,8 +9046,11 @@
"src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals.sql",
"src/trace_processor/metrics/sql/chrome/chrome_performance_mark_hashes.sql",
"src/trace_processor/metrics/sql/chrome/chrome_processes.sql",
+ "src/trace_processor/metrics/sql/chrome/chrome_scroll_jank_caused_by_scheduling.sql",
"src/trace_processor/metrics/sql/chrome/chrome_slice_names.sql",
+ "src/trace_processor/metrics/sql/chrome/chrome_stack_samples_for_task.sql",
"src/trace_processor/metrics/sql/chrome/chrome_tasks.sql",
+ "src/trace_processor/metrics/sql/chrome/chrome_tasks_delaying_input_processing.sql",
"src/trace_processor/metrics/sql/chrome/chrome_thread_slice.sql",
"src/trace_processor/metrics/sql/chrome/chrome_unsymbolized_args.sql",
"src/trace_processor/metrics/sql/chrome/chrome_user_event_hashes.sql",
@@ -9349,6 +9353,14 @@
],
}
+// GN: //src/trace_processor/util:stack_traces_util
+filegroup {
+ name: "perfetto_src_trace_processor_util_stack_traces_util",
+ srcs: [
+ "src/trace_processor/util/stack_traces_util.cc",
+ ],
+}
+
// GN: //src/trace_processor/util:unittests
filegroup {
name: "perfetto_src_trace_processor_util_unittests",
@@ -10650,6 +10662,7 @@
":perfetto_src_trace_processor_util_interned_message_view",
":perfetto_src_trace_processor_util_proto_to_args_parser",
":perfetto_src_trace_processor_util_protozero_to_text",
+ ":perfetto_src_trace_processor_util_stack_traces_util",
":perfetto_src_trace_processor_util_unittests",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_processor_views_unittests",
@@ -10978,6 +10991,7 @@
":perfetto_src_trace_processor_util_interned_message_view",
":perfetto_src_trace_processor_util_proto_to_args_parser",
":perfetto_src_trace_processor_util_protozero_to_text",
+ ":perfetto_src_trace_processor_util_stack_traces_util",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_processor_views_views",
"src/trace_processor/trace_processor_shell.cc",
@@ -11149,6 +11163,7 @@
":perfetto_src_trace_processor_util_interned_message_view",
":perfetto_src_trace_processor_util_proto_to_args_parser",
":perfetto_src_trace_processor_util_protozero_to_text",
+ ":perfetto_src_trace_processor_util_stack_traces_util",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_processor_views_views",
":perfetto_src_traceconv_lib",
diff --git a/BUILD b/BUILD
index c9bce0b..591153d 100644
--- a/BUILD
+++ b/BUILD
@@ -1261,8 +1261,11 @@
"src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals.sql",
"src/trace_processor/metrics/sql/chrome/chrome_performance_mark_hashes.sql",
"src/trace_processor/metrics/sql/chrome/chrome_processes.sql",
+ "src/trace_processor/metrics/sql/chrome/chrome_scroll_jank_caused_by_scheduling.sql",
"src/trace_processor/metrics/sql/chrome/chrome_slice_names.sql",
+ "src/trace_processor/metrics/sql/chrome/chrome_stack_samples_for_task.sql",
"src/trace_processor/metrics/sql/chrome/chrome_tasks.sql",
+ "src/trace_processor/metrics/sql/chrome/chrome_tasks_delaying_input_processing.sql",
"src/trace_processor/metrics/sql/chrome/chrome_thread_slice.sql",
"src/trace_processor/metrics/sql/chrome/chrome_unsymbolized_args.sql",
"src/trace_processor/metrics/sql/chrome/chrome_user_event_hashes.sql",
@@ -1494,6 +1497,15 @@
],
)
+# GN target: //src/trace_processor/util:stack_traces_util
+perfetto_filegroup(
+ name = "src_trace_processor_util_stack_traces_util",
+ srcs = [
+ "src/trace_processor/util/stack_traces_util.cc",
+ "src/trace_processor/util/stack_traces_util.h",
+ ],
+)
+
# GN target: //src/trace_processor/util:util
perfetto_filegroup(
name = "src_trace_processor_util_util",
@@ -4042,6 +4054,7 @@
":src_trace_processor_util_interned_message_view",
":src_trace_processor_util_proto_to_args_parser",
":src_trace_processor_util_protozero_to_text",
+ ":src_trace_processor_util_stack_traces_util",
":src_trace_processor_util_util",
":src_trace_processor_views_views",
],
@@ -4157,6 +4170,7 @@
":src_trace_processor_util_interned_message_view",
":src_trace_processor_util_proto_to_args_parser",
":src_trace_processor_util_protozero_to_text",
+ ":src_trace_processor_util_stack_traces_util",
":src_trace_processor_util_util",
":src_trace_processor_views_views",
"src/trace_processor/trace_processor_shell.cc",
@@ -4228,6 +4242,7 @@
":src_profiling_deobfuscator",
":src_profiling_symbolizer_symbolize_database",
":src_profiling_symbolizer_symbolizer",
+ ":src_trace_processor_util_stack_traces_util",
":src_traceconv_pprofbuilder",
":src_traceconv_utils",
],
@@ -4327,6 +4342,7 @@
":src_trace_processor_util_interned_message_view",
":src_trace_processor_util_proto_to_args_parser",
":src_trace_processor_util_protozero_to_text",
+ ":src_trace_processor_util_stack_traces_util",
":src_trace_processor_util_util",
":src_trace_processor_views_views",
":src_traceconv_lib",
diff --git a/CHANGELOG b/CHANGELOG
index 052d8be..15cac4e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,8 +1,10 @@
Unreleased:
Tracing service and probes:
* Add android.statsd datasource.
+ * Removed log spam about sys.trace.traced_started in standalone builds.
Trace Processor:
- *
+ * Deprecate calling NotifyEndOfFile more than once: Flush should instead be
+ used for all but the final call.
UI:
*
SDK:
diff --git a/examples/sdk/BUILD.gn b/examples/sdk/BUILD.gn
index 8a49c88..219d642 100644
--- a/examples/sdk/BUILD.gn
+++ b/examples/sdk/BUILD.gn
@@ -38,3 +38,14 @@
"../../src/base",
]
}
+
+executable("example_startup_trace") {
+ sources = [ "example_startup_trace.cc" ]
+ defines = [ "PERFETTO_SDK_EXAMPLE_USE_INTERNAL_HEADERS" ]
+ testonly = true
+ deps = [
+ "../..:libperfetto_client_experimental",
+ "../../gn:default_deps",
+ "../../src/base",
+ ]
+}
diff --git a/examples/sdk/CMakeLists.txt b/examples/sdk/CMakeLists.txt
index a2f74bd..ed15e93 100644
--- a/examples/sdk/CMakeLists.txt
+++ b/examples/sdk/CMakeLists.txt
@@ -31,6 +31,7 @@
trace_categories.cc)
add_executable(example_custom_data_source example_custom_data_source.cc)
add_executable(example_console example_console.cc trace_categories.cc)
+add_executable(example_startup_trace example_startup_trace.cc)
target_link_libraries(example perfetto
${CMAKE_THREAD_LIBS_INIT})
@@ -40,6 +41,8 @@
${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(example_console perfetto
${CMAKE_THREAD_LIBS_INIT})
+target_link_libraries(example_startup_trace perfetto
+ ${CMAKE_THREAD_LIBS_INIT})
# On Android we also need the logging library.
if (ANDROID)
@@ -47,6 +50,7 @@
target_link_libraries(example_system_wide log)
target_link_libraries(example_custom_data_source log)
target_link_libraries(example_console log)
+ target_link_libraries(example_startup_trace log)
endif (ANDROID)
if (WIN32)
@@ -60,6 +64,7 @@
target_link_libraries(example_system_wide ws2_32)
target_link_libraries(example_custom_data_source ws2_32)
target_link_libraries(example_console ws2_32)
+ target_link_libraries(example_startup_trace ws2_32)
endif (WIN32)
# Enable standards-compliant mode when using the Visual Studio compiler.
@@ -68,4 +73,5 @@
target_compile_options(example_system_wide PRIVATE "/permissive-")
target_compile_options(example_custom_data_source PRIVATE "/permissive-")
target_compile_options(example_console PRIVATE "/permissive-")
-endif (MSVC)
\ No newline at end of file
+ target_compile_options(example_startup_trace PRIVATE "/permissive-")
+endif (MSVC)
diff --git a/examples/sdk/README.md b/examples/sdk/README.md
index 393217f..d51a171 100644
--- a/examples/sdk/README.md
+++ b/examples/sdk/README.md
@@ -26,6 +26,11 @@
cmake --build build
```
+Note: If amalgamated source files are not present, generate them using
+`cd perfetto ; tools/gen_amalgamated --output sdk/perfetto`.
+[Learn more](https://perfetto.dev/docs/contributing/sdk-releasing#building-and-tagging-the-release)
+at the release section.
+
## Track event example
The [basic example](example.cc) shows how to instrument an app with track
diff --git a/examples/sdk/example_startup_trace.cc b/examples/sdk/example_startup_trace.cc
new file mode 100644
index 0000000..3a3afe3
--- /dev/null
+++ b/examples/sdk/example_startup_trace.cc
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+// This example demonstrates startup tracing with a custom data source.
+// Startup tracing can work only with kSystemBackend. Before running
+// this example, `traced` must already be running in a separate process.
+
+// Run system tracing: ninja -C out/default/ traced && ./out/default/traced
+// And then run this example: ninja -C out/default example_startup_trace &&
+// ./out/default/example_startup_trace
+
+#if defined(PERFETTO_SDK_EXAMPLE_USE_INTERNAL_HEADERS)
+#include "perfetto/tracing.h"
+#include "perfetto/tracing/core/data_source_descriptor.h"
+#include "perfetto/tracing/core/trace_config.h"
+#include "perfetto/tracing/data_source.h"
+#include "perfetto/tracing/tracing.h"
+#include "protos/perfetto/trace/test_event.pbzero.h"
+#else
+#include <perfetto.h>
+#endif
+
+#include <unistd.h>
+#include <fstream>
+#include <iostream>
+#include <thread>
+
+namespace {
+
+class EventObserver {
+ public:
+ // Wait until OnEvent is ever called.
+ // Returns immediately if OnEvent was already called.
+ void Wait() {
+ std::unique_lock<std::mutex> lock(mutex);
+ cv.wait(lock, [&]() { return event_occurred_; });
+ }
+ void OnEvent() {
+ {
+ std::unique_lock<std::mutex> lock(mutex);
+ event_occurred_ = true;
+ }
+ cv.notify_all();
+ }
+ EventObserver() = default;
+
+ private:
+ EventObserver(const EventObserver&) = delete;
+ EventObserver& operator=(const EventObserver&) = delete;
+
+ std::mutex mutex;
+ std::condition_variable cv;
+ bool event_occurred_ = false;
+};
+
+// The definition of our custom data source. Instances of this class will be
+// automatically created and destroyed by Perfetto.
+class CustomDataSource : public perfetto::DataSource<CustomDataSource> {
+ public:
+ CustomDataSource(EventObserver* event_observer)
+ : event_observer_(event_observer) {}
+ void OnStart(const StartArgs&) override { event_observer_->OnEvent(); }
+
+ private:
+ EventObserver* event_observer_;
+};
+
+void InitializePerfetto(EventObserver* event_observer) {
+ perfetto::TracingInitArgs args;
+ // The backends determine where trace events are recorded. For this example we
+ // are going to use the system-wide tracing service, because the in-process
+ // backend doesn't support startup tracing.
+ args.backends = perfetto::kSystemBackend;
+ perfetto::Tracing::Initialize(args);
+
+ // Register our custom data source. Only the name is required, but other
+ // properties can be advertised too.
+ perfetto::DataSourceDescriptor dsd;
+ dsd.set_name("com.example.startup_trace");
+ CustomDataSource::Register(dsd, event_observer);
+}
+
+// The trace config defines which types of data sources are enabled for
+// recording.
+perfetto::TraceConfig GetTraceConfig() {
+ perfetto::TraceConfig cfg;
+ cfg.add_buffers()->set_size_kb(1024);
+ auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+ ds_cfg->set_name("com.example.startup_trace");
+ return cfg;
+}
+
+void StartStartupTracing() {
+ perfetto::Tracing::SetupStartupTracingOpts args;
+ args.backend = perfetto::kSystemBackend;
+ perfetto::Tracing::SetupStartupTracing(GetTraceConfig(), args);
+}
+
+std::unique_ptr<perfetto::TracingSession> StartTracing() {
+ auto tracing_session = perfetto::Tracing::NewTrace();
+ tracing_session->Setup(GetTraceConfig());
+ tracing_session->StartBlocking();
+ return tracing_session;
+}
+
+void StopTracing(std::unique_ptr<perfetto::TracingSession> tracing_session) {
+ // Flush to make sure the last written event ends up in the trace.
+ CustomDataSource::Trace(
+ [](CustomDataSource::TraceContext ctx) { ctx.Flush(); });
+
+ // Stop tracing and read the trace data.
+ tracing_session->StopBlocking();
+ std::vector<char> trace_data(tracing_session->ReadTraceBlocking());
+
+ // Write the result into a file.
+ // Note: To save memory with longer traces, you can tell Perfetto to write
+ // directly into a file by passing a file descriptor into Setup() above.
+ std::ofstream output;
+ const char* filename = "example_startup_trace.pftrace";
+ output.open(filename, std::ios::out | std::ios::binary);
+ output.write(&trace_data[0], static_cast<std::streamsize>(trace_data.size()));
+ output.close();
+ PERFETTO_LOG(
+ "Trace written in %s file. To read this trace in "
+ "text form, run `./tools/traceconv text %s`",
+ filename, filename);
+}
+
+} // namespace
+
+PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(CustomDataSource);
+PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(CustomDataSource);
+
+int main(int, const char**) {
+ EventObserver event_observer;
+ InitializePerfetto(&event_observer);
+
+ StartStartupTracing();
+ // TODO(mohitms): Once we support `SetupStartupTracingBlocking`,
+ // it won't be required.
+ event_observer.Wait();
+
+ // Write an event using our custom data source before starting tracing
+ // session.
+ CustomDataSource::Trace([](CustomDataSource::TraceContext ctx) {
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp(41);
+ packet->set_for_testing()->set_str("Startup Event");
+ });
+
+ auto tracing_session = StartTracing();
+
+ // Write an event using our custom data source.
+ CustomDataSource::Trace([](CustomDataSource::TraceContext ctx) {
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp(42);
+ packet->set_for_testing()->set_str("Main Event");
+ });
+ StopTracing(std::move(tracing_session));
+
+ return 0;
+}
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index fbaf9f9..aa15ff1 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -672,6 +672,7 @@
TASK_TYPE_INTERNAL_INPUT_BLOCKING = 77;
TASK_TYPE_WEB_GPU = 78;
TASK_TYPE_INTERNAL_POST_MESSAGE_FORWARDING = 79;
+ TASK_TYPE_INTERNAL_NAVIGATION_CANCELLATION = 80;
}
enum FrameType {
diff --git a/src/base/utils.cc b/src/base/utils.cc
index 1aa1af5..cb24d25 100644
--- a/src/base/utils.cc
+++ b/src/base/utils.cc
@@ -21,6 +21,7 @@
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/pipe.h"
#include "perfetto/ext/base/string_utils.h"
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
@@ -190,6 +191,7 @@
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+ Pipe pipe = Pipe::Create(Pipe::kBothBlock);
pid_t pid;
switch (pid = fork()) {
case -1:
@@ -205,12 +207,24 @@
// Do not accidentally close stdin/stdout/stderr.
if (*null <= 2)
null.release();
+ WriteAll(*pipe.wr, "1", 1);
break;
}
- default:
+ default: {
+ // Wait for the child process to have reached the setsid() call. This is
+ // to avoid that 'adb shell perfetto -D' destroys the terminal (hence
+ // sending a SIGHUP to the child) before the child has detached from the
+ // terminal (see b/238644870).
+
+ // This is to unblock the read() below (with EOF, which will fail the
+ // CHECK) in the unlikely case of the child crashing before WriteAll("1").
+ pipe.wr.reset();
+ char one = '\0';
+ PERFETTO_CHECK(Read(*pipe.rd, &one, sizeof(one)) == 1 && one == '1');
printf("%d\n", pid);
int err = parent_cb();
exit(err);
+ }
}
#else
// Avoid -Wunreachable warnings.
diff --git a/src/profiling/perf/perf_producer.cc b/src/profiling/perf/perf_producer.cc
index 85fd6ac..951cb4b 100644
--- a/src/profiling/perf/perf_producer.cc
+++ b/src/profiling/perf/perf_producer.cc
@@ -1086,6 +1086,10 @@
desc.set_name(MetatraceWriter::kDataSourceName);
endpoint_->RegisterDataSource(desc);
}
+ // Used by tracebox to synchronize with traced_probes being registered.
+ if (all_data_sources_registered_cb_) {
+ endpoint_->Sync(all_data_sources_registered_cb_);
+ }
}
void PerfProducer::OnDisconnect() {
diff --git a/src/profiling/perf/perf_producer.h b/src/profiling/perf/perf_producer.h
index da87ade..d9c6a18 100644
--- a/src/profiling/perf/perf_producer.h
+++ b/src/profiling/perf/perf_producer.h
@@ -96,6 +96,11 @@
ParsedSample sample) override;
void PostFinishDataSourceStop(DataSourceInstanceID ds_id) override;
+ // Calls `cb` when all data sources have been registered.
+ void SetAllDataSourcesRegisteredCb(std::function<void()> cb) {
+ all_data_sources_registered_cb_ = cb;
+ }
+
private:
// State of the producer's connection to tracing service (traced).
enum State {
@@ -261,6 +266,8 @@
// best effort - can be null if tracefs isn't accessible.
std::unique_ptr<FtraceProcfs> tracefs_;
+ std::function<void()> all_data_sources_registered_cb_;
+
base::WeakPtrFactory<PerfProducer> weak_factory_; // keep last
};
diff --git a/src/profiling/perf/traced_perf.cc b/src/profiling/perf/traced_perf.cc
index 77ce7c4..af2dc1a 100644
--- a/src/profiling/perf/traced_perf.cc
+++ b/src/profiling/perf/traced_perf.cc
@@ -15,6 +15,7 @@
*/
#include "src/profiling/perf/traced_perf.h"
+#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/unix_task_runner.h"
#include "perfetto/ext/tracing/ipc/default_socket.h"
#include "src/profiling/perf/perf_producer.h"
@@ -52,6 +53,14 @@
#endif
profiling::PerfProducer producer(&proc_fd_getter, &task_runner);
+ const char* env_notif = getenv("TRACED_PERF_NOTIFY_FD");
+ if (env_notif) {
+ int notif_fd = atoi(env_notif);
+ producer.SetAllDataSourcesRegisteredCb([notif_fd] {
+ PERFETTO_CHECK(base::WriteAll(notif_fd, "1", 1) == 1);
+ PERFETTO_CHECK(base::CloseFile(notif_fd) == 0);
+ });
+ }
producer.ConnectWithRetries(GetProducerSocket());
task_runner.Run();
return 0;
diff --git a/src/profiling/symbolizer/BUILD.gn b/src/profiling/symbolizer/BUILD.gn
index 70dc091..f5a1af7 100644
--- a/src/profiling/symbolizer/BUILD.gn
+++ b/src/profiling/symbolizer/BUILD.gn
@@ -48,6 +48,7 @@
"../../../include/perfetto/trace_processor:trace_processor",
"../../../protos/perfetto/trace:zero",
"../../../protos/perfetto/trace/profiling:zero",
+ "../../trace_processor/util:stack_traces_util",
]
sources = [
"symbolize_database.cc",
diff --git a/src/profiling/symbolizer/symbolize_database.cc b/src/profiling/symbolizer/symbolize_database.cc
index c6fca90..b7d7cfb 100644
--- a/src/profiling/symbolizer/symbolize_database.cc
+++ b/src/profiling/symbolizer/symbolize_database.cc
@@ -29,6 +29,8 @@
#include "protos/perfetto/trace/trace.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "src/trace_processor/util/stack_traces_util.h"
+
namespace perfetto {
namespace profiling {
@@ -89,10 +91,14 @@
int64_t load_bias = it.Get(3).AsLong();
PERFETTO_CHECK(load_bias >= 0);
std::string build_id;
- if (convert_build_id_to_bytes) {
- build_id = FromHex(it.Get(1).AsString());
+ // TODO(b/148109467): Remove workaround once all active Chrome versions
+ // write raw bytes instead of a string as build_id.
+ std::string raw_build_id = it.Get(1).AsString();
+ if (convert_build_id_to_bytes &&
+ !trace_processor::util::IsHexModuleId(base::StringView(raw_build_id))) {
+ build_id = FromHex(raw_build_id);
} else {
- build_id = it.Get(1).AsString();
+ build_id = raw_build_id;
}
UnsymbolizedMapping unsymbolized_mapping{it.Get(0).AsString(), build_id,
static_cast<uint64_t>(load_bias)};
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 6d0bfc4..253ea87 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -185,6 +185,7 @@
"util:gzip",
"util:interned_message_view",
"util:proto_to_args_parser",
+ "util:stack_traces_util",
"views",
]
public_deps = [
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index 961ecfc..67c5927 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -70,12 +70,14 @@
class ExportJsonTest : public ::testing::Test {
public:
ExportJsonTest() {
- context_.global_args_tracker.reset(new GlobalArgsTracker(&context_));
+ context_.storage.reset(new TraceStorage());
+ context_.global_args_tracker.reset(
+ new GlobalArgsTracker(context_.storage.get()));
context_.args_tracker.reset(new ArgsTracker(&context_));
context_.event_tracker.reset(new EventTracker(&context_));
- context_.storage.reset(new TraceStorage());
context_.track_tracker.reset(new TrackTracker(&context_));
- context_.metadata_tracker.reset(new MetadataTracker(&context_));
+ context_.metadata_tracker.reset(
+ new MetadataTracker(context_.storage.get()));
context_.process_tracker.reset(new ProcessTracker(&context_));
}
diff --git a/src/trace_processor/forwarding_trace_parser.cc b/src/trace_processor/forwarding_trace_parser.cc
index 04f720a..829589b 100644
--- a/src/trace_processor/forwarding_trace_parser.cc
+++ b/src/trace_processor/forwarding_trace_parser.cc
@@ -176,8 +176,10 @@
return kSystraceTraceType;
// Systrace with leading HTML.
- if (base::StartsWith(start, "<!DOCTYPE html>") ||
- base::StartsWith(start, "<html>"))
+ // Both: <!DOCTYPE html> and <!DOCTYPE HTML> have been observed.
+ std::string lower_start = base::ToLower(start);
+ if (base::StartsWith(lower_start, "<!doctype html>") ||
+ base::StartsWith(lower_start, "<html>"))
return kSystraceTraceType;
// Traces obtained from atrace -z (compress).
@@ -190,7 +192,7 @@
if (base::Contains(start, "TRACE:\n"))
return kSystraceTraceType;
- // Ninja's buils log (.ninja_log).
+ // Ninja's build log (.ninja_log).
if (base::StartsWith(start, "# ninja log"))
return kNinjaLogTraceType;
diff --git a/src/trace_processor/forwarding_trace_parser_unittest.cc b/src/trace_processor/forwarding_trace_parser_unittest.cc
index 059caa0..74cb631 100644
--- a/src/trace_processor/forwarding_trace_parser_unittest.cc
+++ b/src/trace_processor/forwarding_trace_parser_unittest.cc
@@ -48,6 +48,36 @@
EXPECT_EQ(kJsonTraceType, GuessTraceType(prefix, sizeof(prefix)));
}
+TEST(TraceProcessorImplTest, GuessTraceType_DoctypeHtmlUppercase) {
+ const uint8_t prefix[] = "<!DOCTYPE HTML>";
+ EXPECT_EQ(kSystraceTraceType, GuessTraceType(prefix, sizeof(prefix)));
+}
+
+TEST(TraceProcessorImplTest, GuessTraceType_DoctypeHtml) {
+ const uint8_t prefix[] = "<!doctype html>";
+ EXPECT_EQ(kSystraceTraceType, GuessTraceType(prefix, sizeof(prefix)));
+}
+
+TEST(TraceProcessorImplTest, GuessTraceType_DoctypeHtmlMixed) {
+ const uint8_t prefix[] = "<!DoCTyPe HtMl>";
+ EXPECT_EQ(kSystraceTraceType, GuessTraceType(prefix, sizeof(prefix)));
+}
+
+TEST(TraceProcessorImplTest, GuessTraceType_Html) {
+ const uint8_t prefix[] = "<html>";
+ EXPECT_EQ(kSystraceTraceType, GuessTraceType(prefix, sizeof(prefix)));
+}
+
+TEST(TraceProcessorImplTest, GuessTraceType_HtmlUpper) {
+ const uint8_t prefix[] = "<HTML>";
+ EXPECT_EQ(kSystraceTraceType, GuessTraceType(prefix, sizeof(prefix)));
+}
+
+TEST(TraceProcessorImplTest, GuessTraceType_HtmlMixed) {
+ const uint8_t prefix[] = "<htmL>";
+ EXPECT_EQ(kSystraceTraceType, GuessTraceType(prefix, sizeof(prefix)));
+}
+
TEST(TraceProcessorImplTest, GuessTraceType_Proto) {
const uint8_t prefix[] = {0x0a, 0x00}; // An empty TracePacket.
EXPECT_EQ(kProtoTraceType, GuessTraceType(prefix, sizeof(prefix)));
diff --git a/src/trace_processor/importers/common/clock_tracker.cc b/src/trace_processor/importers/common/clock_tracker.cc
index 2bb0ff2..bb0158b 100644
--- a/src/trace_processor/importers/common/clock_tracker.cc
+++ b/src/trace_processor/importers/common/clock_tracker.cc
@@ -36,8 +36,8 @@
using Clock = protos::pbzero::ClockSnapshot::Clock;
-ClockTracker::ClockTracker(TraceProcessorContext* ctx)
- : context_(ctx),
+ClockTracker::ClockTracker(TraceStorage* storage)
+ : storage_(storage),
trace_time_clock_id_(protos::pbzero::BUILTIN_CLOCK_BOOTTIME) {}
ClockTracker::~ClockTracker() = default;
@@ -65,7 +65,7 @@
" cannot use incremental encoding; this is only "
"supported for sequence-scoped clocks.",
clock_id);
- context_->storage->IncrementStats(stats::invalid_clock_snapshots);
+ storage_->IncrementStats(stats::invalid_clock_snapshots);
return snapshot_id;
}
domain.unit_multiplier_ns = clock.unit_multiplier_ns;
@@ -79,7 +79,7 @@
"different properties (unit=%" PRIu64 ", incremental=%d).",
clock_id, clock.unit_multiplier_ns, clock.is_incremental,
domain.unit_multiplier_ns, domain.is_incremental);
- context_->storage->IncrementStats(stats::invalid_clock_snapshots);
+ storage_->IncrementStats(stats::invalid_clock_snapshots);
return snapshot_id;
}
const int64_t timestamp_ns =
@@ -92,7 +92,7 @@
PERFETTO_ELOG("Clock sync error: duplicate clock domain with id=%" PRIu64
" at snapshot %" PRIu32 ".",
clock_id, snapshot_id);
- context_->storage->IncrementStats(stats::invalid_clock_snapshots);
+ storage_->IncrementStats(stats::invalid_clock_snapshots);
return snapshot_id;
}
@@ -116,7 +116,7 @@
" not >= %" PRId64 ".",
clock_id, snapshot_id, timestamp_ns,
vect.timestamps_ns.back());
- context_->storage->IncrementStats(stats::invalid_clock_snapshots);
+ storage_->IncrementStats(stats::invalid_clock_snapshots);
return snapshot_id;
}
@@ -216,7 +216,7 @@
PERFETTO_DCHECK(!IsReservedSeqScopedClockId(src_clock_id));
PERFETTO_DCHECK(!IsReservedSeqScopedClockId(target_clock_id));
- context_->storage->IncrementStats(stats::clock_sync_cache_miss);
+ storage_->IncrementStats(stats::clock_sync_cache_miss);
ClockPath path = FindPath(src_clock_id, target_clock_id);
if (!path.valid()) {
@@ -227,7 +227,7 @@
" at timestamp %" PRId64,
src_clock_id, target_clock_id, src_timestamp);
}
- context_->storage->IncrementStats(stats::clock_sync_failure);
+ storage_->IncrementStats(stats::clock_sync_failure);
return base::nullopt;
}
diff --git a/src/trace_processor/importers/common/clock_tracker.h b/src/trace_processor/importers/common/clock_tracker.h
index 576c51f..210a34d 100644
--- a/src/trace_processor/importers/common/clock_tracker.h
+++ b/src/trace_processor/importers/common/clock_tracker.h
@@ -29,6 +29,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/optional.h"
#include "perfetto/ext/base/string_utils.h"
+#include "src/trace_processor/storage/trace_storage.h"
namespace perfetto {
namespace trace_processor {
@@ -135,7 +136,7 @@
return (global_clock_id >> 32) > 0;
}
- explicit ClockTracker(TraceProcessorContext*);
+ explicit ClockTracker(TraceStorage*);
virtual ~ClockTracker();
// Clock description and its value in a snapshot.
@@ -312,7 +313,7 @@
return &it->second;
}
- TraceProcessorContext* const context_;
+ TraceStorage* const storage_;
ClockId trace_time_clock_id_ = 0;
std::map<ClockId, ClockDomain> clocks_;
std::set<ClockGraphEdge> graph_;
diff --git a/src/trace_processor/importers/common/clock_tracker_unittest.cc b/src/trace_processor/importers/common/clock_tracker_unittest.cc
index fc8cc9d..769bb84 100644
--- a/src/trace_processor/importers/common/clock_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/clock_tracker_unittest.cc
@@ -42,10 +42,8 @@
class ClockTrackerTest : public ::testing::Test {
public:
- ClockTrackerTest() { context_.storage.reset(new TraceStorage()); }
-
- TraceProcessorContext context_;
- ClockTracker ct_{&context_};
+ TraceStorage storage_;
+ ClockTracker ct_{&storage_};
};
TEST_F(ClockTrackerTest, ClockDomainConversions) {
diff --git a/src/trace_processor/importers/common/event_tracker_unittest.cc b/src/trace_processor/importers/common/event_tracker_unittest.cc
index 66fc899..c35c7d7 100644
--- a/src/trace_processor/importers/common/event_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/event_tracker_unittest.cc
@@ -34,7 +34,8 @@
public:
EventTrackerTest() {
context.storage.reset(new TraceStorage());
- context.global_args_tracker.reset(new GlobalArgsTracker(&context));
+ context.global_args_tracker.reset(
+ new GlobalArgsTracker(context.storage.get()));
context.args_tracker.reset(new ArgsTracker(&context));
context.process_tracker.reset(new ProcessTracker(&context));
context.event_tracker.reset(new EventTracker(&context));
diff --git a/src/trace_processor/importers/common/global_args_tracker.cc b/src/trace_processor/importers/common/global_args_tracker.cc
index dc1343f..25ca413 100644
--- a/src/trace_processor/importers/common/global_args_tracker.cc
+++ b/src/trace_processor/importers/common/global_args_tracker.cc
@@ -19,8 +19,8 @@
namespace perfetto {
namespace trace_processor {
-GlobalArgsTracker::GlobalArgsTracker(TraceProcessorContext* context)
- : context_(context) {}
+GlobalArgsTracker::GlobalArgsTracker(TraceStorage* storage)
+ : storage_(storage) {}
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/common/global_args_tracker.h b/src/trace_processor/importers/common/global_args_tracker.h
index 3d5fb3d..573a917 100644
--- a/src/trace_processor/importers/common/global_args_tracker.h
+++ b/src/trace_processor/importers/common/global_args_tracker.h
@@ -21,7 +21,6 @@
#include "perfetto/ext/base/hash.h"
#include "perfetto/ext/base/small_vector.h"
#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/types/variadic.h"
namespace perfetto {
@@ -93,7 +92,7 @@
}
};
- explicit GlobalArgsTracker(TraceProcessorContext* context);
+ explicit GlobalArgsTracker(TraceStorage* storage);
// Assumes that the interval [begin, end) of |args| is sorted by keys.
ArgSetId AddArgSet(const Arg* args, uint32_t begin, uint32_t end) {
@@ -123,7 +122,7 @@
hash.Update(ArgHasher()(args[i]));
}
- auto* arg_table = context_->storage->mutable_arg_table();
+ auto* arg_table = storage_->mutable_arg_table();
ArgSetHash digest = hash.digest();
auto it_and_inserted =
@@ -168,7 +167,7 @@
case Variadic::Type::kNull:
break;
}
- row.value_type = context_->storage->GetIdForVariadicType(arg.value.type);
+ row.value_type = storage_->GetIdForVariadicType(arg.value.type);
arg_table->Insert(row);
}
return id;
@@ -187,7 +186,7 @@
base::FlatHashMap<ArgSetHash, uint32_t, base::AlreadyHashed<ArgSetHash>>
arg_row_for_hash_;
- TraceProcessorContext* context_;
+ TraceStorage* storage_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/importers/common/process_tracker_unittest.cc b/src/trace_processor/importers/common/process_tracker_unittest.cc
index d037136..de4102e 100644
--- a/src/trace_processor/importers/common/process_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/process_tracker_unittest.cc
@@ -34,7 +34,8 @@
public:
ProcessTrackerTest() {
context.storage.reset(new TraceStorage());
- context.global_args_tracker.reset(new GlobalArgsTracker(&context));
+ context.global_args_tracker.reset(
+ new GlobalArgsTracker(context.storage.get()));
context.args_tracker.reset(new ArgsTracker(&context));
context.process_tracker.reset(new ProcessTracker(&context));
context.event_tracker.reset(new EventTracker(&context));
diff --git a/src/trace_processor/importers/common/slice_tracker_unittest.cc b/src/trace_processor/importers/common/slice_tracker_unittest.cc
index 29e7489..5b8d37d 100644
--- a/src/trace_processor/importers/common/slice_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/slice_tracker_unittest.cc
@@ -130,7 +130,8 @@
TEST(SliceTrackerTest, OneSliceWithArgs) {
TraceProcessorContext context;
context.storage.reset(new TraceStorage());
- context.global_args_tracker.reset(new GlobalArgsTracker(&context));
+ context.global_args_tracker.reset(
+ new GlobalArgsTracker(context.storage.get()));
context.slice_translation_table.reset(
new SliceTranslationTable(context.storage.get()));
SliceTracker tracker(&context);
@@ -175,7 +176,8 @@
TEST(SliceTrackerTest, OneSliceWithArgsWithTranslatedName) {
TraceProcessorContext context;
context.storage.reset(new TraceStorage());
- context.global_args_tracker.reset(new GlobalArgsTracker(&context));
+ context.global_args_tracker.reset(
+ new GlobalArgsTracker(context.storage.get()));
context.slice_translation_table.reset(
new SliceTranslationTable(context.storage.get()));
SliceTracker tracker(&context);
diff --git a/src/trace_processor/importers/common/system_info_tracker.cc b/src/trace_processor/importers/common/system_info_tracker.cc
index aac30fd..f8c1007 100644
--- a/src/trace_processor/importers/common/system_info_tracker.cc
+++ b/src/trace_processor/importers/common/system_info_tracker.cc
@@ -20,7 +20,7 @@
namespace perfetto {
namespace trace_processor {
-SystemInfoTracker::SystemInfoTracker(TraceProcessorContext*) {}
+SystemInfoTracker::SystemInfoTracker() {}
SystemInfoTracker::~SystemInfoTracker() = default;
void SystemInfoTracker::SetKernelVersion(base::StringView name,
diff --git a/src/trace_processor/importers/common/system_info_tracker.h b/src/trace_processor/importers/common/system_info_tracker.h
index 1295db6..af108c5 100644
--- a/src/trace_processor/importers/common/system_info_tracker.h
+++ b/src/trace_processor/importers/common/system_info_tracker.h
@@ -35,7 +35,7 @@
static SystemInfoTracker* GetOrCreate(TraceProcessorContext* context) {
if (!context->system_info_tracker) {
- context->system_info_tracker.reset(new SystemInfoTracker(context));
+ context->system_info_tracker.reset(new SystemInfoTracker());
}
return static_cast<SystemInfoTracker*>(context->system_info_tracker.get());
}
@@ -45,7 +45,7 @@
base::Optional<VersionNumber> GetKernelVersion() { return version_; }
private:
- explicit SystemInfoTracker(TraceProcessorContext*);
+ explicit SystemInfoTracker();
base::Optional<VersionNumber> version_;
};
diff --git a/src/trace_processor/importers/ftrace/binder_tracker_unittest.cc b/src/trace_processor/importers/ftrace/binder_tracker_unittest.cc
index 960bc25..8884704 100644
--- a/src/trace_processor/importers/ftrace/binder_tracker_unittest.cc
+++ b/src/trace_processor/importers/ftrace/binder_tracker_unittest.cc
@@ -37,7 +37,8 @@
public:
BinderTrackerTest() {
context.storage.reset(new TraceStorage());
- context.global_args_tracker.reset(new GlobalArgsTracker(&context));
+ context.global_args_tracker.reset(
+ new GlobalArgsTracker(context.storage.get()));
context.args_tracker.reset(new ArgsTracker(&context));
context.args_translation_table.reset(
new ArgsTranslationTable(context.storage.get()));
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index a1167e0..203b811 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -262,7 +262,7 @@
cros_ec_arg_ec_id_(context->storage->InternString("ec_delta")),
cros_ec_arg_sample_ts_id_(context->storage->InternString("sample_ts")),
ufs_clkgating_id_(context->storage->InternString(
- "UFS clkgating (OFF/REQ_OFF/REQ_ON/ON)")),
+ "io.ufs.clkgating (OFF:0/REQ_OFF/REQ_ON/ON:3)")),
ufs_command_count_id_(
context->storage->InternString("io.ufs.command.count")) {
// Build the lookup table for the strings inside ftrace events (e.g. the
diff --git a/src/trace_processor/importers/ftrace/sched_event_tracker_unittest.cc b/src/trace_processor/importers/ftrace/sched_event_tracker_unittest.cc
index 802990f..22b1139 100644
--- a/src/trace_processor/importers/ftrace/sched_event_tracker_unittest.cc
+++ b/src/trace_processor/importers/ftrace/sched_event_tracker_unittest.cc
@@ -34,7 +34,8 @@
public:
SchedEventTrackerTest() {
context.storage.reset(new TraceStorage());
- context.global_args_tracker.reset(new GlobalArgsTracker(&context));
+ context.global_args_tracker.reset(
+ new GlobalArgsTracker(context.storage.get()));
context.args_tracker.reset(new ArgsTracker(&context));
context.event_tracker.reset(new EventTracker(&context));
context.process_tracker.reset(new ProcessTracker(&context));
diff --git a/src/trace_processor/importers/ftrace/thread_state_tracker.cc b/src/trace_processor/importers/ftrace/thread_state_tracker.cc
index 8289dd1..9ff75e3 100644
--- a/src/trace_processor/importers/ftrace/thread_state_tracker.cc
+++ b/src/trace_processor/importers/ftrace/thread_state_tracker.cc
@@ -15,14 +15,12 @@
*/
#include "src/trace_processor/importers/ftrace/thread_state_tracker.h"
-#include "src/trace_processor/types/trace_processor_context.h"
-
namespace perfetto {
namespace trace_processor {
-ThreadStateTracker::ThreadStateTracker(TraceProcessorContext* context)
- : context_(context),
- running_string_id_(context->storage->InternString("Running")),
- runnable_string_id_(context->storage->InternString("R")) {}
+ThreadStateTracker::ThreadStateTracker(TraceStorage* storage)
+ : storage_(storage),
+ running_string_id_(storage->InternString("Running")),
+ runnable_string_id_(storage->InternString("R")) {}
ThreadStateTracker::~ThreadStateTracker() = default;
void ThreadStateTracker::PushSchedSwitchEvent(int64_t event_ts,
@@ -113,8 +111,7 @@
row.dur = -1;
row.utid = utid;
row.state = state;
- auto row_num =
- context_->storage->mutable_thread_state_table()->Insert(row).row_number;
+ auto row_num = storage_->mutable_thread_state_table()->Insert(row).row_number;
if (utid >= prev_row_numbers_for_thread_.size()) {
prev_row_numbers_for_thread_.resize(utid + 1);
diff --git a/src/trace_processor/importers/ftrace/thread_state_tracker.h b/src/trace_processor/importers/ftrace/thread_state_tracker.h
index 6e9c414..af3ad4c 100644
--- a/src/trace_processor/importers/ftrace/thread_state_tracker.h
+++ b/src/trace_processor/importers/ftrace/thread_state_tracker.h
@@ -27,13 +27,14 @@
// waking events and blocking reasons.
class ThreadStateTracker : public Destructible {
public:
- explicit ThreadStateTracker(TraceProcessorContext*);
+ explicit ThreadStateTracker(TraceStorage*);
ThreadStateTracker(const ThreadStateTracker&) = delete;
ThreadStateTracker& operator=(const ThreadStateTracker&) = delete;
~ThreadStateTracker() override;
static ThreadStateTracker* GetOrCreate(TraceProcessorContext* context) {
if (!context->thread_state_tracker) {
- context->thread_state_tracker.reset(new ThreadStateTracker(context));
+ context->thread_state_tracker.reset(
+ new ThreadStateTracker(context->storage.get()));
}
return static_cast<ThreadStateTracker*>(
context->thread_state_tracker.get());
@@ -74,11 +75,10 @@
tables::ThreadStateTable::RowReference RowNumToRef(
tables::ThreadStateTable::RowNumber row_number) {
- return row_number.ToRowReference(
- context_->storage->mutable_thread_state_table());
+ return row_number.ToRowReference(storage_->mutable_thread_state_table());
}
- TraceProcessorContext* const context_;
+ TraceStorage* const storage_;
// Strings
StringId running_string_id_;
diff --git a/src/trace_processor/importers/ftrace/thread_state_tracker_unittest.cc b/src/trace_processor/importers/ftrace/thread_state_tracker_unittest.cc
index 9a19a2e..0c6c370 100644
--- a/src/trace_processor/importers/ftrace/thread_state_tracker_unittest.cc
+++ b/src/trace_processor/importers/ftrace/thread_state_tracker_unittest.cc
@@ -41,9 +41,10 @@
public:
ThreadStateTrackerUnittest() {
context_.storage.reset(new TraceStorage());
- context_.global_args_tracker.reset(new GlobalArgsTracker(&context_));
+ context_.global_args_tracker.reset(
+ new GlobalArgsTracker(context_.storage.get()));
context_.args_tracker.reset(new ArgsTracker(&context_));
- tracker_.reset(new ThreadStateTracker(&context_));
+ tracker_.reset(new ThreadStateTracker(context_.storage.get()));
}
StringId StringIdOf(const char* s) {
diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn
index f2c5d45..732e002 100644
--- a/src/trace_processor/importers/proto/BUILD.gn
+++ b/src/trace_processor/importers/proto/BUILD.gn
@@ -28,6 +28,7 @@
"../../storage",
"../../tables",
"../../types",
+ "../../util:stack_traces_util",
"../common",
]
}
diff --git a/src/trace_processor/importers/proto/android_probes_module.cc b/src/trace_processor/importers/proto/android_probes_module.cc
index 799c8b3..23c25b4 100644
--- a/src/trace_processor/importers/proto/android_probes_module.cc
+++ b/src/trace_processor/importers/proto/android_probes_module.cc
@@ -89,6 +89,7 @@
RegisterForField(TracePacket::kAndroidGameInterventionListFieldNumber,
context);
RegisterForField(TracePacket::kInitialDisplayStateFieldNumber, context);
+ RegisterForField(TracePacket::kAndroidSystemPropertyFieldNumber, context);
}
ModuleResult AndroidProbesModule::TokenizePacket(
@@ -198,6 +199,10 @@
parser_.ParseInitialDisplayState(ttp.timestamp,
decoder.initial_display_state());
return;
+ case TracePacket::kAndroidSystemPropertyFieldNumber:
+ parser_.ParseAndroidSystemProperty(ttp.timestamp,
+ decoder.android_system_property());
+ return;
}
}
diff --git a/src/trace_processor/importers/proto/android_probes_parser.cc b/src/trace_processor/importers/proto/android_probes_parser.cc
index 7b5b7e8..fc114ce 100644
--- a/src/trace_processor/importers/proto/android_probes_parser.cc
+++ b/src/trace_processor/importers/proto/android_probes_parser.cc
@@ -24,6 +24,7 @@
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/proto/async_track_set_tracker.h"
#include "src/trace_processor/importers/proto/metadata_tracker.h"
#include "src/trace_processor/importers/syscalls/syscall_tracker.h"
#include "src/trace_processor/types/trace_processor_context.h"
@@ -33,6 +34,7 @@
#include "protos/perfetto/config/trace_config.pbzero.h"
#include "protos/perfetto/trace/android/android_game_intervention_list.pbzero.h"
#include "protos/perfetto/trace/android/android_log.pbzero.h"
+#include "protos/perfetto/trace/android/android_system_property.pbzero.h"
#include "protos/perfetto/trace/android/initial_display_state.pbzero.h"
#include "protos/perfetto/trace/android/packages_list.pbzero.h"
#include "protos/perfetto/trace/power/battery_counters.pbzero.h"
@@ -54,7 +56,8 @@
batt_current_id_(context->storage->InternString("batt.current_ua")),
batt_current_avg_id_(
context->storage->InternString("batt.current.avg_ua")),
- screen_state_id_(context->storage->InternString("ScreenState")) {}
+ screen_state_id_(context->storage->InternString("ScreenState")),
+ device_state_id_(context->storage->InternString("DeviceStateChanged")) {}
void AndroidProbesParser::ParseBatteryCounters(int64_t ts, ConstBytes blob) {
protos::pbzero::BatteryCounters::Decoder evt(blob.data, blob.size);
@@ -309,5 +312,33 @@
context_->event_tracker->PushCounter(ts, state.display_state(), track);
}
+void AndroidProbesParser::ParseAndroidSystemProperty(int64_t ts,
+ ConstBytes blob) {
+ protos::pbzero::AndroidSystemProperty::Decoder properties(blob.data,
+ blob.size);
+ for (auto it = properties.values(); it; ++it) {
+ protos::pbzero::AndroidSystemProperty::PropertyValue::Decoder kv(*it);
+ if (base::StringView(kv.name()) == "debug.tracing.screen_state") {
+ base::Optional<int32_t> state =
+ base::StringToInt32(kv.value().ToStdString());
+ if (state) {
+ TrackId track =
+ context_->track_tracker->InternGlobalCounterTrack(screen_state_id_);
+ context_->event_tracker->PushCounter(ts, *state, track);
+ }
+ } else if (base::StringView(kv.name()) == "debug.tracing.device_state") {
+ auto state = kv.value();
+
+ StringId state_id = context_->storage->InternString(state);
+ auto track_set_id =
+ context_->async_track_set_tracker->InternGlobalTrackSet(
+ device_state_id_);
+ TrackId track_id =
+ context_->async_track_set_tracker->Scoped(track_set_id, ts, 0);
+ context_->slice_tracker->Scoped(ts, track_id, kNullStringId, state_id, 0);
+ }
+ }
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/proto/android_probes_parser.h b/src/trace_processor/importers/proto/android_probes_parser.h
index fd8fb11..3464866 100644
--- a/src/trace_processor/importers/proto/android_probes_parser.h
+++ b/src/trace_processor/importers/proto/android_probes_parser.h
@@ -41,6 +41,7 @@
void ParseStatsdMetadata(ConstBytes);
void ParseAndroidPackagesList(ConstBytes);
void ParseInitialDisplayState(int64_t ts, ConstBytes);
+ void ParseAndroidSystemProperty(int64_t ts, ConstBytes);
void ParseAndroidGameIntervention(ConstBytes);
private:
@@ -51,6 +52,7 @@
const StringId batt_current_id_;
const StringId batt_current_avg_id_;
const StringId screen_state_id_;
+ const StringId device_state_id_;
};
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/proto/android_probes_tracker.cc b/src/trace_processor/importers/proto/android_probes_tracker.cc
index 490ac87..4235cf1 100644
--- a/src/trace_processor/importers/proto/android_probes_tracker.cc
+++ b/src/trace_processor/importers/proto/android_probes_tracker.cc
@@ -19,7 +19,7 @@
namespace perfetto {
namespace trace_processor {
-AndroidProbesTracker::AndroidProbesTracker(TraceProcessorContext*) {}
+AndroidProbesTracker::AndroidProbesTracker(TraceStorage*) {}
AndroidProbesTracker::~AndroidProbesTracker() = default;
diff --git a/src/trace_processor/importers/proto/android_probes_tracker.h b/src/trace_processor/importers/proto/android_probes_tracker.h
index b155cf5..c199e7c 100644
--- a/src/trace_processor/importers/proto/android_probes_tracker.h
+++ b/src/trace_processor/importers/proto/android_probes_tracker.h
@@ -31,12 +31,13 @@
class AndroidProbesTracker : public Destructible {
public:
- explicit AndroidProbesTracker(TraceProcessorContext*);
+ explicit AndroidProbesTracker(TraceStorage*);
~AndroidProbesTracker() override;
static AndroidProbesTracker* GetOrCreate(TraceProcessorContext* context) {
if (!context->android_probes_tracker) {
- context->android_probes_tracker.reset(new AndroidProbesTracker(context));
+ context->android_probes_tracker.reset(
+ new AndroidProbesTracker(context->storage.get()));
}
return static_cast<AndroidProbesTracker*>(
context->android_probes_tracker.get());
diff --git a/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc b/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc
index eb2e041..7d26b04 100644
--- a/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc
+++ b/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc
@@ -29,7 +29,8 @@
public:
AsyncTrackSetTrackerUnittest() {
context_.storage.reset(new TraceStorage());
- context_.global_args_tracker.reset(new GlobalArgsTracker(&context_));
+ context_.global_args_tracker.reset(
+ new GlobalArgsTracker(context_.storage.get()));
context_.args_tracker.reset(new ArgsTracker(&context_));
context_.track_tracker.reset(new TrackTracker(&context_));
context_.async_track_set_tracker.reset(new AsyncTrackSetTracker(&context_));
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index 696b9e4..39e1418 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -273,18 +273,16 @@
return result;
}
-HeapGraphTracker::HeapGraphTracker(TraceProcessorContext* context)
- : context_(context),
- cleaner_thunk_str_id_(
- context_->storage->InternString("sun.misc.Cleaner.thunk")),
+HeapGraphTracker::HeapGraphTracker(TraceStorage* storage)
+ : storage_(storage),
+ cleaner_thunk_str_id_(storage_->InternString("sun.misc.Cleaner.thunk")),
referent_str_id_(
- context_->storage->InternString("java.lang.ref.Reference.referent")),
- cleaner_thunk_this0_str_id_(context_->storage->InternString(
+ storage_->InternString("java.lang.ref.Reference.referent")),
+ cleaner_thunk_this0_str_id_(storage_->InternString(
"libcore.util.NativeAllocationRegistry$CleanerThunk.this$0")),
- native_size_str_id_(context_->storage->InternString(
- "libcore.util.NativeAllocationRegistry.size")),
- cleaner_next_str_id_(
- context_->storage->InternString("sun.misc.Cleaner.next")) {}
+ native_size_str_id_(
+ storage_->InternString("libcore.util.NativeAllocationRegistry.size")),
+ cleaner_next_str_id_(storage_->InternString("sun.misc.Cleaner.next")) {}
HeapGraphTracker::SequenceState& HeapGraphTracker::GetOrCreateSequence(
uint32_t seq_id) {
@@ -296,11 +294,11 @@
int64_t ts) {
if (sequence_state->current_upid != 0 &&
sequence_state->current_upid != upid) {
- context_->storage->IncrementStats(stats::heap_graph_non_finalized_graph);
+ storage_->IncrementStats(stats::heap_graph_non_finalized_graph);
return false;
}
if (sequence_state->current_ts != 0 && sequence_state->current_ts != ts) {
- context_->storage->IncrementStats(stats::heap_graph_non_finalized_graph);
+ storage_->IncrementStats(stats::heap_graph_non_finalized_graph);
return false;
}
sequence_state->current_upid = upid;
@@ -311,7 +309,7 @@
ObjectTable::RowReference HeapGraphTracker::GetOrInsertObject(
SequenceState* sequence_state,
uint64_t object_id) {
- auto object_table = context_->storage->mutable_heap_graph_object_table();
+ auto object_table = storage_->mutable_heap_graph_object_table();
auto* ptr = sequence_state->object_id_to_db_row.Find(object_id);
if (!ptr) {
auto id_and_row = object_table->Insert({sequence_state->current_upid,
@@ -333,7 +331,7 @@
ClassTable::RowReference HeapGraphTracker::GetOrInsertType(
SequenceState* sequence_state,
uint64_t type_id) {
- auto class_table = context_->storage->mutable_heap_graph_class_table();
+ auto class_table = storage_->mutable_heap_graph_class_table();
auto* ptr = sequence_state->type_id_to_db_row.Find(type_id);
if (!ptr) {
auto id_and_row =
@@ -372,7 +370,7 @@
}
uint32_t reference_set_id =
- context_->storage->heap_graph_reference_table().row_count();
+ storage_->heap_graph_reference_table().row_count();
bool any_references = false;
ObjectTable::Id owner_id = owner_row_ref.id();
@@ -384,7 +382,7 @@
owned_row_ref = GetOrInsertObject(&sequence_state, owned_object_id);
auto ref_id_and_row =
- context_->storage->mutable_heap_graph_reference_table()->Insert(
+ storage_->mutable_heap_graph_reference_table()->Insert(
{reference_set_id,
owner_id,
owned_row_ref ? base::make_optional(owned_row_ref->id())
@@ -462,15 +460,15 @@
type = str.substr(0, space);
str = str.substr(space + 1);
}
- StringId field_name = context_->storage->InternString(str);
- StringId type_name = context_->storage->InternString(type);
+ StringId field_name = storage_->InternString(str);
+ StringId type_name = storage_->InternString(type);
sequence_state.interned_fields.Insert(intern_id,
InternedField{field_name, type_name});
auto it = sequence_state.references_for_field_name_id.find(intern_id);
if (it != sequence_state.references_for_field_name_id.end()) {
- auto hgr = context_->storage->mutable_heap_graph_reference_table();
+ auto hgr = storage_->mutable_heap_graph_reference_table();
for (ReferenceTable::RowNumber reference_row_num : it->second) {
auto row_ref = reference_row_num.ToRowReference(hgr);
row_ref.set_field_name(field_name);
@@ -501,7 +499,7 @@
PERFETTO_ELOG("Invalid first packet index %" PRIu64 " (!= 0)", index);
}
- context_->storage->IncrementIndexedStats(
+ storage_->IncrementIndexedStats(
stats::heap_graph_missing_packet,
static_cast<int>(sequence_state.current_upid));
}
@@ -518,7 +516,7 @@
if (it != sequence_state->interned_types.end())
return &it->second;
}
- context_->storage->IncrementIndexedStats(
+ storage_->IncrementIndexedStats(
stats::heap_graph_malformed_packet,
static_cast<int>(sequence_state->current_upid));
return nullptr;
@@ -541,7 +539,7 @@
auto it = sequence_state.interned_location_names.find(
*interned_type.location_id);
if (it == sequence_state.interned_location_names.end()) {
- context_->storage->IncrementIndexedStats(
+ storage_->IncrementIndexedStats(
stats::heap_graph_invalid_string_id,
static_cast<int>(sequence_state.current_upid));
} else {
@@ -555,7 +553,7 @@
auto sz_obj_it =
sequence_state.deferred_size_objects_for_type_.find(type_id);
if (sz_obj_it != sequence_state.deferred_size_objects_for_type_.end()) {
- auto* hgo = context_->storage->mutable_heap_graph_object_table();
+ auto* hgo = storage_->mutable_heap_graph_object_table();
for (ObjectTable::RowNumber obj_row_num : sz_obj_it->second) {
auto obj_row_ref = obj_row_num.ToRowReference(hgo);
obj_row_ref.set_self_size(
@@ -570,14 +568,14 @@
sequence_state.deferred_reference_objects_for_type_.end()) {
for (ObjectTable::RowNumber obj_row_number : ref_obj_it->second) {
auto obj_row_ref = obj_row_number.ToRowReference(
- context_->storage->mutable_heap_graph_object_table());
+ storage_->mutable_heap_graph_object_table());
const InternedType* current_type = &interned_type;
if (interned_type.no_fields) {
continue;
}
size_t field_offset_in_cls = 0;
ForReferenceSet(
- context_->storage.get(), obj_row_ref,
+ storage_, obj_row_ref,
[this, ¤t_type, &sequence_state,
&field_offset_in_cls](ReferenceTable::RowReference ref) {
while (current_type && field_offset_in_cls >=
@@ -596,7 +594,7 @@
auto* ptr = sequence_state.interned_fields.Find(field_id);
if (!ptr) {
PERFETTO_DLOG("Invalid field id.");
- context_->storage->IncrementIndexedStats(
+ storage_->IncrementIndexedStats(
stats::heap_graph_malformed_packet,
static_cast<int>(sequence_state.current_upid));
return true;
@@ -622,40 +620,36 @@
type_row_ref.set_kind(interned_type.kind);
base::StringView normalized_type =
- NormalizeTypeName(context_->storage->GetString(interned_type.name));
+ NormalizeTypeName(storage_->GetString(interned_type.name));
base::Optional<StringId> class_package;
if (location_name) {
base::Optional<std::string> package_name =
- PackageFromLocation(context_->storage.get(),
- context_->storage->GetString(*location_name));
+ PackageFromLocation(storage_, storage_->GetString(*location_name));
if (package_name) {
- class_package =
- context_->storage->InternString(base::StringView(*package_name));
+ class_package = storage_->InternString(base::StringView(*package_name));
}
}
if (!class_package) {
- auto app_id = context_->storage->process_table()
+ auto app_id = storage_->process_table()
.android_appid()[sequence_state.current_upid];
if (app_id) {
- auto pkg_row =
- context_->storage->package_list_table().uid().IndexOf(*app_id);
+ auto pkg_row = storage_->package_list_table().uid().IndexOf(*app_id);
if (pkg_row) {
class_package =
- context_->storage->package_list_table().package_name()[*pkg_row];
+ storage_->package_list_table().package_name()[*pkg_row];
}
}
}
- class_to_rows_[std::make_pair(
- class_package,
- context_->storage->InternString(normalized_type))]
+ class_to_rows_[std::make_pair(class_package,
+ storage_->InternString(normalized_type))]
.emplace_back(type_row_ref.ToRowNumber());
}
if (!sequence_state.deferred_size_objects_for_type_.empty() ||
!sequence_state.deferred_reference_objects_for_type_.empty()) {
- context_->storage->IncrementIndexedStats(
+ storage_->IncrementIndexedStats(
stats::heap_graph_malformed_packet,
static_cast<int>(sequence_state.current_upid));
}
@@ -668,13 +662,13 @@
if (!ptr)
continue;
- ObjectTable::RowReference row_ref = ptr->ToRowReference(
- context_->storage->mutable_heap_graph_object_table());
+ ObjectTable::RowReference row_ref =
+ ptr->ToRowReference(storage_->mutable_heap_graph_object_table());
auto it_and_success = roots_[std::make_pair(sequence_state.current_upid,
sequence_state.current_ts)]
.emplace(*ptr);
if (it_and_success.second)
- MarkRoot(context_->storage.get(), row_ref, root.root_type);
+ MarkRoot(storage_, row_ref, root.root_type);
}
}
@@ -687,9 +681,8 @@
ObjectTable::Id obj,
StringId field) {
base::Optional<ObjectTable::Id> referred;
- auto obj_row_ref =
- *context_->storage->heap_graph_object_table().FindById(obj);
- ForReferenceSet(context_->storage.get(), obj_row_ref,
+ auto obj_row_ref = *storage_->heap_graph_object_table().FindById(obj);
+ ForReferenceSet(storage_, obj_row_ref,
[&](ReferenceTable::RowReference ref) -> bool {
if (ref.field_name() == field) {
referred = ref.owned_id();
@@ -720,8 +713,8 @@
//
// `.size` should be attributed as the native size of Object
- const auto& class_tbl = context_->storage->heap_graph_class_table();
- auto& objects_tbl = *context_->storage->mutable_heap_graph_object_table();
+ const auto& class_tbl = storage_->heap_graph_class_table();
+ auto& objects_tbl = *storage_->mutable_heap_graph_object_table();
struct Cleaner {
ObjectTable::Id referent;
@@ -783,10 +776,9 @@
void HeapGraphTracker::PopulateSuperClasses(const SequenceState& seq) {
// Maps from normalized class name and location, to superclass.
std::map<ClassDescriptor, ClassDescriptor> superclass_map =
- BuildSuperclassMap(seq.current_upid, seq.current_ts,
- context_->storage.get());
+ BuildSuperclassMap(seq.current_upid, seq.current_ts, storage_);
- auto* classes_tbl = context_->storage->mutable_heap_graph_class_table();
+ auto* classes_tbl = storage_->mutable_heap_graph_class_table();
std::map<ClassDescriptor, ClassTable::Id> class_to_id;
for (uint32_t idx = 0; idx < classes_tbl->row_count(); ++idx) {
class_to_id[{classes_tbl->name()[idx], classes_tbl->location()[idx]}] =
@@ -798,13 +790,13 @@
// mapping was generated on the current sequence) - if we cannot identify
// a superclass we will just skip.
for (uint32_t idx = 0; idx < classes_tbl->row_count(); ++idx) {
- auto name = context_->storage->GetString(classes_tbl->name()[idx]);
+ auto name = storage_->GetString(classes_tbl->name()[idx]);
auto location = classes_tbl->location()[idx];
auto normalized = GetNormalizedType(name);
if (normalized.is_static_class || normalized.number_of_arrays > 0)
continue;
- StringId class_name_id = context_->storage->InternString(normalized.name);
+ StringId class_name_id = storage_->InternString(normalized.name);
auto map_it = superclass_map.find({class_name_id, location});
if (map_it == superclass_map.end()) {
continue;
@@ -941,12 +933,12 @@
std::unique_ptr<tables::ExperimentalFlamegraphNodesTable>
HeapGraphTracker::BuildFlamegraph(const int64_t current_ts,
const UniquePid current_upid) {
- auto profile_type = context_->storage->InternString("graph");
- auto java_mapping = context_->storage->InternString("JAVA");
+ auto profile_type = storage_->InternString("graph");
+ auto java_mapping = storage_->InternString("JAVA");
std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> tbl(
new tables::ExperimentalFlamegraphNodesTable(
- context_->storage->mutable_string_pool(), nullptr));
+ storage_->mutable_string_pool(), nullptr));
auto it = roots_.find(std::make_pair(current_upid, current_ts));
if (it == roots_.end()) {
@@ -958,7 +950,7 @@
alloc_row.upid = current_upid;
alloc_row.profile_type = profile_type;
alloc_row.depth = 0;
- alloc_row.name = context_->storage->InternString(
+ alloc_row.name = storage_->InternString(
"ERROR: INCOMPLETE GRAPH (try increasing buffer size)");
alloc_row.map_name = java_mapping;
alloc_row.count = 1;
@@ -974,17 +966,15 @@
}
const std::set<ObjectTable::RowNumber>& roots = it->second;
- auto* object_table = context_->storage->mutable_heap_graph_object_table();
+ auto* object_table = storage_->mutable_heap_graph_object_table();
// First pass to calculate shortest paths
for (ObjectTable::RowNumber root : roots) {
- UpdateShortestPaths(context_->storage.get(),
- root.ToRowReference(object_table));
+ UpdateShortestPaths(storage_, root.ToRowReference(object_table));
}
PathFromRoot init_path;
for (ObjectTable::RowNumber root : roots) {
- FindPathFromRoot(context_->storage.get(), root.ToRowReference(object_table),
- &init_path);
+ FindPathFromRoot(storage_, root.ToRowReference(object_table), &init_path);
}
std::vector<int64_t> node_to_cumulative_size(init_path.nodes.size());
@@ -1030,7 +1020,7 @@
void HeapGraphTracker::FinalizeAllProfiles() {
if (!sequence_state_.empty()) {
- context_->storage->IncrementStats(stats::heap_graph_non_finalized_graph);
+ storage_->IncrementStats(stats::heap_graph_non_finalized_graph);
// There might still be valuable data even though the trace is truncated.
while (!sequence_state_.empty()) {
FinalizeProfile(sequence_state_.begin()->first);
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.h b/src/trace_processor/importers/proto/heap_graph_tracker.h
index c73c902..6fd4fda 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.h
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.h
@@ -93,11 +93,12 @@
std::vector<uint64_t> object_ids;
};
- explicit HeapGraphTracker(TraceProcessorContext* context);
+ explicit HeapGraphTracker(TraceStorage* storage);
static HeapGraphTracker* GetOrCreate(TraceProcessorContext* context) {
if (!context->heap_graph_tracker) {
- context->heap_graph_tracker.reset(new HeapGraphTracker(context));
+ context->heap_graph_tracker.reset(
+ new HeapGraphTracker(context->storage.get()));
}
return static_cast<HeapGraphTracker*>(context->heap_graph_tracker.get());
}
@@ -230,7 +231,7 @@
// all the other tables have been fully populated.
void PopulateNativeSize(const SequenceState& seq);
- TraceProcessorContext* const context_;
+ TraceStorage* const storage_;
std::map<uint32_t, SequenceState> sequence_state_;
std::map<std::pair<base::Optional<StringId>, StringId>,
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc b/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
index 950f3e9..491082a 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
@@ -65,7 +65,7 @@
context.process_tracker.reset(new ProcessTracker(&context));
context.process_tracker->GetOrCreateProcess(kPid);
- HeapGraphTracker tracker(&context);
+ HeapGraphTracker tracker(context.storage.get());
StringPool::Id normal_kind = context.storage->InternString("KIND_NORMAL");
@@ -208,7 +208,7 @@
context.process_tracker.reset(new ProcessTracker(&context));
context.process_tracker->GetOrCreateProcess(kPid);
- HeapGraphTracker tracker(&context);
+ HeapGraphTracker tracker(context.storage.get());
constexpr uint64_t kField = 1;
constexpr uint64_t kLocation = 0;
diff --git a/src/trace_processor/importers/proto/metadata_tracker.cc b/src/trace_processor/importers/proto/metadata_tracker.cc
index 7f324b9..a5e6928 100644
--- a/src/trace_processor/importers/proto/metadata_tracker.cc
+++ b/src/trace_processor/importers/proto/metadata_tracker.cc
@@ -30,14 +30,12 @@
}
-MetadataTracker::MetadataTracker(TraceProcessorContext* context)
- : context_(context) {
+MetadataTracker::MetadataTracker(TraceStorage* storage) : storage_(storage) {
for (uint32_t i = 0; i < kNumKeys; ++i) {
- key_ids_[i] = context->storage->InternString(metadata::kNames[i]);
+ key_ids_[i] = storage->InternString(metadata::kNames[i]);
}
for (uint32_t i = 0; i < kNumKeyTypes; ++i) {
- key_type_ids_[i] =
- context->storage->InternString(metadata::kKeyTypeNames[i]);
+ key_type_ids_[i] = storage->InternString(metadata::kKeyTypeNames[i]);
}
}
@@ -48,11 +46,11 @@
// When the trace_uuid is set, store a copy in a crash key, so in case of
// a crash in the pipelines we can tell which trace caused the crash.
if (key == metadata::trace_uuid && value.type == Variadic::kString) {
- auto uuid_string_view = context_->storage->GetString(value.string_value);
+ auto uuid_string_view = storage_->GetString(value.string_value);
g_crash_key_uuid.Set(uuid_string_view);
}
- auto* metadata_table = context_->storage->mutable_metadata_table();
+ auto* metadata_table = storage_->mutable_metadata_table();
uint32_t key_idx = static_cast<uint32_t>(key);
base::Optional<uint32_t> opt_row =
metadata_table->name().IndexOf(metadata::kNames[key_idx]);
@@ -74,7 +72,7 @@
// KeyType::kMulti not yet supported by this method:
PERFETTO_CHECK(metadata::kKeyTypes[key] == metadata::KeyType::kSingle);
- auto* metadata_table = context_->storage->mutable_metadata_table();
+ auto* metadata_table = storage_->mutable_metadata_table();
uint32_t key_idx = static_cast<uint32_t>(key);
uint32_t row =
metadata_table->name().IndexOf(metadata::kNames[key_idx]).value();
@@ -108,7 +106,7 @@
row.name = key_ids_[key_idx];
row.key_type = key_type_ids_[static_cast<size_t>(metadata::KeyType::kMulti)];
- auto* metadata_table = context_->storage->mutable_metadata_table();
+ auto* metadata_table = storage_->mutable_metadata_table();
auto id_and_row = metadata_table->Insert(row);
WriteValue(id_and_row.row, value);
return id_and_row.id;
@@ -119,14 +117,14 @@
row.name = key;
row.key_type = key_type_ids_[static_cast<size_t>(metadata::KeyType::kSingle)];
- auto* metadata_table = context_->storage->mutable_metadata_table();
+ auto* metadata_table = storage_->mutable_metadata_table();
auto id_and_row = metadata_table->Insert(row);
WriteValue(id_and_row.row, value);
return id_and_row.id;
}
void MetadataTracker::WriteValue(uint32_t row, Variadic value) {
- auto* metadata_table = context_->storage->mutable_metadata_table();
+ auto* metadata_table = storage_->mutable_metadata_table();
switch (value.type) {
case Variadic::Type::kInt:
metadata_table->mutable_int_value()->Set(row, value.int_value);
diff --git a/src/trace_processor/importers/proto/metadata_tracker.h b/src/trace_processor/importers/proto/metadata_tracker.h
index 23f48a1..c3e587e 100644
--- a/src/trace_processor/importers/proto/metadata_tracker.h
+++ b/src/trace_processor/importers/proto/metadata_tracker.h
@@ -22,12 +22,10 @@
namespace perfetto {
namespace trace_processor {
-class TraceProcessorContext;
-
// Tracks information in the metadata table.
class MetadataTracker {
public:
- MetadataTracker(TraceProcessorContext* context);
+ MetadataTracker(TraceStorage* storage);
// Example usage:
// SetMetadata(metadata::benchmark_name,
@@ -66,7 +64,7 @@
std::array<StringId, kNumKeyTypes> key_type_ids_;
uint32_t chrome_metadata_bundle_count_ = 0;
- TraceProcessorContext* context_;
+ TraceStorage* storage_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/profile_module.cc b/src/trace_processor/importers/proto/profile_module.cc
index a255502..a8cf943 100644
--- a/src/trace_processor/importers/proto/profile_module.cc
+++ b/src/trace_processor/importers/proto/profile_module.cc
@@ -15,6 +15,7 @@
*/
#include "src/trace_processor/importers/proto/profile_module.h"
+#include <string>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_utils.h"
@@ -28,11 +29,13 @@
#include "src/trace_processor/importers/proto/profile_packet_utils.h"
#include "src/trace_processor/importers/proto/profiler_util.h"
#include "src/trace_processor/importers/proto/stack_profile_tracker.h"
+#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/tables/profiler_tables.h"
#include "src/trace_processor/timestamped_trace_piece.h"
#include "src/trace_processor/trace_sorter.h"
#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/stack_traces_util.h"
#include "protos/perfetto/common/builtin_clock.pbzero.h"
#include "protos/perfetto/common/perf_events.pbzero.h"
@@ -446,7 +449,7 @@
StringId build_id;
// TODO(b/148109467): Remove workaround once all active Chrome versions
// write raw bytes instead of a string as build_id.
- if (module_symbols.build_id().size == 33) {
+ if (util::IsHexModuleId(module_symbols.build_id())) {
build_id = context_->storage->InternString(module_symbols.build_id());
} else {
build_id = context_->storage->InternString(base::StringView(base::ToHex(
@@ -590,5 +593,18 @@
}
}
+void ProfileModule::NotifyEndOfFile() {
+ for (auto it = context_->storage->stack_profile_mapping_table().IterateRows();
+ it; ++it) {
+ NullTermStringView path = context_->storage->GetString(it.name());
+ NullTermStringView build_id = context_->storage->GetString(it.build_id());
+
+ if (path.StartsWith("/data/local/tmp/") && build_id.empty()) {
+ context_->storage->IncrementStats(
+ stats::symbolization_tmp_build_id_not_found);
+ }
+ }
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/proto/profile_module.h b/src/trace_processor/importers/proto/profile_module.h
index 92f3384..acc2794 100644
--- a/src/trace_processor/importers/proto/profile_module.h
+++ b/src/trace_processor/importers/proto/profile_module.h
@@ -44,6 +44,8 @@
const TimestampedTracePiece& ttp,
uint32_t field_id) override;
+ void NotifyEndOfFile() override;
+
private:
// chrome stack sampling:
ModuleResult TokenizeStreamingProfilePacket(
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index b6ba662..cbb42cc 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -226,12 +226,14 @@
storage_ = new TraceStorage();
context_.storage.reset(storage_);
context_.track_tracker.reset(new TrackTracker(&context_));
- context_.global_args_tracker.reset(new GlobalArgsTracker(&context_));
+ context_.global_args_tracker.reset(
+ new GlobalArgsTracker(context_.storage.get()));
context_.global_stack_profile_tracker.reset(
new GlobalStackProfileTracker());
context_.args_tracker.reset(new ArgsTracker(&context_));
context_.args_translation_table.reset(new ArgsTranslationTable(storage_));
- context_.metadata_tracker.reset(new MetadataTracker(&context_));
+ context_.metadata_tracker.reset(
+ new MetadataTracker(context_.storage.get()));
event_ = new MockEventTracker(&context_);
context_.event_tracker.reset(event_);
sched_ = new MockSchedEventTracker(&context_);
@@ -241,7 +243,7 @@
slice_ = new NiceMock<MockSliceTracker>(&context_);
context_.slice_tracker.reset(slice_);
context_.slice_translation_table.reset(new SliceTranslationTable(storage_));
- clock_ = new ClockTracker(&context_);
+ clock_ = new ClockTracker(context_.storage.get());
context_.clock_tracker.reset(clock_);
context_.flow_tracker.reset(new FlowTracker(&context_));
context_.sorter.reset(new TraceSorter(&context_, CreateParser(),
diff --git a/src/trace_processor/importers/proto/stack_profile_tracker.cc b/src/trace_processor/importers/proto/stack_profile_tracker.cc
index 384adbf..2451142 100644
--- a/src/trace_processor/importers/proto/stack_profile_tracker.cc
+++ b/src/trace_processor/importers/proto/stack_profile_tracker.cc
@@ -20,6 +20,7 @@
#include "perfetto/ext/base/string_utils.h"
#include "src/trace_processor/importers/proto/profiler_util.h"
#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/stack_traces_util.h"
namespace perfetto {
namespace trace_processor {
@@ -74,7 +75,7 @@
// identifier which is already in Hex and doesn't need conversion.
// TODO(b/148109467): Remove workaround once all active Chrome versions
// write raw bytes instead of a string as build_id.
- if (raw_build_id_str.size() == 33) {
+ if (util::IsHexModuleId(raw_build_id_str)) {
build_id = raw_build_id;
} else {
std::string hex_build_id =
diff --git a/src/trace_processor/importers/systrace/systrace_parser.cc b/src/trace_processor/importers/systrace/systrace_parser.cc
index d973c8c..8960a39 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser.cc
@@ -225,9 +225,16 @@
StringId track_name_id = context_->storage->InternString(point.str_value);
UniquePid upid =
context_->process_tracker->GetOrCreateProcess(point.tgid);
- auto track_set_id =
- context_->async_track_set_tracker->InternProcessTrackSet(
- upid, track_name_id);
+
+ // Promote DeviceStateChanged to its own top level track.
+ AsyncTrackSetTracker::TrackSetId track_set_id;
+ if (point.str_value == "DeviceStateChanged") {
+ track_set_id = context_->async_track_set_tracker->InternGlobalTrackSet(
+ track_name_id);
+ } else {
+ track_set_id = context_->async_track_set_tracker->InternProcessTrackSet(
+ upid, track_name_id);
+ }
if (point.phase == 'N') {
TrackId track_id =
diff --git a/src/trace_processor/metrics/sql/BUILD.gn b/src/trace_processor/metrics/sql/BUILD.gn
index 412e395..2f6be48 100644
--- a/src/trace_processor/metrics/sql/BUILD.gn
+++ b/src/trace_processor/metrics/sql/BUILD.gn
@@ -103,9 +103,12 @@
"chrome/chrome_input_to_browser_intervals.sql",
"chrome/chrome_performance_mark_hashes.sql",
"chrome/chrome_processes.sql",
+ "chrome/chrome_scroll_jank_caused_by_scheduling.sql",
"chrome/chrome_slice_names.sql",
+ "chrome/chrome_stack_samples_for_task.sql",
"chrome/chrome_unsymbolized_args.sql",
"chrome/chrome_tasks.sql",
+ "chrome/chrome_tasks_delaying_input_processing.sql",
"chrome/chrome_thread_slice.sql",
"chrome/chrome_user_event_hashes.sql",
"chrome/cpu_time_by_category.sql",
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals.sql b/src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals.sql
index 5b029b3..9f64718 100644
--- a/src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals.sql
+++ b/src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals.sql
@@ -196,7 +196,7 @@
-- and is not a fling generated by the viz compositor thread(GPU process).
ancestor_slices.name = "sendTouchEvent"
)
- > 0
+ = 1
THEN FALSE
ELSE TRUE
END AS blocked_gesture
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_scroll_jank_caused_by_scheduling.sql b/src/trace_processor/metrics/sql/chrome/chrome_scroll_jank_caused_by_scheduling.sql
new file mode 100644
index 0000000..9fe4516
--- /dev/null
+++ b/src/trace_processor/metrics/sql/chrome/chrome_scroll_jank_caused_by_scheduling.sql
@@ -0,0 +1,90 @@
+--
+-- Copyright 2022 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
+--
+-- https://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.
+
+SELECT RUN_METRIC('chrome/chrome_input_to_browser_intervals.sql');
+
+-- Script params:
+-- {{dur_causes_jank_ms}} : The duration of a task barrage on the Chrome
+-- main thread that will delay input causing jank.
+
+-- Filter intervals to only durations longer than {{dur_causes_jank_ms}}.
+DROP VIEW IF EXISTS chrome_input_to_browser_longer_intervals;
+CREATE VIEW chrome_input_to_browser_longer_intervals AS
+SELECT
+ *
+FROM chrome_input_to_browser_intervals
+WHERE
+ (window_end_ts - window_start_ts) >= {{dur_causes_jank_ms}} * 1e6;
+
+-- Assign tasks to each delay interval that we could have started
+-- processing input but didn't on the main thread, and sum those
+-- tasks.
+-- We filter java out here as we're interested in tasks that delayed
+-- yielding to java native work, and we filter tasks that are more
+-- than 8ms here as those are handled separately and are not regarded
+-- as scheduling issues.
+DROP VIEW IF EXISTS chrome_task_barrages_per_interval;
+CREATE VIEW chrome_task_barrages_per_interval AS
+SELECT
+ GROUP_CONCAT(DISTINCT full_name) AS full_name,
+ SUM(dur / 1e6) AS total_duration_ms,
+ SUM(thread_dur / 1e6) AS total_thread_duration_ms,
+ MIN(id) AS first_id_per_task_barrage,
+ MAX(id) AS last_id_per_task_barrage,
+ COUNT(*) as count,
+ window_start_id,
+ window_start_ts,
+ window_end_id,
+ window_end_ts
+FROM
+ (SELECT * FROM (
+ (
+ SELECT
+ chrome_tasks.full_name AS full_name,
+ chrome_tasks.dur AS dur,
+ chrome_tasks.thread_dur AS thread_dur,
+ chrome_tasks.ts AS ts,
+ chrome_tasks.id,
+ chrome_tasks.upid
+ FROM
+ chrome_tasks
+ WHERE
+ chrome_tasks.thread_name = "CrBrowserMain"
+ AND task_type != "java"
+ AND task_type != "choreographer"
+ ORDER BY chrome_tasks.ts
+ ) tasks
+ JOIN chrome_input_to_browser_longer_intervals
+ ON (tasks.ts + tasks.dur) >
+ chrome_input_to_browser_longer_intervals.window_start_ts
+ AND (tasks.ts + tasks.dur) <
+ chrome_input_to_browser_longer_intervals.window_end_ts
+ AND tasks.ts > chrome_input_to_browser_longer_intervals.window_start_ts
+ AND tasks.ts < chrome_input_to_browser_longer_intervals.window_end_ts
+ -- For cases when there are multiple chrome instances.
+ and tasks.upid = chrome_input_to_browser_longer_intervals.upid)
+ ORDER BY
+ window_start_ts, window_end_ts
+ )
+ GROUP BY window_start_ts, window_end_ts;
+
+-- Filter to task barrages that took more than 8ms, as barrages
+-- that lasted less than that are unlikely to have caused jank.
+DROP VIEW IF EXISTS chrome_scroll_jank_caused_by_scheduling;
+CREATE VIEW chrome_scroll_jank_caused_by_scheduling AS
+ SELECT *
+ FROM chrome_task_barrages_per_interval
+ WHERE total_duration_ms > {{dur_causes_jank_ms}} AND count > 1
+ ORDER BY total_duration_ms DESC;
\ No newline at end of file
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_stack_samples_for_task.sql b/src/trace_processor/metrics/sql/chrome/chrome_stack_samples_for_task.sql
new file mode 100644
index 0000000..a0523f3
--- /dev/null
+++ b/src/trace_processor/metrics/sql/chrome/chrome_stack_samples_for_task.sql
@@ -0,0 +1,165 @@
+--
+-- Copyright 2022 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
+--
+-- https://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.
+--
+
+-- Params:
+-- @target_duration_ms: find stack samples on tasks that are longer than
+-- this value
+
+-- @thread_name: thread name to look for stack samples on
+
+-- @task_name: a task name following chrome_tasks.sql naming convention to
+-- find stack samples on.
+
+
+SELECT RUN_METRIC('chrome/chrome_tasks.sql');
+
+
+SELECT CREATE_FUNCTION(
+ 'DescribeSymbol(symbol STRING, frame_name STRING)',
+ 'STRING',
+ 'SELECT COALESCE($symbol,
+ CASE WHEN demangle($frame_name) IS NULL
+ THEN $frame_name
+ ELSE demangle($frame_name)
+ END)'
+);
+
+-- Get all Chrome tasks that match a specific name on a specific thread.
+-- The timestamps for those tasks are going to be used later on to gather
+-- information about stack traces that were collected during running them.
+DROP VIEW IF EXISTS chrome_targeted_task;
+CREATE VIEW chrome_targeted_task AS
+SELECT
+ chrome_tasks.full_name AS full_name,
+ chrome_tasks.dur AS dur,
+ chrome_tasks.ts AS ts,
+ chrome_tasks.id AS id,
+ utid AS utid
+FROM
+ chrome_tasks
+WHERE
+ chrome_tasks.dur >= {{target_duration_ms}} * 1e6
+ AND chrome_tasks.thread_name = {{thread_name}}
+ AND chrome_tasks.full_name = {{task_name}};
+
+
+-- Get all frames attached to callsite ids, as frames can be
+-- reused between stack frames, callsite ids are unique per
+-- stack sample.
+DROP VIEW IF EXISTS chrome_non_symbolized_frames;
+CREATE VIEW chrome_non_symbolized_frames AS
+SELECT
+ frames.name as frame_name,
+ callsite.id as callsite_id,
+ *
+FROM
+ stack_profile_frame frames
+ JOIN stack_profile_callsite callsite
+ ON callsite.frame_id = frames.id;
+
+-- Only lowest child frames are join-able with chrome_non_symbolized_frames
+-- which we need for the time at which the callstack was taken.
+DROP VIEW IF EXISTS chrome_symbolized_child_frames;
+CREATE VIEW chrome_symbolized_child_frames AS
+SELECT
+ thread.name as thread_name,
+ sample.utid AS sample_utid,
+ *
+FROM
+ chrome_non_symbolized_frames frames
+ JOIN cpu_profile_stack_sample sample USING(callsite_id)
+ JOIN thread USING(utid)
+ JOIN process USING(upid);
+
+-- Not all frames are symbolized, in cases where those frames
+-- are not symbolized, use the file name as it is usually descriptive
+-- of the function.
+DROP VIEW IF EXISTS chrome_thread_symbolized_child_frames;
+CREATE VIEW chrome_thread_symbolized_child_frames AS
+SELECT
+ DescribeSymbol(symbol.name, frame_name) AS description,
+ depth,
+ ts,
+ callsite_id,
+ sample_utid
+FROM chrome_symbolized_child_frames
+ LEFT JOIN stack_profile_symbol symbol USING(symbol_set_id)
+WHERE thread_name = {{thread_name}} ORDER BY ts DESC;
+
+-- Since only leaf stack frames have a timestamp, let's export this
+-- timestamp to all it's ancestor frames to use it later on for
+-- filtering frames within specific windows
+DROP VIEW IF EXISTS chrome_non_symbolized_frames_timed;
+CREATE VIEW chrome_non_symbolized_frames_timed AS
+ SELECT
+ chrome_non_symbolized_frames.frame_name,
+ chrome_non_symbolized_frames.depth,
+ chrome_thread_symbolized_child_frames.ts,
+ chrome_thread_symbolized_child_frames.sample_utid,
+ chrome_non_symbolized_frames.callsite_id,
+ symbol_set_id,
+ chrome_non_symbolized_frames.frame_id
+FROM chrome_thread_symbolized_child_frames
+ JOIN experimental_ancestor_stack_profile_callsite(
+ chrome_thread_symbolized_child_frames.callsite_id) child
+ JOIN chrome_non_symbolized_frames
+ ON chrome_non_symbolized_frames.callsite_id = child.id;
+
+DROP VIEW IF EXISTS chrome_frames_timed_and_symbolized;
+CREATE VIEW chrome_frames_timed_and_symbolized AS
+ SELECT
+ DescribeSymbol(symbol.name, frame_name) AS description,
+ ts,
+ depth,
+ callsite_id,
+ sample_utid
+FROM chrome_non_symbolized_frames_timed
+ LEFT JOIN stack_profile_symbol symbol
+ USING(symbol_set_id)
+ORDER BY DEPTH ASC;
+
+-- Union leaf stack frames with all stack frames after the timestamp
+-- is attached to get a view of all frames timestamped.
+DROP VIEW IF EXISTS all_frames;
+CREATE VIEW all_frames AS
+SELECT
+ *
+FROM
+ (SELECT
+ * FROM
+ chrome_frames_timed_and_symbolized
+ UNION
+ SELECT
+ description,
+ ts,
+ depth,
+ callsite_id,
+ sample_utid
+ FROM chrome_thread_symbolized_child_frames)
+ORDER BY depth ASC;
+
+-- Filter stack samples that happened only during the specified
+-- task on the specified thread.
+DROP VIEW IF EXISTS chrome_stack_samples_for_task;
+CREATE VIEW chrome_stack_samples_for_task AS
+SELECT
+ all_frames.*
+FROM
+ all_frames JOIN
+ chrome_targeted_task ON
+ all_frames.sample_utid = chrome_targeted_task.utid
+ AND all_frames.ts >= chrome_targeted_task.ts
+ AND all_frames.ts <= chrome_targeted_task.ts + chrome_targeted_task.dur;
\ No newline at end of file
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_tasks_delaying_input_processing.sql b/src/trace_processor/metrics/sql/chrome/chrome_tasks_delaying_input_processing.sql
new file mode 100644
index 0000000..dfab111
--- /dev/null
+++ b/src/trace_processor/metrics/sql/chrome/chrome_tasks_delaying_input_processing.sql
@@ -0,0 +1,87 @@
+--
+-- Copyright 2022 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
+--
+-- https://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.
+
+-- Script params:
+-- {{duration_causing_jank_ms}} : The duration of a single task that would cause
+-- jank, by delaying input from being handled on the main thread.
+
+SELECT RUN_METRIC('chrome/chrome_tasks.sql');
+SELECT RUN_METRIC('chrome/chrome_input_to_browser_intervals.sql');
+
+-- Get the tasks that was running for more than 8ms within windows
+-- that we could have started processing input but did not on the
+-- main thread, because it was blocked by those tasks.
+DROP VIEW IF EXISTS chrome_tasks_delaying_input_processing_unaggregated;
+CREATE VIEW chrome_tasks_delaying_input_processing_unaggregated AS
+SELECT
+ tasks.full_name AS full_name,
+ tasks.dur / 1e6 AS duration_ms,
+ id AS slice_id,
+ thread_dur / 1e6 AS thread_dur_ms,
+ chrome_input_to_browser_intervals.window_start_id,
+ chrome_input_to_browser_intervals.window_end_id
+FROM
+ (
+ (
+ SELECT
+ chrome_tasks.full_name AS full_name,
+ chrome_tasks.dur AS dur,
+ chrome_tasks.ts AS ts,
+ chrome_tasks.id AS id,
+ chrome_tasks.upid AS upid,
+ thread_dur
+ FROM
+ chrome_tasks
+ WHERE
+ chrome_tasks.dur >= {{duration_causing_jank_ms}} * 1e6
+ and chrome_tasks.thread_name = "CrBrowserMain"
+ ) tasks
+ JOIN chrome_input_to_browser_intervals
+ ON tasks.ts + tasks.dur > chrome_input_to_browser_intervals.window_start_ts
+ AND tasks.ts + tasks.dur < chrome_input_to_browser_intervals.window_end_ts
+ AND tasks.upid = chrome_input_to_browser_intervals.upid
+ );
+
+-- Same task can delay multiple GestureUpdates, this step dedups
+-- multiple occrences of the same slice_id
+DROP VIEW IF EXISTS chrome_tasks_delaying_input_processing;
+CREATE VIEW chrome_tasks_delaying_input_processing AS
+SELECT
+ full_name,
+ duration_ms,
+ slice_id,
+ thread_dur_ms
+FROM chrome_tasks_delaying_input_processing_unaggregated
+GROUP BY slice_id;
+
+-- Get the tasks that were running for more than 8ms within windows
+-- that we could have started processing input but did not on the
+-- main thread, because it was blocked by those tasks.
+DROP VIEW IF EXISTS chrome_tasks_delaying_input_processing_summary;
+CREATE VIEW chrome_tasks_delaying_input_processing_summary AS
+SELECT
+ full_name AS full_name,
+ AVG(duration_ms) AS avg_duration_ms,
+ AVG(thread_dur_ms) AS avg_thread_duration_ms,
+ MIN(duration_ms) AS min_task_duration,
+ MAX(duration_ms) as max_task_duration,
+ SUM(duration_ms) AS total_duration_ms,
+ SUM(thread_dur_ms) AS total_thread_duration_ms,
+ GROUP_CONCAT(slice_id, '-') AS slice_ids,
+ COUNT(*) AS count
+FROM
+ chrome_tasks_delaying_input_processing
+GROUP BY
+ full_name;
\ No newline at end of file
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index faa83e6..748172b 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -185,6 +185,10 @@
"Time (us) the heapprofd client was blocked on the spinlock."), \
F(heapprofd_last_profile_timestamp, kIndexed, kInfo, kTrace, \
"The timestamp (in trace time) for the last dump for a process"), \
+ F(symbolization_tmp_build_id_not_found, kSingle, kError, kAnalysis, \
+ "Number of file mappings in /data/local/tmp without a build id. " \
+ "Symbolization doesn't work for executables in /data/local/tmp " \
+ "because of SELinux. Please use /data/local/tests"), \
F(metatrace_overruns, kSingle, kError, kTrace, ""), \
F(packages_list_has_parse_errors, kSingle, kError, kTrace, ""), \
F(packages_list_has_read_errors, kSingle, kError, kTrace, ""), \
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 25087d5..2e019f3 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -59,12 +59,13 @@
context_.flow_tracker.reset(new FlowTracker(&context_));
context_.event_tracker.reset(new EventTracker(&context_));
context_.process_tracker.reset(new ProcessTracker(&context_));
- context_.clock_tracker.reset(new ClockTracker(&context_));
+ context_.clock_tracker.reset(new ClockTracker(context_.storage.get()));
context_.heap_profile_tracker.reset(new HeapProfileTracker(&context_));
context_.perf_sample_tracker.reset(new PerfSampleTracker(&context_));
context_.global_stack_profile_tracker.reset(new GlobalStackProfileTracker());
- context_.metadata_tracker.reset(new MetadataTracker(&context_));
- context_.global_args_tracker.reset(new GlobalArgsTracker(&context_));
+ context_.metadata_tracker.reset(new MetadataTracker(context_.storage.get()));
+ context_.global_args_tracker.reset(
+ new GlobalArgsTracker(context_.storage.get()));
{
context_.descriptor_pool_.reset(new DescriptorPool());
auto status = context_.descriptor_pool_->AddFromFileDescriptorSet(
diff --git a/src/trace_processor/util/BUILD.gn b/src/trace_processor/util/BUILD.gn
index a7e7341..79f04fb 100644
--- a/src/trace_processor/util/BUILD.gn
+++ b/src/trace_processor/util/BUILD.gn
@@ -45,6 +45,17 @@
}
}
+source_set("stack_traces_util") {
+ sources = [
+ "stack_traces_util.cc",
+ "stack_traces_util.h",
+ ]
+ deps = [
+ "../../../gn:default_deps",
+ "../../../include/perfetto/ext/base:base",
+ ]
+}
+
source_set("protozero_to_text") {
sources = [
"protozero_to_text.cc",
diff --git a/src/trace_processor/util/stack_traces_util.cc b/src/trace_processor/util/stack_traces_util.cc
new file mode 100644
index 0000000..a255560
--- /dev/null
+++ b/src/trace_processor/util/stack_traces_util.cc
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "src/trace_processor/util/stack_traces_util.h"
+#include "perfetto/ext/base/string_view.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace util {
+
+bool IsHexModuleId(base::StringView module) {
+ return module.size() == 33;
+}
+
+} // namespace util
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/util/stack_traces_util.h b/src/trace_processor/util/stack_traces_util.h
new file mode 100644
index 0000000..1171786
--- /dev/null
+++ b/src/trace_processor/util/stack_traces_util.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_UTIL_STACK_TRACES_UTIL_H_
+#define SRC_TRACE_PROCESSOR_UTIL_STACK_TRACES_UTIL_H_
+
+#include "perfetto/ext/base/string_view.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace util {
+
+// Returns whether this string is of a hex chrome module or not to decide
+// whether to convert the module to/from hex.
+// TODO(b/148109467): Remove workaround once all active Chrome versions
+// write raw bytes instead of a string as build_id.
+bool IsHexModuleId(base::StringView module);
+
+} // namespace util
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_UTIL_STACK_TRACES_UTIL_H_
diff --git a/src/tracebox/BUILD.gn b/src/tracebox/BUILD.gn
index 9000732..aeed465 100644
--- a/src/tracebox/BUILD.gn
+++ b/src/tracebox/BUILD.gn
@@ -26,5 +26,8 @@
"../traced/service",
"../websocket_bridge:lib",
]
+ if(enable_perfetto_traced_perf) {
+ deps += ["../profiling/perf:traced_perf_main"]
+ }
sources = [ "tracebox.cc" ]
}
diff --git a/src/tracebox/tracebox.cc b/src/tracebox/tracebox.cc
index a421b97..359b370 100644
--- a/src/tracebox/tracebox.cc
+++ b/src/tracebox/tracebox.cc
@@ -22,6 +22,10 @@
#include "src/perfetto_cmd/perfetto_cmd.h"
#include "src/websocket_bridge/websocket_bridge.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_TRACED_PERF)
+#include "src/profiling/perf/traced_perf.h"
+#endif
+
#include <stdio.h>
#include <tuple>
@@ -38,6 +42,9 @@
const Applet g_applets[]{
{"traced", ServiceMain},
{"traced_probes", ProbesMain},
+#if PERFETTO_BUILDFLAG(PERFETTO_TRACED_PERF)
+ {"traced_perf", TracedPerfMain},
+#endif
{"perfetto", PerfettoCmdMain},
{"trigger_perfetto", TriggerPerfettoMain},
{"websocket_bridge", WebsocketBridgeMain},
@@ -182,7 +189,28 @@
&traced_probes_notify_msg);
if (traced_probes_notify_msg != "1")
PERFETTO_FATAL(
- "The traced_proces service failed unexpectedly. Check the logs");
+ "The traced_probes service failed unexpectedly. Check the logs");
+#endif
+
+#if PERFETTO_BUILDFLAG(PERFETTO_TRACED_PERF)
+ base::Subprocess traced_perf({self_path, "traced_perf"});
+ // Put traced_perf in the same process group as traced. Same reason (CTRL+C)
+ // but it's not worth creating a new group.
+ traced_perf.args.posix_proc_group_id = traced.pid();
+
+ base::Pipe traced_perf_sync_pipe = base::Pipe::Create();
+ int traced_perf_fd = *traced_perf_sync_pipe.wr;
+ base::SetEnv("TRACED_PERF_NOTIFY_FD", std::to_string(traced_perf_fd));
+ traced_perf.args.preserve_fds.emplace_back(traced_perf_fd);
+ traced_perf.Start();
+ traced_perf_sync_pipe.wr.reset();
+
+ std::string traced_perf_notify_msg;
+ base::ReadPlatformHandle(*traced_perf_sync_pipe.rd,
+ &traced_perf_notify_msg);
+ if (traced_perf_notify_msg != "1")
+ PERFETTO_FATAL(
+ "The traced_perf service failed unexpectedly. Check the logs");
#endif
perfetto_cmd.ConnectToServiceRunAndMaybeNotify();
diff --git a/src/traced/service/service.cc b/src/traced/service/service.cc
index 215392a..72f19ac 100644
--- a/src/traced/service/service.cc
+++ b/src/traced/service/service.cc
@@ -225,9 +225,12 @@
PERFETTO_CHECK(base::CloseFile(notif_fd) == 0);
}
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) && \
+ PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
// Notify init (perfetto.rc) that traced has been started. Used only by
// the perfetto_trace_on_boot init service.
+ // This property can be set only in in-tree builds. shell.te doesn't have
+ // SELinux permissions to set sys.trace.* properties.
if (__system_property_set("sys.trace.traced_started", "1") != 0) {
PERFETTO_PLOG("Failed to set property sys.trace.traced_started");
}
diff --git a/src/tracing/internal/tracing_muxer_impl.cc b/src/tracing/internal/tracing_muxer_impl.cc
index 41da7e6..1d6e2ca 100644
--- a/src/tracing/internal/tracing_muxer_impl.cc
+++ b/src/tracing/internal/tracing_muxer_impl.cc
@@ -2046,7 +2046,7 @@
return;
}
- PERFETTO_DLOG("Reconnecting backend %ld for startup tracing",
+ PERFETTO_DLOG("Reconnecting backend %zu for startup tracing",
backend_id);
backend.producer_conn_args.use_producer_provided_smb = true;
backend.producer->service_->Disconnect(); // Causes a reconnect.
diff --git a/test/data/chrome_stack_traces_symbolized_trace.pftrace.sha256 b/test/data/chrome_stack_traces_symbolized_trace.pftrace.sha256
new file mode 100644
index 0000000..76156d4
--- /dev/null
+++ b/test/data/chrome_stack_traces_symbolized_trace.pftrace.sha256
@@ -0,0 +1 @@
+a3cb32a2faaef29b0b16c0f8242073beffdc6d0454e7a07a5e5466349f3c3125
\ No newline at end of file
diff --git a/test/data/fling_with_input_delay.pftrace.sha256 b/test/data/fling_with_input_delay.pftrace.sha256
new file mode 100644
index 0000000..c8ea1a2
--- /dev/null
+++ b/test/data/fling_with_input_delay.pftrace.sha256
@@ -0,0 +1 @@
+8c968b20e71481475a429399b4366bd796c527293218fe80789f9ed6ab9db5b4
\ No newline at end of file
diff --git a/test/trace_processor/android/android_system_property.textproto b/test/trace_processor/android/android_system_property.textproto
new file mode 100644
index 0000000..ffcb98d
--- /dev/null
+++ b/test/trace_processor/android/android_system_property.textproto
@@ -0,0 +1,32 @@
+packet {
+ timestamp: 1000
+ android_system_property {
+ values {
+ name: "debug.tracing.screen_state"
+ value: "2"
+ }
+ values {
+ name: "debug.tracing.device_state"
+ value: "some_state_from_sysprops"
+ }
+ }
+}
+packet {
+ ftrace_events {
+ cpu: 1
+ event {
+ timestamp: 2000
+ pid: 1
+ print {
+ buf: "C|1000|ScreenState|1\n"
+ }
+ }
+ event {
+ timestamp: 3000
+ pid: 1
+ print {
+ buf: "N|1000|DeviceStateChanged|some_state_from_atrace\n"
+ }
+ }
+ }
+}
diff --git a/test/trace_processor/android/android_system_property_counter.out b/test/trace_processor/android/android_system_property_counter.out
new file mode 100644
index 0000000..9a52c9f
--- /dev/null
+++ b/test/trace_processor/android/android_system_property_counter.out
@@ -0,0 +1,3 @@
+"id","type","name","id","ts","type","value"
+0,"counter_track","ScreenState",0,1000,"counter",2.000000
+0,"counter_track","ScreenState",1,2000,"counter",1.000000
diff --git a/test/trace_processor/android/android_system_property_counter_test.sql b/test/trace_processor/android/android_system_property_counter_test.sql
new file mode 100644
index 0000000..580e4a9
--- /dev/null
+++ b/test/trace_processor/android/android_system_property_counter_test.sql
@@ -0,0 +1,3 @@
+select t.id, t.type, t.name, c.id, c.ts, c.type, c.value
+from counter_track t join counter c on t.id = c.track_id
+where name = 'ScreenState';
diff --git a/test/trace_processor/android/android_system_property_slice.out b/test/trace_processor/android/android_system_property_slice.out
new file mode 100644
index 0000000..8fe8d07
--- /dev/null
+++ b/test/trace_processor/android/android_system_property_slice.out
@@ -0,0 +1,3 @@
+"id","type","name","id","ts","dur","type","name"
+1,"track","DeviceStateChanged",0,1000,0,"internal_slice","some_state_from_sysprops"
+1,"track","DeviceStateChanged",1,3000,0,"internal_slice","some_state_from_atrace"
diff --git a/test/trace_processor/android/android_system_property_slice_test.sql b/test/trace_processor/android/android_system_property_slice_test.sql
new file mode 100644
index 0000000..96e0fcf
--- /dev/null
+++ b/test/trace_processor/android/android_system_property_slice_test.sql
@@ -0,0 +1,3 @@
+select t.id, t.type, t.name, s.id, s.ts, s.dur, s.type, s.name
+from track t join slice s on s.track_id = t.id
+where t.name = 'DeviceStateChanged';
diff --git a/test/trace_processor/android/index b/test/trace_processor/android/index
index ce248b1..d6be769 100644
--- a/test/trace_processor/android/index
+++ b/test/trace_processor/android/index
@@ -1,2 +1,5 @@
# Ensure Android game intervntion list are parsed correctly
game_intervention_list_test.textproto game_intervention_list_test.sql game_intervention_list_test.out
+
+android_system_property.textproto android_system_property_counter_test.sql android_system_property_counter.out
+android_system_property.textproto android_system_property_slice_test.sql android_system_property_slice.out
diff --git a/test/trace_processor/chrome/chrome_scroll_jank_caused_by_scheduling_test.out b/test/trace_processor/chrome/chrome_scroll_jank_caused_by_scheduling_test.out
new file mode 100644
index 0000000..5b78dca
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_scroll_jank_caused_by_scheduling_test.out
@@ -0,0 +1,3 @@
+
+"full_name","total_duration_ms","total_thread_duration_ms","count","window_start_ts","window_end_ts"
+"RunTask(posted_from=cc/scheduler/scheduler.cc:PostPendingBeginFrameTask),RunTask(posted_from=cc/scheduler/scheduler.cc:ScheduleBeginImplFrameDeadline),SingleThreadProxy::BeginMainFrame(java_views=ToolbarLayout),blink.mojom.WidgetInputHandler reply (hash=3392143105),cc.mojom.RenderFrameMetadataObserverClient message (hash=330497194),viz.mojom.CompositorFrameSinkClient message (hash=3114070324),viz.mojom.CompositorFrameSinkClient message (hash=50871626),viz.mojom.FrameSinkManagerClient message (hash=532012934)",7.568000,6.745000,11,666960999011,666972176011
diff --git a/test/trace_processor/chrome/chrome_scroll_jank_caused_by_scheduling_test.sql b/test/trace_processor/chrome/chrome_scroll_jank_caused_by_scheduling_test.sql
new file mode 100644
index 0000000..4caea94
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_scroll_jank_caused_by_scheduling_test.sql
@@ -0,0 +1,27 @@
+--
+-- Copyright 2022 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
+--
+-- https://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.
+
+SELECT RUN_METRIC('chrome/chrome_scroll_jank_caused_by_scheduling.sql',
+'dur_causes_jank_ms',
+/* dur_causes_jank_ms = */ '5') AS suppress_query_output;
+
+SELECT
+ full_name,
+ total_duration_ms,
+ total_thread_duration_ms,
+ count,
+ window_start_ts,
+ window_end_ts
+FROM chrome_scroll_jank_caused_by_scheduling;
\ No newline at end of file
diff --git a/test/trace_processor/chrome/chrome_stack_samples_for_task_test.out b/test/trace_processor/chrome/chrome_stack_samples_for_task_test.out
new file mode 100644
index 0000000..6e63af3
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_stack_samples_for_task_test.out
@@ -0,0 +1,11 @@
+
+"description","ts","depth"
+"Scanned",696373965111470,0
+"tracing::TraceEventDataSource::OnAddLegacyTraceEvent(base::trace_event::TraceEvent*, bool, base::trace_event::TraceEventHandle*)",696373965111470,1
+"base::trace_event::TraceLog::AddTraceEventWithThreadIdAndTimestamps(char, unsigned char const*, char const*, char const*, unsigned long long, unsigned long long, int, base::TimeTicks const&, base::ThreadTicks const&, base::trace_event::TraceArguments*, unsigned int)",696373965111470,2
+"base::subtle::TimeTicksNowIgnoringOverride()",696373965111470,3
+"base::tracing::AutoThreadLocalBoolean::~AutoThreadLocalBoolean()",696373965111470,4
+"tracing::TraceEventDataSource::OnUpdateDuration(unsigned char const*, char const*, base::trace_event::TraceEventHandle, int, bool, base::TimeTicks const&, base::ThreadTicks const&, base::trace_event::ThreadInstructionCount)",696373965111470,5
+"content::ServiceWorkerRegistry::StoreUserData(long long, blink::StorageKey const&, std::__1::vector<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > const&, base::OnceCallback<void (blink::ServiceWorkerStatusCode)>)",696373965111470,6
+"base::trace_event::TraceLog::UpdateTraceEventDuration(unsigned char const*, char const*, base::trace_event::TraceEventHandle)",696373965111470,7
+"XML_ParseBuffer",696373965111470,8
diff --git a/test/trace_processor/chrome/chrome_stack_samples_for_task_test.sql b/test/trace_processor/chrome/chrome_stack_samples_for_task_test.sql
new file mode 100644
index 0000000..3b33d47
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_stack_samples_for_task_test.sql
@@ -0,0 +1,34 @@
+--
+-- Copyright 2022 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
+--
+-- https://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.
+
+SELECT RUN_METRIC('chrome/chrome_stack_samples_for_task.sql',
+ 'target_duration_ms', '0.000001',
+ 'thread_name', '"CrBrowserMain"',
+ 'task_name', '"sendTouchEvent"') AS suppress_query_output;
+
+SELECT
+ sample.description,
+ sample.ts,
+ sample.depth
+FROM chrome_stack_samples_for_task sample
+JOIN (
+ SELECT
+ ts,
+ dur
+ FROM slice
+ WHERE ts = 696373965001470
+) test_slice
+ ON sample.ts >= test_slice.ts
+ AND sample.ts <= test_slice.ts + test_slice.dur;
\ No newline at end of file
diff --git a/test/trace_processor/chrome/chrome_tasks_delaying_input_processing_test.out b/test/trace_processor/chrome/chrome_tasks_delaying_input_processing_test.out
new file mode 100644
index 0000000..4707368
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_tasks_delaying_input_processing_test.out
@@ -0,0 +1,5 @@
+
+"full_name","duration_ms","thread_dur_ms"
+"content.mojom.FrameHost message (hash=2168461044)",16.111000,10.451000
+"Looper.dispatch: android.net.ConnectivityManager$CallbackHandler(null)",10.507000,0.878000
+"blink.mojom.PresentationService message (hash=3202951471)",22.524000,9.992000
diff --git a/test/trace_processor/chrome/chrome_tasks_delaying_input_processing_test.sql b/test/trace_processor/chrome/chrome_tasks_delaying_input_processing_test.sql
new file mode 100644
index 0000000..d0b2a19
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_tasks_delaying_input_processing_test.sql
@@ -0,0 +1,24 @@
+--
+-- Copyright 2022 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
+--
+-- https://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.
+
+SELECT RUN_METRIC('chrome/chrome_tasks_delaying_input_processing.sql',
+'duration_causing_jank_ms',
+ /* duration_causing_jank_ms = */ '8') AS suppress_query_output;
+
+SELECT
+ full_name,
+ duration_ms,
+ thread_dur_ms
+FROM chrome_tasks_delaying_input_processing;
\ No newline at end of file
diff --git a/test/trace_processor/chrome/index b/test/trace_processor/chrome/index
index e5ef48a..f311dac 100644
--- a/test/trace_processor/chrome/index
+++ b/test/trace_processor/chrome/index
@@ -15,6 +15,8 @@
../../data/chrome_scroll_without_vsync.pftrace scroll_jank_cause_queuing_delay_general_validation_test.sql scroll_jank_cause_queuing_delay_general_validation.out
../../data/chrome_scroll_without_vsync.pftrace chrome_thread_slice_test.sql chrome_thread_slice.out
../../data/scrolling_with_blocked_nonblocked_frames.pftrace chrome_input_to_browser_intervals_test.sql chrome_input_to_browser_intervals.out
+../../data/fling_with_input_delay.pftrace chrome_scroll_jank_caused_by_scheduling_test.sql chrome_scroll_jank_caused_by_scheduling_test.out
+../../data/fling_with_input_delay.pftrace chrome_tasks_delaying_input_processing_test.sql chrome_tasks_delaying_input_processing_test.out
../track_event/track_event_counters.textproto chrome_thread_slice_repeated_test.sql chrome_thread_slice_repeated.out
../../data/chrome_rendering_desktop.pftrace frame_times frame_times_metric.out
../../data/chrome_rendering_desktop.pftrace chrome_dropped_frames chrome_dropped_frames_metric.out
@@ -71,5 +73,8 @@
# Chrome tasks.
../../data/chrome_page_load_all_categories_not_extended.pftrace.gz chrome_tasks_test.sql chrome_tasks.out
+# Chrome stack samples.
+../../data/chrome_stack_traces_symbolized_trace.pftrace chrome_stack_samples_for_task_test.sql chrome_stack_samples_for_task_test.out
+
# Unsymbolized args.
unsymbolized_args.textproto chrome_unsymbolized_args unsymbolized_args.out
diff --git a/test/trace_processor/profiling/heap_profile_data_local_tmp.textproto b/test/trace_processor/profiling/heap_profile_data_local_tmp.textproto
new file mode 100644
index 0000000..990dcbb
--- /dev/null
+++ b/test/trace_processor/profiling/heap_profile_data_local_tmp.textproto
@@ -0,0 +1,171 @@
+packet {
+ clock_snapshot {
+ primary_trace_clock: BUILTIN_CLOCK_BOOTTIME
+ clocks {
+ clock_id: 6
+ timestamp: 62009635880088
+ }
+ clocks {
+ clock_id: 4
+ timestamp: 16144710445815
+ }
+ }
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+}
+packet {
+ interned_data {
+ build_ids {
+ iid: 0
+ str: ""
+ }
+ mapping_paths {
+ iid: 0
+ str: ""
+ }
+ function_names {
+ iid: 0
+ str: ""
+ }
+ }
+ sequence_flags: 1
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 3
+ trusted_pid: 14809
+}
+packet {
+ profile_packet {
+ index: 0
+ process_dumps {
+ heap_name: "libc.malloc"
+ sampling_interval_bytes: 1
+ orig_sampling_interval_bytes: 1
+ samples {
+ callstack_id: 5
+ self_allocated: 4096
+ self_freed: 0
+ alloc_count: 1
+ free_count: 0
+ }
+ samples {
+ callstack_id: 3
+ self_allocated: 4096
+ self_freed: 4096
+ alloc_count: 1
+ free_count: 1
+ }
+ }
+ }
+ interned_data {
+ mapping_paths {
+ iid: 3
+ str: "apex"
+ }
+ mapping_paths {
+ iid: 4
+ str: "com.android.runtime"
+ }
+ mapping_paths {
+ iid: 5
+ str: "lib64"
+ }
+ mapping_paths {
+ iid: 6
+ str: "bionic"
+ }
+ mapping_paths {
+ iid: 7
+ str: "libc.so"
+ }
+ build_ids {
+ iid: 2
+ str: "\335\234\322\001?\331\324E\033\037\217R\260\320\336\355"
+ }
+ mappings {
+ iid: 2
+ exact_offset: 249856
+ start_offset: 0
+ start: 488590266368
+ end: 488590811136
+ load_bias: 0
+ build_id: 2
+ path_string_ids: 3
+ path_string_ids: 4
+ path_string_ids: 5
+ path_string_ids: 6
+ path_string_ids: 7
+ }
+ function_names {
+ iid: 8
+ str: "malloc"
+ }
+ frames {
+ iid: 2
+ function_name_id: 8
+ mapping_id: 2
+ rel_pc: 254632
+ }
+ mapping_paths {
+ iid: 9
+ str: "data"
+ }
+ mapping_paths {
+ iid: 10
+ str: "local"
+ }
+ mapping_paths {
+ iid: 11
+ str: "tmp"
+ }
+ mapping_paths {
+ iid: 12
+ str: "mallocer"
+ }
+ build_ids {
+ iid: 1
+ str: "" # Build id for /data/local/tmp/mallocer is empty!
+ }
+ mappings {
+ iid: 3
+ exact_offset: 4096
+ start_offset: 0
+ start: 418850668544
+ end: 418850672640
+ load_bias: 0
+ build_id: 1
+ path_string_ids: 9
+ path_string_ids: 10
+ path_string_ids: 11
+ path_string_ids: 12
+ }
+ function_names {
+ iid: 1
+ str: ""
+ }
+ frames {
+ iid: 4
+ function_name_id: 1
+ mapping_id: 3
+ rel_pc: 4476
+ }
+ callstacks {
+ iid: 5
+ frame_ids: 4
+ frame_ids: 2
+ }
+ frames {
+ iid: 3
+ function_name_id: 1
+ mapping_id: 3
+ rel_pc: 4440
+ }
+ callstacks {
+ iid: 3
+ frame_ids: 3
+ frame_ids: 2
+ }
+ }
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 3
+ trusted_pid: 14809
+}
diff --git a/test/trace_processor/profiling/index b/test/trace_processor/profiling/index
index c07214f..733a29c 100644
--- a/test/trace_processor/profiling/index
+++ b/test/trace_processor/profiling/index
@@ -60,3 +60,5 @@
../../data/heapprofd_standalone_client_example-trace stack_profile_symbols_test.sql stack_profile_symbols.out
../../data/callstack_sampling.pftrace callstack_sampling_flamegraph_test.sql callstack_sampling_flamegraph.out
../../data/callstack_sampling.pftrace callstack_sampling_flamegraph_multi_process_test.sql callstack_sampling_flamegraph_multi_process.out
+
+heap_profile_data_local_tmp.textproto no_build_id.sql no_build_id.out
diff --git a/test/trace_processor/profiling/no_build_id.out b/test/trace_processor/profiling/no_build_id.out
new file mode 100644
index 0000000..ba2fd3f
--- /dev/null
+++ b/test/trace_processor/profiling/no_build_id.out
@@ -0,0 +1,2 @@
+"value"
+1
diff --git a/test/trace_processor/profiling/no_build_id.sql b/test/trace_processor/profiling/no_build_id.sql
new file mode 100644
index 0000000..46514db
--- /dev/null
+++ b/test/trace_processor/profiling/no_build_id.sql
@@ -0,0 +1 @@
+SELECT value FROM stats WHERE name = 'symbolization_tmp_build_id_not_found'
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index 8f55aa8..1ac2fe3 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -314,6 +314,10 @@
background: #c7d0db;
}
}
+
+ .nested-menu {
+ padding-right: 1em;
+ }
}
.track {
diff --git a/ui/src/common/protos.ts b/ui/src/common/protos.ts
index 9af9fb9..2245f6e 100644
--- a/ui/src/common/protos.ts
+++ b/ui/src/common/protos.ts
@@ -46,7 +46,7 @@
import IPCFrame = protos.perfetto.protos.IPCFrame;
import IMethodInfo =
protos.perfetto.protos.IPCFrame.BindServiceReply.IMethodInfo;
-import ITraceStats = protos.perfetto.protos.ITraceStats;
+import IBufferStats = protos.perfetto.protos.TraceStats.IBufferStats;
import ISlice = protos.perfetto.protos.ReadBuffersResponse.ISlice;
import EnableTracingRequest = protos.perfetto.protos.EnableTracingRequest;
import DisableTracingRequest = protos.perfetto.protos.DisableTracingRequest;
@@ -87,13 +87,13 @@
HeapprofdConfig,
IAndroidPowerConfig,
IBufferConfig,
+ IBufferStats,
IMethodInfo,
IPCFrame,
IProcessStatsConfig,
ISlice,
ISysStatsConfig,
ITraceConfig,
- ITraceStats,
JavaContinuousDumpConfig,
JavaHprofConfig,
MeminfoCounters,
diff --git a/ui/src/common/recordingV2/chrome_traced_tracing_session.ts b/ui/src/common/recordingV2/chrome_traced_tracing_session.ts
new file mode 100644
index 0000000..6128658
--- /dev/null
+++ b/ui/src/common/recordingV2/chrome_traced_tracing_session.ts
@@ -0,0 +1,228 @@
+// Copyright (C) 2022 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 {defer, Deferred} from '../../base/deferred';
+import {assertExists, assertTrue} from '../../base/logging';
+import {binaryDecode, binaryEncode} from '../../base/string_utils';
+import {
+ ChromeExtensionMessage,
+ isChromeExtensionError,
+ isChromeExtensionStatus,
+ isGetCategoriesResponse,
+} from '../../controller/chrome_proxy_record_controller';
+import {
+ isDisableTracingResponse,
+ isEnableTracingResponse,
+ isFreeBuffersResponse,
+ isGetTraceStatsResponse,
+ isReadBuffersResponse,
+} from '../../controller/consumer_port_types';
+import {
+ EnableTracingRequest,
+ IBufferStats,
+ ISlice,
+ TraceConfig,
+} from '../protos';
+
+import {
+ BUFFER_USAGE_INCORRECT_FORMAT,
+ BUFFER_USAGE_NOT_ACCESSIBLE,
+ EXTENSION_ID,
+ MALFORMED_EXTENSION_MESSAGE,
+} from './chrome_utils';
+import {RecordingError} from './recording_error_handling';
+import {
+ TracingSession,
+ TracingSessionListener,
+} from './recording_interfaces_v2';
+
+// This class implements the protocol described in
+// https://perfetto.dev/docs/design-docs/api-and-abi#tracing-protocol-abi
+// However, with the Chrome extension we communicate using JSON messages.
+export class ChromeTracedTracingSession implements TracingSession {
+ // Needed for ReadBufferResponse: all the trace packets are split into
+ // several slices. |partialPacket| is the buffer for them. Once we receive a
+ // slice with the flag |lastSliceForPacket|, a new packet is created.
+ private partialPacket: ISlice[] = [];
+
+ // For concurrent calls to 'GetCategories', we return the same value.
+ private pendingGetCategoriesMessage?: Deferred<string[]>;
+
+ private pendingStatsMessages = new Array<Deferred<IBufferStats[]>>();
+
+ // Port through which we communicate with the extension.
+ private chromePort: chrome.runtime.Port;
+ // True when Perfetto is connected via the port to the tracing session.
+ private isPortConnected: boolean;
+
+ constructor(private tracingSessionListener: TracingSessionListener) {
+ this.chromePort = chrome.runtime.connect(EXTENSION_ID);
+ this.isPortConnected = true;
+ }
+
+ start(config: TraceConfig): void {
+ if (!this.isPortConnected) return;
+ const duration = config.durationMs;
+ this.tracingSessionListener.onStatus(`Recording in progress${
+ duration ? ' for ' + duration.toString() + ' ms' : ''}...`);
+
+ const enableTracingRequest = new EnableTracingRequest();
+ enableTracingRequest.traceConfig = config;
+ const enableTracingRequestProto = binaryEncode(
+ EnableTracingRequest.encode(enableTracingRequest).finish());
+ this.chromePort.postMessage(
+ {method: 'EnableTracing', requestData: enableTracingRequestProto});
+ }
+
+ // The 'cancel' method will end the tracing session and will NOT return the
+ // trace. Therefore, we do not need to keep the connection open.
+ cancel(): void {
+ if (!this.isPortConnected) return;
+ this.terminateConnection();
+ }
+
+ // The 'stop' method will end the tracing session and cause the trace to be
+ // returned via a callback. We maintain the connection to the target so we can
+ // extract the trace.
+ // See 'DisableTracing' in:
+ // https://perfetto.dev/docs/design-docs/life-of-a-tracing-session
+ stop(): void {
+ if (!this.isPortConnected) return;
+ this.chromePort.postMessage({method: 'DisableTracing'});
+ }
+
+ getCategories(): Promise<string[]> {
+ if (!this.isPortConnected) {
+ throw new RecordingError(
+ 'Attempting to get categories from a ' +
+ 'disconnected tracing session.');
+ }
+ if (this.pendingGetCategoriesMessage) {
+ return this.pendingGetCategoriesMessage;
+ }
+
+ this.chromePort.postMessage({method: 'GetCategories'});
+ return this.pendingGetCategoriesMessage = defer<string[]>();
+ }
+
+ async getTraceBufferUsage(): Promise<number> {
+ if (!this.isPortConnected) return 0;
+ const bufferStats = await this.getBufferStats();
+ let percentageUsed = -1;
+ for (const buffer of bufferStats) {
+ const used = assertExists(buffer.bytesWritten);
+ const total = assertExists(buffer.bufferSize);
+ if (total >= 0) {
+ percentageUsed = Math.max(percentageUsed, used / total);
+ }
+ }
+
+ if (percentageUsed === -1) {
+ throw new RecordingError(BUFFER_USAGE_INCORRECT_FORMAT);
+ }
+ return percentageUsed;
+ }
+
+ initConnection(): void {
+ this.chromePort.onMessage.addListener((message: ChromeExtensionMessage) => {
+ this.handleExtensionMessage(message);
+ });
+ }
+
+ private getBufferStats(): Promise<IBufferStats[]> {
+ this.chromePort.postMessage({method: 'GetTraceStats'});
+
+ const statsMessage = defer<IBufferStats[]>();
+ this.pendingStatsMessages.push(statsMessage);
+ return statsMessage;
+ }
+
+ private terminateConnection(): void {
+ this.chromePort.postMessage({method: 'FreeBuffers'});
+ this.clearState();
+ }
+
+ private clearState() {
+ this.chromePort.disconnect();
+ this.isPortConnected = false;
+ for (const statsMessage of this.pendingStatsMessages) {
+ statsMessage.reject(new RecordingError(BUFFER_USAGE_NOT_ACCESSIBLE));
+ }
+ this.pendingStatsMessages = [];
+ this.pendingGetCategoriesMessage = undefined;
+ }
+
+ private handleExtensionMessage(message: ChromeExtensionMessage) {
+ if (isChromeExtensionError(message)) {
+ this.terminateConnection();
+ this.tracingSessionListener.onError(message.error);
+ } else if (isChromeExtensionStatus(message)) {
+ this.tracingSessionListener.onStatus(message.status);
+ } else if (isReadBuffersResponse(message)) {
+ if (!message.slices) {
+ return;
+ }
+ for (const messageSlice of message.slices) {
+ // The extension sends the binary data as a string.
+ // see http://shortn/_oPmO2GT6Vb
+ if (typeof messageSlice.data !== 'string') {
+ throw new RecordingError(MALFORMED_EXTENSION_MESSAGE);
+ }
+ const decodedSlice = {
+ data: binaryDecode(messageSlice.data),
+ };
+ this.partialPacket.push(decodedSlice);
+ if (messageSlice.lastSliceForPacket) {
+ let bufferSize = 0;
+ for (const slice of this.partialPacket) {
+ bufferSize += slice.data!.length;
+ }
+
+ const completeTrace = new Uint8Array(bufferSize);
+ let written = 0;
+ for (const slice of this.partialPacket) {
+ const data = slice.data!;
+ completeTrace.set(data, written);
+ written += data.length;
+ }
+ // The trace already comes encoded as a proto.
+ this.tracingSessionListener.onTraceData(completeTrace);
+ this.terminateConnection();
+ }
+ }
+ } else if (isGetCategoriesResponse(message)) {
+ assertExists(this.pendingGetCategoriesMessage)
+ .resolve(message.categories);
+ this.pendingGetCategoriesMessage = undefined;
+ } else if (isEnableTracingResponse(message)) {
+ // Once the service notifies us that a tracing session is enabled,
+ // we can start streaming the response using 'ReadBuffers'.
+ this.chromePort.postMessage({method: 'ReadBuffers'});
+ } else if (isGetTraceStatsResponse(message)) {
+ const maybePendingStatsMessage = this.pendingStatsMessages.shift();
+ if (maybePendingStatsMessage) {
+ maybePendingStatsMessage.resolve(
+ message?.traceStats?.bufferStats || []);
+ }
+ } else if (isFreeBuffersResponse(message)) {
+ // No action required. If we successfully read a whole trace,
+ // we close the connection. Alternatively, if the tracing finishes
+ // with an exception or if the user cancels it, we also close the
+ // connection.
+ } else {
+ assertTrue(isDisableTracingResponse(message));
+ // No action required. Same reasoning as for FreeBuffers.
+ }
+ }
+}
diff --git a/ui/src/common/recordingV2/chrome_utils.ts b/ui/src/common/recordingV2/chrome_utils.ts
new file mode 100644
index 0000000..2cc4b89
--- /dev/null
+++ b/ui/src/common/recordingV2/chrome_utils.ts
@@ -0,0 +1,26 @@
+// Copyright (C) 2022 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.
+
+export const EXTENSION_ID = 'lfmkphfpdbjijhpomgecfikhfohaoine';
+export const EXTENSION_URL =
+ `https://chrome.google.com/webstore/detail/perfetto-ui/${EXTENSION_ID}`;
+export const EXTENSION_NAME = 'Chrome extension';
+export const EXTENSION_NOT_INSTALLED =
+ `To trace Chrome from the Perfetto UI, you need to install our
+ ${EXTENSION_URL} and then reload this page.`;
+
+export const MALFORMED_EXTENSION_MESSAGE = 'Malformed extension message.';
+export const BUFFER_USAGE_NOT_ACCESSIBLE = 'Buffer usage not accessible';
+export const BUFFER_USAGE_INCORRECT_FORMAT =
+ 'The buffer usage data has am incorrect format';
diff --git a/ui/src/common/recordingV2/recording_error_handling.ts b/ui/src/common/recordingV2/recording_error_handling.ts
index d3b1b31..e1c00ba 100644
--- a/ui/src/common/recordingV2/recording_error_handling.ts
+++ b/ui/src/common/recordingV2/recording_error_handling.ts
@@ -15,6 +15,7 @@
import {
showAllowUSBDebugging,
showConnectionLostError,
+ showExtensionNotInstalled,
showIssueParsingTheTracedResponse,
showNoDeviceSelected,
showWebsocketConnectionIssue,
@@ -24,6 +25,7 @@
import {
WEBSOCKET_UNABLE_TO_CONNECT,
} from './adb_connection_over_websocket';
+import {EXTENSION_NOT_INSTALLED} from './chrome_utils';
import {OnMessageCallback} from './recording_interfaces_v2';
import {
PARSING_UNABLE_TO_DECODE_METHOD,
@@ -89,6 +91,8 @@
showNoDeviceSelected();
} else if (WEBSOCKET_UNABLE_TO_CONNECT === message) {
showWebsocketConnectionIssue(message);
+ } else if (message === EXTENSION_NOT_INSTALLED) {
+ showExtensionNotInstalled();
} else if (isParsingError(message)) {
showIssueParsingTheTracedResponse(message);
} else {
diff --git a/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts b/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts
new file mode 100644
index 0000000..e012bcf
--- /dev/null
+++ b/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts
@@ -0,0 +1,78 @@
+// Copyright (C) 2022 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 {EXTENSION_NOT_INSTALLED} from '../chrome_utils';
+import {RecordingError} from '../recording_error_handling';
+import {
+ OnTargetChangeCallback,
+ RecordingTargetV2,
+ TargetFactory,
+} from '../recording_interfaces_v2';
+import {targetFactoryRegistry} from '../target_factory_registry';
+import {ChromeTarget} from '../targets/chrome_target';
+
+const CHROME_TARGET_FACTORY = 'ChromeTargetFactory';
+
+// Sample user agent for Chrome on Chrome OS:
+// "Mozilla/5.0 (X11; CrOS x86_64 14816.99.0) AppleWebKit/537.36
+// (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36"
+// This condition is wider, in the unlikely possibility of different casing,
+export function isCrOS(userAgent: string) {
+ return userAgent.toLowerCase().includes(' cros ');
+}
+
+export class ChromeTargetFactory implements TargetFactory {
+ readonly kind = CHROME_TARGET_FACTORY;
+ private targets: ChromeTarget[];
+
+ constructor() {
+ this.targets = [new ChromeTarget('Chrome', 'CHROME')];
+ if (isCrOS(navigator.userAgent)) {
+ this.targets.push(new ChromeTarget('Chrome', 'CHROME_OS'));
+ }
+ }
+
+ connectNewTarget(): Promise<RecordingTargetV2> {
+ throw new RecordingError(
+ 'Can not create a new Chrome target.' +
+ 'All Chrome targets are created at factory initialisation.');
+ }
+
+ getName(): string {
+ return 'Chrome';
+ }
+
+ listRecordingProblems(): string[] {
+ const recordingProblems = [];
+ if (!this.targets[0].getInfo().isExtensionInstalled) {
+ recordingProblems.push(EXTENSION_NOT_INSTALLED);
+ }
+ return recordingProblems;
+ }
+
+ listTargets(): RecordingTargetV2[] {
+ return this.targets;
+ }
+
+ setOnTargetChange(onTargetChange: OnTargetChangeCallback): void {
+ for (const target of this.targets) {
+ target.onTargetChange = onTargetChange;
+ }
+ }
+}
+
+// We only instantiate the factory if Perfetto UI is open in the Chrome browser.
+if (window.chrome && chrome.runtime) {
+ targetFactoryRegistry.register(new ChromeTargetFactory());
+}
diff --git a/ui/src/common/recordingV2/target_factories/chrome_target_factory_unittest.ts b/ui/src/common/recordingV2/target_factories/chrome_target_factory_unittest.ts
new file mode 100644
index 0000000..478a3f4
--- /dev/null
+++ b/ui/src/common/recordingV2/target_factories/chrome_target_factory_unittest.ts
@@ -0,0 +1,28 @@
+// Copyright (C) 2022 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 {isCrOS} from './chrome_target_factory';
+
+test('parse Chrome on Chrome OS user agent', () => {
+ const userAgent = 'Mozilla/5.0 (X11; CrOS x86_64 14816.99.0) ' +
+ 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 ' +
+ 'Safari/537.36';
+ expect(isCrOS(userAgent)).toBe(true);
+});
+
+test('parse Chrome on Mac user agent', () => {
+ const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
+ 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36';
+ expect(isCrOS(userAgent)).toBe(false);
+});
diff --git a/ui/src/common/recordingV2/target_factories/index.ts b/ui/src/common/recordingV2/target_factories/index.ts
index 17afaae..da8b328 100644
--- a/ui/src/common/recordingV2/target_factories/index.ts
+++ b/ui/src/common/recordingV2/target_factories/index.ts
@@ -14,3 +14,4 @@
import './android_webusb_target_factory';
import './android_websocket_target_factory';
+import './chrome_target_factory';
diff --git a/ui/src/common/recordingV2/targets/chrome_target.ts b/ui/src/common/recordingV2/targets/chrome_target.ts
new file mode 100644
index 0000000..39b13eb
--- /dev/null
+++ b/ui/src/common/recordingV2/targets/chrome_target.ts
@@ -0,0 +1,86 @@
+// Copyright (C) 2022 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 {ChromeTracedTracingSession} from '../chrome_traced_tracing_session';
+import {EXTENSION_ID} from '../chrome_utils';
+import {
+ ChromeTargetInfo,
+ OnTargetChangeCallback,
+ RecordingTargetV2,
+ TracingSession,
+ TracingSessionListener,
+} from '../recording_interfaces_v2';
+
+export class ChromeTarget implements RecordingTargetV2 {
+ onTargetChange?: OnTargetChangeCallback;
+ private chromeCategories?: string[];
+ // We only check the connection once at the beginning to:
+ // a) Avoid creating a 'Port' object every time 'getInfo' is called.
+ // b) When a new Port is created, the extension starts communicating with it
+ // and leaves aside the old Port objects, so creating a new Port would break
+ // any ongoing tracing session.
+ private isExtensionInstalled: boolean;
+
+ constructor(private name: string, private targetType: 'CHROME'|'CHROME_OS') {
+ const testPort = chrome.runtime.connect(EXTENSION_ID);
+ this.isExtensionInstalled = !!testPort;
+ testPort.disconnect();
+ }
+
+ getInfo(): ChromeTargetInfo {
+ return {
+ targetType: this.targetType,
+ name: this.name,
+ isExtensionInstalled: this.isExtensionInstalled,
+ dataSources:
+ [{name: 'chromeCategories', descriptor: this.chromeCategories}],
+ };
+ }
+
+ async createTracingSession(tracingSessionListener: TracingSessionListener):
+ Promise<TracingSession> {
+ const tracingSession =
+ new ChromeTracedTracingSession(tracingSessionListener);
+ tracingSession.initConnection();
+
+ if (!this.chromeCategories) {
+ // Fetch chrome categories from the extension.
+ this.chromeCategories = await tracingSession.getCategories();
+ if (this.onTargetChange) {
+ this.onTargetChange();
+ }
+ }
+
+ return tracingSession;
+ }
+
+ // Starts a tracing session in order to fetch chrome categories from the
+ // device. Then, it cancels the session.
+ async fetchTargetInfo(tracingSessionListener: TracingSessionListener):
+ Promise<void> {
+ const tracingSession =
+ await this.createTracingSession(tracingSessionListener);
+ tracingSession.cancel();
+ }
+
+ disconnect(_disconnectMessage?: string): Promise<void> {
+ return Promise.resolve(undefined);
+ }
+
+ // We can connect to the Chrome target without taking the connection away
+ // from another process.
+ async canConnectWithoutContention(): Promise<boolean> {
+ return true;
+ }
+}
diff --git a/ui/src/common/recordingV2/traced_tracing_session.ts b/ui/src/common/recordingV2/traced_tracing_session.ts
index e110ddb..2257f85 100644
--- a/ui/src/common/recordingV2/traced_tracing_session.ts
+++ b/ui/src/common/recordingV2/traced_tracing_session.ts
@@ -25,15 +25,19 @@
FreeBuffersResponse,
GetTraceStatsRequest,
GetTraceStatsResponse,
+ IBufferStats,
IMethodInfo,
IPCFrame,
ISlice,
- ITraceStats,
ReadBuffersRequest,
ReadBuffersResponse,
TraceConfig,
} from '../protos';
+import {
+ BUFFER_USAGE_INCORRECT_FORMAT,
+ BUFFER_USAGE_NOT_ACCESSIBLE,
+} from './chrome_utils';
import {RecordingError} from './recording_error_handling';
import {
ByteStream,
@@ -87,7 +91,7 @@
// to keep track of the type of request, and parse the response correctly.
private requestId = 1;
- private pendingStatsMessages = new Array<Deferred<ITraceStats>>();
+ private pendingStatsMessages = new Array<Deferred<IBufferStats[]>>();
// The bytestream is obtained when creating a connection with a target.
// For instance, the AdbStream is obtained from a connection with an Adb
@@ -125,15 +129,9 @@
if (!this.byteStream.isOpen()) {
return 0;
}
- const traceStats = await this.getTraceStats();
- if (!traceStats.bufferStats) {
- // // If a buffer stats is pending and we finish tracing, it will be
- // resolved as {} when closing the connection. In that case just ignore it
- // rather than erroring.
- return 0;
- }
- let percentageUsed = 0;
- for (const buffer of assertExists(traceStats.bufferStats)) {
+ const bufferStats = await this.getBufferStats();
+ let percentageUsed = -1;
+ for (const buffer of bufferStats) {
if (!Number.isFinite(buffer.bytesWritten) ||
!Number.isFinite(buffer.bufferSize)) {
continue;
@@ -144,6 +142,10 @@
percentageUsed = Math.max(percentageUsed, used / total);
}
}
+
+ if (percentageUsed === -1) {
+ return Promise.reject(new RecordingError(BUFFER_USAGE_INCORRECT_FORMAT));
+ }
return percentageUsed;
}
@@ -163,7 +165,7 @@
return this.resolveBindingPromise;
}
- private getTraceStats(): Promise<ITraceStats> {
+ private getBufferStats(): Promise<IBufferStats[]> {
const getTraceStatsRequestProto =
GetTraceStatsRequest.encode(new GetTraceStatsRequest()).finish();
try {
@@ -173,7 +175,7 @@
this.raiseError(e);
}
- const statsMessage = defer<ITraceStats>();
+ const statsMessage = defer<IBufferStats[]>();
this.pendingStatsMessages.push(statsMessage);
return statsMessage;
}
@@ -188,12 +190,7 @@
private clearState() {
for (const statsMessage of this.pendingStatsMessages) {
- // Resolving with an empty object instead of rejecting because a rejection
- // would trigger an 'unhandledRejection' event, causing the application
- // to show an error modal in the UI, which is not necessary for stats
- // messages because that would mean the error modal is shown on every
- // successful recording.
- statsMessage.resolve({});
+ statsMessage.reject(new RecordingError(BUFFER_USAGE_NOT_ACCESSIBLE));
}
this.pendingStatsMessages = [];
}
@@ -345,7 +342,7 @@
} else if (method === 'GetTraceStats') {
const maybePendingStatsMessage = this.pendingStatsMessages.shift();
if (maybePendingStatsMessage) {
- maybePendingStatsMessage.resolve(data.traceStats || {});
+ maybePendingStatsMessage.resolve(data?.traceStats?.bufferStats || []);
}
} else if (method === 'FreeBuffers') {
// No action required. If we successfully read a whole trace,
diff --git a/ui/src/controller/chrome_proxy_record_controller.ts b/ui/src/controller/chrome_proxy_record_controller.ts
index 2c6571e..1444478 100644
--- a/ui/src/controller/chrome_proxy_record_controller.ts
+++ b/ui/src/controller/chrome_proxy_record_controller.ts
@@ -38,11 +38,13 @@
export type ChromeExtensionMessage = ChromeExtensionError|ChromeExtensionStatus|
ConsumerPortResponse|GetCategoriesResponse;
-function isError(obj: Typed): obj is ChromeExtensionError {
+export function isChromeExtensionError(obj: Typed):
+ obj is ChromeExtensionError {
return obj.type === 'ChromeExtensionError';
}
-function isStatus(obj: Typed): obj is ChromeExtensionStatus {
+export function isChromeExtensionStatus(obj: Typed):
+ obj is ChromeExtensionStatus {
return obj.type === 'ChromeExtensionStatus';
}
@@ -80,11 +82,11 @@
}
onExtensionMessage(message: {data: ChromeExtensionMessage}) {
- if (isError(message.data)) {
+ if (isChromeExtensionError(message.data)) {
this.sendErrorMessage(message.data.error);
return;
}
- if (isStatus(message.data)) {
+ if (isChromeExtensionStatus(message.data)) {
this.sendStatus(message.data.status);
return;
}
diff --git a/ui/src/frontend/chrome_slice_panel.ts b/ui/src/frontend/chrome_slice_panel.ts
index 387e68b..5273098 100644
--- a/ui/src/frontend/chrome_slice_panel.ts
+++ b/ui/src/frontend/chrome_slice_panel.ts
@@ -339,12 +339,14 @@
const fullKey = argument.full_key;
return [
{
+ itemType: 'regular',
text: 'Copy full key',
callback: () => {
navigator.clipboard.writeText(fullKey);
},
},
{
+ itemType: 'regular',
text: 'Find slices with the same arg value',
callback: () => {
globals.dispatch(Actions.executeQuery({
@@ -360,6 +362,7 @@
},
},
{
+ itemType: 'regular',
text: 'Visualise argument values',
callback: () => {
globals.dispatch(Actions.addVisualisedArg({argName: fullKey}));
diff --git a/ui/src/frontend/error_dialog.ts b/ui/src/frontend/error_dialog.ts
index b2e20af..b3813df 100644
--- a/ui/src/frontend/error_dialog.ts
+++ b/ui/src/frontend/error_dialog.ts
@@ -16,6 +16,7 @@
import {assertExists} from '../base/logging';
import {RECORDING_V2_FLAG} from '../common/feature_flags';
+import {EXTENSION_URL} from '../common/recordingV2/chrome_utils';
import {TraceUrlSource} from '../common/state';
import {saveTrace} from '../common/upload_utils';
@@ -333,6 +334,20 @@
});
}
+export function showExtensionNotInstalled(): void {
+ showModal({
+ title: 'Perfetto Chrome extension not installed',
+ content:
+ m('div',
+ m('.note',
+ `To trace Chrome from the Perfetto UI, you need to install our `,
+ m('a', {href: EXTENSION_URL, target: '_blank'}, 'Chrome extension'),
+ ' and then reload this page.'),
+ m('br')),
+ buttons: [],
+ });
+}
+
export function showWebsocketConnectionIssue(message: string): void {
showModal({
title: 'Unable to connect to the device via websocket',
diff --git a/ui/src/frontend/pivot_table_redux.ts b/ui/src/frontend/pivot_table_redux.ts
index b4ba46b..8d71712 100644
--- a/ui/src/frontend/pivot_table_redux.ts
+++ b/ui/src/frontend/pivot_table_redux.ts
@@ -17,7 +17,7 @@
import * as m from 'mithril';
import {sqliteString} from '../base/string_utils';
-import {Actions, DeferredAction} from '../common/actions';
+import {Actions} from '../common/actions';
import {COUNT_AGGREGATION} from '../common/empty_state';
import {ColumnType} from '../common/query_result';
import {
@@ -44,7 +44,6 @@
generateQuery,
QueryGeneratorError,
sliceAggregationColumns,
- Table,
tableColumnEquals,
tables,
threadSliceAggregationColumns,
@@ -52,7 +51,6 @@
import {
Aggregation,
AggregationFunction,
- aggregationKey,
columnKey,
PivotTree,
TableColumn,
@@ -65,49 +63,6 @@
nextKey: ColumnType;
}
-// Arguments to an action to toggle a table column in a particular part of
-// application's state.
-interface ColumnSetArgs<T> {
- column: T;
- selected: boolean;
-}
-
-interface ColumnSetCheckboxAttrs<T> {
- set: (args: ColumnSetArgs<T>) => DeferredAction<ColumnSetArgs<T>>;
- get: Map<string, T>;
- setKey: T;
-}
-
-abstract class GenericCheckbox<T> implements
- m.ClassComponent<ColumnSetCheckboxAttrs<T>> {
- abstract keyFn(value: T): string;
-
- view({attrs}: m.Vnode<ColumnSetCheckboxAttrs<T>>) {
- return m('input[type=checkbox]', {
- onclick: (e: InputEvent) => {
- const target = e.target as HTMLInputElement;
-
- globals.dispatch(
- attrs.set({column: attrs.setKey, selected: target.checked}));
- globals.rafScheduler.scheduleFullRedraw();
- },
- checked: attrs.get.has(this.keyFn(attrs.setKey)),
- });
- }
-}
-
-class ColumnSetCheckbox extends GenericCheckbox<TableColumn> {
- keyFn(value: TableColumn): string {
- return columnKey(value);
- }
-}
-
-class AggregationCheckbox extends GenericCheckbox<Aggregation> {
- keyFn(value: Aggregation): string {
- return aggregationKey(value);
- }
-}
-
interface PivotTableReduxAttrs {
selectionArea: PivotTableReduxAreaState;
}
@@ -167,22 +122,6 @@
this.constrainToArea);
}
- renderTablePivotColumns(t: Table) {
- return m(
- 'li',
- t.name,
- m('ul',
- t.columns.map(
- (col) =>
- m('li',
- m(ColumnSetCheckbox, {
- get: this.selectedPivotsMap,
- set: Actions.setPivotTablePivotSelected,
- setKey: {kind: 'regular', table: t.name, column: col},
- }),
- col))));
- }
-
renderResultsView(attrs: PivotTableReduxAttrs) {
return m(
'.pivot-table-redux',
@@ -338,11 +277,9 @@
}
sortingItem(column: TableColumn, order: SortDirection): PopupMenuItem {
- // Arrow contains unicode character for up or down arrow, according to the
- // direction of sorting.
- const arrow = order === 'DESC' ? '\u25BC' : '\u25B2';
return {
- text: `Sort ${arrow}`,
+ itemType: 'regular',
+ text: order === 'DESC' ? 'Highest first' : 'Lowest first',
callback() {
globals.dispatch(Actions.setPivotTableSortColumn({column, order}));
globals.dispatch(
@@ -359,6 +296,51 @@
readableColumnName(aggregation.column)})`;
}
+ aggregationPopupItem(aggregation: Aggregation, nameOverride?: string):
+ PopupMenuItem {
+ return {
+ itemType: 'regular',
+ text: nameOverride ?? readableColumnName(aggregation.column),
+ callback: () => {
+ globals.dispatch(Actions.setPivotTableAggregationSelected({
+ column: {
+ aggregationFunction: aggregation.aggregationFunction,
+ column: aggregation.column,
+ },
+ selected: true,
+ }));
+ globals.dispatch(
+ Actions.setPivotTableQueryRequested({queryRequested: true}));
+ },
+ };
+ }
+
+ aggregationPopupTableGroup(
+ table: string, columns: string[], used: Set<string>): PopupMenuItem
+ |undefined {
+ const items = [];
+ for (const column of columns) {
+ const tableColumn: TableColumn = {kind: 'regular', table, column};
+ if (used.has(columnKey(tableColumn))) {
+ continue;
+ }
+
+ items.push(this.aggregationPopupItem(
+ {aggregationFunction: 'SUM', column: tableColumn}));
+ }
+
+ if (items.length === 0) {
+ return undefined;
+ }
+
+ return {
+ itemType: 'group',
+ itemId: `aggregations-${table}`,
+ text: `Add ${table} aggregation`,
+ children: items,
+ };
+ }
+
renderAggregationHeaderCell(aggregation: Aggregation): m.Child {
const column = aggregation.column;
const popupItems: PopupMenuItem[] = [];
@@ -384,6 +366,7 @@
}
popupItems.push({
+ itemType: 'regular',
text: otherAgg,
callback() {
globals.dispatch(Actions.setPivotTableAggregationSelected(
@@ -400,6 +383,35 @@
}
}
+ const usedAggregations: Set<string> = new Set();
+ let hasCount = false;
+
+ for (const agg of state.selectedAggregations.values()) {
+ if (agg.aggregationFunction === 'COUNT') {
+ hasCount = true;
+ continue;
+ }
+
+ usedAggregations.add(columnKey(agg.column));
+ }
+
+ if (!hasCount) {
+ popupItems.push(this.aggregationPopupItem(
+ COUNT_AGGREGATION, 'Add count aggregation'));
+ }
+
+ const sliceAggregationsItem = this.aggregationPopupTableGroup(
+ 'slice', sliceAggregationColumns, usedAggregations);
+ if (sliceAggregationsItem !== undefined) {
+ popupItems.push(sliceAggregationsItem);
+ }
+
+ const threadSliceAggregationsItem = this.aggregationPopupTableGroup(
+ 'thread_slice', threadSliceAggregationColumns, usedAggregations);
+ if (threadSliceAggregationsItem !== undefined) {
+ popupItems.push(threadSliceAggregationsItem);
+ }
+
return m(
'td', this.readableAggregationName(aggregation), m(PopupMenuButton, {
icon,
@@ -459,7 +471,8 @@
const pivotTableHeaders = [];
for (const pivot of state.queryResult.metadata.pivotColumns) {
- const items = [{
+ const items: PopupMenuItem[] = [{
+ itemType: 'regular',
text: 'Add argument pivot',
callback: () => {
this.showModal = true;
@@ -469,6 +482,7 @@
}];
if (state.queryResult.metadata.pivotColumns.length > 1) {
items.push({
+ itemType: 'regular',
text: 'Remove',
callback() {
globals.dispatch(Actions.setPivotTablePivotSelected(
@@ -478,6 +492,38 @@
},
});
}
+
+ for (const table of tables) {
+ const group: PopupMenuItem[] = [];
+ for (const columnName of table.columns) {
+ const column: TableColumn = {
+ kind: 'regular',
+ table: table.name,
+ column: columnName,
+ };
+ if (this.selectedPivotsMap.has(columnKey(column))) {
+ continue;
+ }
+
+ group.push({
+ itemType: 'regular',
+ text: columnName,
+ callback() {
+ globals.dispatch(
+ Actions.setPivotTablePivotSelected({column, selected: true}));
+ globals.dispatch(
+ Actions.setPivotTableQueryRequested({queryRequested: true}));
+ },
+ });
+ }
+ items.push({
+ itemType: 'group',
+ itemId: `pivot-${table.name}`,
+ text: `Add ${table.name} pivot`,
+ children: group,
+ });
+ }
+
pivotTableHeaders.push(
m('td',
readableColumnName(pivot),
@@ -501,6 +547,7 @@
m('td.menu', m(PopupMenuButton, {
icon: 'menu',
items: [{
+ itemType: 'regular',
text: 'Edit mode',
callback: () => {
globals.dispatch(
@@ -568,47 +615,6 @@
renderEditView(attrs: PivotTableReduxAttrs) {
return m(
'.pivot-table-redux.edit',
- m('div',
- m('h2', 'Pivots'),
- m('ul',
- tables.map(
- (t) => this.renderTablePivotColumns(t),
- ))),
- m('div',
- m('h2', 'Aggregations'),
- m('ul',
- m('li',
- m(AggregationCheckbox, {
- get: this.selectedAggregations,
- set: Actions.setPivotTableAggregationSelected,
- setKey: COUNT_AGGREGATION,
- }),
- 'count'),
- ...sliceAggregationColumns.map(
- (t) =>
- m('li',
- m(AggregationCheckbox, {
- get: this.selectedAggregations,
- set: Actions.setPivotTableAggregationSelected,
- setKey: {
- aggregationFunction: 'SUM',
- column: {kind: 'regular', table: 'slice', column: t},
- },
- }),
- t)),
- ...threadSliceAggregationColumns.map(
- (t) => m(
- 'li',
- m(AggregationCheckbox, {
- get: this.selectedAggregations,
- set: Actions.setPivotTableAggregationSelected,
- setKey: {
- aggregationFunction: 'SUM',
- column:
- {kind: 'regular', table: 'thread_slice', column: t},
- },
- }),
- `thread_slice.${t}`)))),
this.renderQuery(attrs));
}
}
diff --git a/ui/src/frontend/popup_menu.ts b/ui/src/frontend/popup_menu.ts
index 2111d9d..863ddd1 100644
--- a/ui/src/frontend/popup_menu.ts
+++ b/ui/src/frontend/popup_menu.ts
@@ -15,13 +15,23 @@
import * as m from 'mithril';
import {globals} from './globals';
-export interface PopupMenuItem {
+export interface RegularPopupMenuItem {
+ itemType: 'regular';
// Display text
text: string;
// Action on menu item click
callback: () => void;
}
+export interface GroupPopupMenuItem {
+ itemType: 'group';
+ text: string;
+ itemId: string;
+ children: PopupMenuItem[];
+}
+
+export type PopupMenuItem = RegularPopupMenuItem|GroupPopupMenuItem;
+
interface PopupMenuButtonAttrs {
// Icon for button opening a menu
icon: string;
@@ -83,6 +93,7 @@
// Component that displays a button that shows a popup menu on click.
export class PopupMenuButton implements m.ClassComponent<PopupMenuButtonAttrs> {
popupShown = false;
+ expandedGroups: Set<string> = new Set();
setVisible(visible: boolean) {
this.popupShown = visible;
@@ -94,6 +105,42 @@
globals.rafScheduler.scheduleFullRedraw();
}
+ renderItem(item: PopupMenuItem): m.Child {
+ switch (item.itemType) {
+ case 'regular':
+ return m(
+ 'button.open-menu',
+ {
+ onclick: () => {
+ item.callback();
+ // Hide the menu item after the action has been invoked
+ this.setVisible(false);
+ },
+ },
+ item.text);
+ case 'group':
+ const isExpanded = this.expandedGroups.has(item.itemId);
+ return m(
+ 'div',
+ m('button.open-menu.disallow-selection',
+ {
+ onclick: () => {
+ if (this.expandedGroups.has(item.itemId)) {
+ this.expandedGroups.delete(item.itemId);
+ } else {
+ this.expandedGroups.add(item.itemId);
+ }
+ globals.rafScheduler.scheduleFullRedraw();
+ },
+ },
+ // Show text with up/down arrow, depending on expanded state.
+ item.text + (isExpanded ? ' \u25B2' : ' \u25BC')),
+ isExpanded ? m('div.nested-menu',
+ item.children.map((item) => this.renderItem(item))) :
+ null);
+ }
+ }
+
view(vnode: m.Vnode<PopupMenuButtonAttrs, this>) {
return m(
'.dropdown',
@@ -105,16 +152,6 @@
},
vnode.attrs.icon),
m(this.popupShown ? '.popup-menu.opened' : '.popup-menu.closed',
- vnode.attrs.items.map(
- (item) =>
- m('button.open-menu',
- {
- onclick: () => {
- item.callback();
- // Hide the menu item after the action has been invoked
- this.setVisible(false);
- },
- },
- item.text))));
+ vnode.attrs.items.map((item) => this.renderItem(item))));
}
}
diff --git a/ui/src/frontend/record_page_v2.ts b/ui/src/frontend/record_page_v2.ts
index e85b2a5..6474ab8 100644
--- a/ui/src/frontend/record_page_v2.ts
+++ b/ui/src/frontend/record_page_v2.ts
@@ -20,6 +20,12 @@
import {TRACE_SUFFIX} from '../common/constants';
import {TraceConfig} from '../common/protos';
import {
+ BUFFER_USAGE_INCORRECT_FORMAT,
+ BUFFER_USAGE_NOT_ACCESSIBLE,
+ EXTENSION_NAME,
+ EXTENSION_URL,
+} from '../common/recordingV2/chrome_utils';
+import {
genTraceConfig,
RecordingConfigUtils,
} from '../common/recordingV2/recording_config_utils';
@@ -28,6 +34,7 @@
showRecordingModal,
} from '../common/recordingV2/recording_error_handling';
import {
+ ChromeTargetInfo,
OnTargetChangeCallback,
RecordingTargetV2,
TargetInfo,
@@ -64,6 +71,7 @@
import {CodeSnippet} from './record_widgets';
import {AdvancedSettings} from './recording/advanced_settings';
import {AndroidSettings} from './recording/android_settings';
+import {ChromeSettings} from './recording/chrome_settings';
import {CpuSettings} from './recording/cpu_settings';
import {GpuSettings} from './recording/gpu_settings';
import {MemorySettings} from './recording/memory_settings';
@@ -122,9 +130,9 @@
this.tracingSession.stop();
}
- getTraceBufferUsage(): Promise<number>|undefined {
+ getTraceBufferUsage(): Promise<number> {
if (!this.tracingSession) {
- return undefined;
+ throw new RecordingError(BUFFER_USAGE_NOT_ACCESSIBLE);
}
return this.tracingSession.getTraceBufferUsage();
}
@@ -180,6 +188,11 @@
},
};
+function isChromeTargetInfo(targetInfo: TargetInfo):
+ targetInfo is ChromeTargetInfo {
+ return ['CHROME', 'CHROME_OS'].includes(targetInfo.targetType);
+}
+
function RecordHeader() {
const platformSelection = RecordingPlatformSelection();
const statusLabel = RecordingStatusLabel();
@@ -296,15 +309,29 @@
m('.buttons', StopCancelButtons()));
}
-function BufferUsageProgressBar() {
- const bufferUsagePromise = tracingSessionWrapper?.getTraceBufferUsage();
- if (!bufferUsagePromise) {
- return undefined;
- }
+async function fetchBufferUsage() {
+ if (!tracingSessionWrapper) return;
- bufferUsagePromise.then((percentage) => {
+ try {
+ const percentage = await tracingSessionWrapper.getTraceBufferUsage();
publishBufferUsage({percentage});
- });
+ } catch (e) {
+ if (e instanceof RecordingError) {
+ if (e.message === BUFFER_USAGE_INCORRECT_FORMAT) {
+ // If we have received an incorrectly formatted message, we will
+ // redraw, so we can query the buffer usage again.
+ globals.rafScheduler.scheduleFullRedraw();
+ }
+ // We ignore other possible tracing buffer message errors because they
+ // are not necessary for the trace to be successfully collected.
+ } else {
+ throw e;
+ }
+ }
+}
+
+function BufferUsageProgressBar() {
+ fetchBufferUsage();
const bufferUsage = globals.bufferUsage ? globals.bufferUsage : 0.0;
// Buffer usage is not available yet on Android.
@@ -322,8 +349,6 @@
const linuxUrl = 'https://perfetto.dev/docs/quickstart/linux-tracing';
const cmdlineUrl =
'https://perfetto.dev/docs/quickstart/android-tracing#perfetto-cmdline';
- const extensionURL = `https://chrome.google.com/webstore/detail/
- perfetto-ui/lfmkphfpdbjijhpomgecfikhfohaoine`;
const notes: m.Children = [];
@@ -342,12 +367,6 @@
`sideload the latest version of
Perfetto.`));
- const msgChrome =
- m('.note',
- `To trace Chrome from the Perfetto UI, you need to install our `,
- m('a', {href: extensionURL, target: '_blank'}, 'Chrome extension'),
- ' and then reload this page.');
-
const msgLinux =
m('.note',
`Use this `,
@@ -372,17 +391,21 @@
}
targetFactoryRegistry.listRecordingProblems().map((recordingProblem) => {
- notes.push(m('.note', recordingProblem));
+ if (recordingProblem.includes(EXTENSION_URL)) {
+ // Special case for rendering the link to the Chrome extension.
+ const parts = recordingProblem.split(EXTENSION_URL);
+ notes.push(
+ m('.note',
+ parts[0],
+ m('a', {href: EXTENSION_URL, target: '_blank'}, EXTENSION_NAME),
+ parts[1]));
+ }
});
if (recordingTargetV2) {
const targetInfo = recordingTargetV2.getInfo();
switch (targetInfo.targetType) {
- case 'CHROME':
- case 'CHROME_OS':
- if (!globals.state.extensionInstalled) notes.push(msgChrome);
- break;
case 'LINUX':
notes.push(msgLinux);
break;
@@ -409,8 +432,12 @@
function RecordingSnippet() {
const targetInfo = assertExists(recordingTargetV2).getInfo();
// We don't need commands to start tracing on chrome
- if (targetInfo.targetType === 'CHROME') {
- if (!globals.state.extensionInstalled) return undefined;
+ if (isChromeTargetInfo(targetInfo)) {
+ if (tracingSessionWrapper) {
+ // If the UI has started tracing, don't display a message guiding the user
+ // to start recording.
+ return undefined;
+ }
return m(
'div',
m('label', `To trace Chrome from the Perfetto UI you just have to press
@@ -482,7 +509,8 @@
if (targetType === 'ANDROID' &&
globals.state.recordConfig.mode !== 'LONG_TRACE') {
buttons.push(start);
- } else if (targetType === 'CHROME' && globals.state.extensionInstalled) {
+ } else if (
+ isChromeTargetInfo(targetInfo) && targetInfo.isExtensionInstalled) {
buttons.push(start);
}
return m('.button', buttons);
@@ -521,14 +549,10 @@
const target = assertExists(recordingTargetV2);
const targetInfo = target.getInfo();
- if (targetInfo.targetType === 'ANDROID' ||
- targetInfo.targetType === 'CHROME') {
- globals.logging.logEvent(
- 'Record Trace',
- `Record trace (${targetInfo.targetType}${targetInfo.targetType})`);
- const traceConfig = genTraceConfig(globals.state.recordConfig, targetInfo);
- tracingSessionWrapper = new TracingSessionWrapper(traceConfig, target);
- }
+ globals.logging.logEvent(
+ 'Record Trace', `Record trace (${targetInfo.targetType})`);
+ const traceConfig = genTraceConfig(globals.state.recordConfig, targetInfo);
+ tracingSessionWrapper = new TracingSessionWrapper(traceConfig, target);
globals.rafScheduler.scheduleFullRedraw();
}
@@ -539,6 +563,12 @@
}
function recordMenu(routePage: string) {
+ const chromeProbe =
+ m('a[href="#!/record/chrome"]',
+ m(`li${routePage === 'chrome' ? '.active' : ''}`,
+ m('i.material-icons', 'laptop_chromebook'),
+ m('.title', 'Chrome'),
+ m('.sub', 'Chrome traces')));
const cpuProbe =
m('a[href="#!/record/cpu"]',
m(`li${routePage === 'cpu' ? '.active' : ''}`,
@@ -579,7 +609,9 @@
const targetType = assertExists(recordingTargetV2).getInfo().targetType;
const probes = [];
if (targetType === 'CHROME_OS' || targetType === 'LINUX') {
- probes.push(cpuProbe, powerProbe, memoryProbe, advancedProbe);
+ probes.push(cpuProbe, powerProbe, memoryProbe, chromeProbe, advancedProbe);
+ } else if (targetType === 'CHROME') {
+ probes.push(chromeProbe);
} else {
probes.push(
cpuProbe,
@@ -587,6 +619,7 @@
powerProbe,
memoryProbe,
androidProbe,
+ chromeProbe,
advancedProbe);
}
@@ -694,12 +727,12 @@
['power', PowerSettings],
['memory', MemorySettings],
['android', AndroidSettings],
+ ['chrome', ChromeSettings],
['advanced', AdvancedSettings],
- // TODO(octaviant): Add Chrome settings.
]);
for (const [section, component] of settingsSections.entries()) {
pages.push(m(component, {
- dataSources: [],
+ dataSources: targetInfo.dataSources,
cssClass: maybeGetActiveCss(routePage, section),
} as RecordingSectionAttrs));
}
diff --git a/ui/src/frontend/recording/chrome_settings.ts b/ui/src/frontend/recording/chrome_settings.ts
index 3dbd2a2..09b729e 100644
--- a/ui/src/frontend/recording/chrome_settings.ts
+++ b/ui/src/frontend/recording/chrome_settings.ts
@@ -14,18 +14,31 @@
import * as m from 'mithril';
+import {DataSource} from '../../common/recordingV2/recording_interfaces_v2';
import {getBuiltinChromeCategoryList, isChromeTarget} from '../../common/state';
import {globals} from '../globals';
import {CategoriesCheckboxList, CompactProbe} from '../record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
-class ChromeCategoriesSelection implements m.ClassComponent {
- view() {
+function extractChromeCategories(dataSources: DataSource[]): string[]|
+ undefined {
+ for (const dataSource of dataSources) {
+ if (dataSource.name === 'chromeCategories') {
+ return dataSource.descriptor as string[];
+ }
+ }
+ return undefined;
+}
+
+class ChromeCategoriesSelection implements
+ m.ClassComponent<RecordingSectionAttrs> {
+ view({attrs}: m.CVnode<RecordingSectionAttrs>) {
// If we are attempting to record via the Chrome extension, we receive the
// list of actually supported categories via DevTools. Otherwise, we fall
// back to an integrated list of categories from a recent version of Chrome.
- let categories = globals.state.chromeCategories;
+ let categories = globals.state.chromeCategories ||
+ extractChromeCategories(attrs.dataSources);
if (!categories || !isChromeTarget(globals.state.recordingTarget)) {
categories = getBuiltinChromeCategoryList();
}
@@ -102,6 +115,6 @@
setEnabled: (cfg, val) => cfg.chromeLogs = val,
isEnabled: (cfg) => cfg.chromeLogs,
}),
- m(ChromeCategoriesSelection));
+ m(ChromeCategoriesSelection, attrs));
}
}