Merge "test: Remove old unused header" into main
diff --git a/Android.bp b/Android.bp
index 0efdda3..2341f3b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2467,6 +2467,8 @@
":perfetto_src_trace_processor_export_json",
":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
":perfetto_src_trace_processor_importers_android_bugreport_android_log_event",
+ ":perfetto_src_trace_processor_importers_art_method_art_method",
+ ":perfetto_src_trace_processor_importers_art_method_art_method_event",
":perfetto_src_trace_processor_importers_common_common",
":perfetto_src_trace_processor_importers_common_parser_types",
":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
@@ -12394,6 +12396,20 @@
],
}
+// GN: //src/trace_processor/importers/art_method:art_method
+filegroup {
+ name: "perfetto_src_trace_processor_importers_art_method_art_method",
+ srcs: [
+ "src/trace_processor/importers/art_method/art_method_parser_impl.cc",
+ "src/trace_processor/importers/art_method/art_method_tokenizer.cc",
+ ],
+}
+
+// GN: //src/trace_processor/importers/art_method:art_method_event
+filegroup {
+ name: "perfetto_src_trace_processor_importers_art_method_art_method_event",
+}
+
// GN: //src/trace_processor/importers/common:common
filegroup {
name: "perfetto_src_trace_processor_importers_common_common",
@@ -15583,6 +15599,8 @@
":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
":perfetto_src_trace_processor_importers_android_bugreport_android_log_event",
":perfetto_src_trace_processor_importers_android_bugreport_unittests",
+ ":perfetto_src_trace_processor_importers_art_method_art_method",
+ ":perfetto_src_trace_processor_importers_art_method_art_method_event",
":perfetto_src_trace_processor_importers_common_common",
":perfetto_src_trace_processor_importers_common_parser_types",
":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
@@ -16653,6 +16671,8 @@
":perfetto_src_trace_processor_export_json",
":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
":perfetto_src_trace_processor_importers_android_bugreport_android_log_event",
+ ":perfetto_src_trace_processor_importers_art_method_art_method",
+ ":perfetto_src_trace_processor_importers_art_method_art_method_event",
":perfetto_src_trace_processor_importers_common_common",
":perfetto_src_trace_processor_importers_common_parser_types",
":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
@@ -16901,6 +16921,7 @@
":perfetto_src_trace_processor_db_compare",
":perfetto_src_trace_processor_db_minimal",
":perfetto_src_trace_processor_importers_android_bugreport_android_log_event",
+ ":perfetto_src_trace_processor_importers_art_method_art_method_event",
":perfetto_src_trace_processor_importers_common_common",
":perfetto_src_trace_processor_importers_common_parser_types",
":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
@@ -17072,6 +17093,8 @@
":perfetto_src_trace_processor_export_json",
":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
":perfetto_src_trace_processor_importers_android_bugreport_android_log_event",
+ ":perfetto_src_trace_processor_importers_art_method_art_method",
+ ":perfetto_src_trace_processor_importers_art_method_art_method_event",
":perfetto_src_trace_processor_importers_common_common",
":perfetto_src_trace_processor_importers_common_parser_types",
":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
diff --git a/BUILD b/BUILD
index 5a733b4..4e69402 100644
--- a/BUILD
+++ b/BUILD
@@ -223,6 +223,8 @@
":src_trace_processor_export_json",
":src_trace_processor_importers_android_bugreport_android_bugreport",
":src_trace_processor_importers_android_bugreport_android_log_event",
+ ":src_trace_processor_importers_art_method_art_method",
+ ":src_trace_processor_importers_art_method_art_method_event",
":src_trace_processor_importers_common_common",
":src_trace_processor_importers_common_parser_types",
":src_trace_processor_importers_common_trace_parser_hdr",
@@ -1515,6 +1517,25 @@
],
)
+# GN target: //src/trace_processor/importers/art_method:art_method
+perfetto_filegroup(
+ name = "src_trace_processor_importers_art_method_art_method",
+ srcs = [
+ "src/trace_processor/importers/art_method/art_method_parser_impl.cc",
+ "src/trace_processor/importers/art_method/art_method_parser_impl.h",
+ "src/trace_processor/importers/art_method/art_method_tokenizer.cc",
+ "src/trace_processor/importers/art_method/art_method_tokenizer.h",
+ ],
+)
+
+# GN target: //src/trace_processor/importers/art_method:art_method_event
+perfetto_filegroup(
+ name = "src_trace_processor_importers_art_method_art_method_event",
+ srcs = [
+ "src/trace_processor/importers/art_method/art_method_event.h",
+ ],
+)
+
# GN target: //src/trace_processor/importers/common:common
perfetto_filegroup(
name = "src_trace_processor_importers_common_common",
@@ -6357,6 +6378,8 @@
":src_trace_processor_export_json",
":src_trace_processor_importers_android_bugreport_android_bugreport",
":src_trace_processor_importers_android_bugreport_android_log_event",
+ ":src_trace_processor_importers_art_method_art_method",
+ ":src_trace_processor_importers_art_method_art_method_event",
":src_trace_processor_importers_common_common",
":src_trace_processor_importers_common_parser_types",
":src_trace_processor_importers_common_trace_parser_hdr",
@@ -6556,6 +6579,8 @@
":src_trace_processor_export_json",
":src_trace_processor_importers_android_bugreport_android_bugreport",
":src_trace_processor_importers_android_bugreport_android_log_event",
+ ":src_trace_processor_importers_art_method_art_method",
+ ":src_trace_processor_importers_art_method_art_method_event",
":src_trace_processor_importers_common_common",
":src_trace_processor_importers_common_parser_types",
":src_trace_processor_importers_common_trace_parser_hdr",
@@ -6812,6 +6837,8 @@
":src_trace_processor_export_json",
":src_trace_processor_importers_android_bugreport_android_bugreport",
":src_trace_processor_importers_android_bugreport_android_log_event",
+ ":src_trace_processor_importers_art_method_art_method",
+ ":src_trace_processor_importers_art_method_art_method_event",
":src_trace_processor_importers_common_common",
":src_trace_processor_importers_common_parser_types",
":src_trace_processor_importers_common_trace_parser_hdr",
diff --git a/CHANGELOG b/CHANGELOG
index 03fa600..5c1cf03 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,9 +6,18 @@
* Increased watchdog timeout to 180s from 30s to make watchdog crashes
much less likely when system is under heavy load.
SQL Standard library:
- *
+ * Improved CPU cycles calculation in `linux.cpu.utilization` modules:
+ `process`, `system` and `thread` by fixing a bug responsible for too high
+ CPU cycles values.
+ * Introduces functions responsible for calculating CPU cycles with
+ breakdown by CPU, thread, process and slice for a given interval.
Trace Processor:
- *
+ * Renamed Trace Processor's C++ method `RegisterSqlModule()` to
+ `RegisterSqlPackage()`, which better represents the module/package
+ relationship. Package is the top level grouping of modules, which are
+ objects being included with `INCLUDE PERFETTO MODULE`.
+ `RegisterSqlModule()` is still available and runs `RegisterSqlPackage()`.
+ `RegisterSqlModule()` will be deprecated in v50.0.
UI:
* Scheduling wakeup information now reflects whether the wakeup came
from an interrupt context. The per-cpu scheduling tracks now show only
diff --git a/include/perfetto/trace_processor/basic_types.h b/include/perfetto/trace_processor/basic_types.h
index f06b782..44ead6e 100644
--- a/include/perfetto/trace_processor/basic_types.h
+++ b/include/perfetto/trace_processor/basic_types.h
@@ -263,9 +263,9 @@
};
// Data used to register a new SQL package.
-struct SqlModule {
+struct SqlPackage {
// Must be unique among modules, or can be used to override existing module if
- // |allow_module_override| is set.
+ // |allow_override| is set.
std::string name;
// Pairs of strings used for |INCLUDE| with the contents of SQL files being
@@ -276,10 +276,18 @@
// run, with slashes replaced by dots and without the SQL extension. For
// example, 'android/camera/jank.sql' would be included by
// 'android.camera.jank'.
- std::vector<std::pair<std::string, std::string>> files;
+ std::vector<std::pair<std::string, std::string>> modules;
// If true, SqlPackage will override registered module with the same name. Can
// only be set if enable_dev_features is true, otherwise will throw an error.
+ bool allow_override = false;
+};
+
+// Deprecated. Use only with |trace_processor->RegisterSqlModule()|. Alias of
+// |SqlPackage|.
+struct SqlModule {
+ std::string name;
+ std::vector<std::pair<std::string, std::string>> files;
bool allow_module_override = false;
};
diff --git a/include/perfetto/trace_processor/trace_processor.h b/include/perfetto/trace_processor/trace_processor.h
index 56d6630..7f70653 100644
--- a/include/perfetto/trace_processor/trace_processor.h
+++ b/include/perfetto/trace_processor/trace_processor.h
@@ -63,7 +63,7 @@
// PERFETTO MODULE camera.cpu.metrics". The first word of the string has to be
// a package name and there can be only one package registered with a given
// name.
- virtual base::Status RegisterSqlModule(SqlModule) = 0;
+ virtual base::Status RegisterSqlPackage(SqlPackage) = 0;
// Registers a metric at the given path which will run the specified SQL.
virtual base::Status RegisterMetric(const std::string& path,
@@ -138,6 +138,11 @@
// loaded by trace processor shell at runtime. The message is encoded as
// DescriptorSet, defined in perfetto/trace_processor/trace_processor.proto.
virtual std::vector<uint8_t> GetMetricDescriptors() = 0;
+
+ // Deprecated. Use |RegisterSqlPackage()| instead, which is identical in
+ // functionality to |RegisterSqlModule()| and the only difference is in
+ // the argument, which is directly translatable to |SqlPackage|.
+ virtual base::Status RegisterSqlModule(SqlModule) = 0;
};
} // namespace trace_processor
diff --git a/protos/perfetto/trace_processor/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index c597206..049d089 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -88,13 +88,15 @@
optional string fatal_error = 5;
enum TraceProcessorMethod {
+ reserved 4, 12;
+ reserved "TPM_QUERY_RAW_DEPRECATED";
+ reserved "TPM_REGISTER_SQL_MODULE";
+
TPM_UNSPECIFIED = 0;
TPM_APPEND_TRACE_DATA = 1;
TPM_FINALIZE_TRACE_DATA = 2;
TPM_QUERY_STREAMING = 3;
// Previously: TPM_QUERY_RAW_DEPRECATED
- reserved 4;
- reserved "TPM_QUERY_RAW_DEPRECATED";
TPM_COMPUTE_METRIC = 5;
TPM_GET_METRIC_DESCRIPTORS = 6;
TPM_RESTORE_INITIAL_TABLES = 7;
@@ -102,7 +104,7 @@
TPM_DISABLE_AND_READ_METATRACE = 9;
TPM_GET_STATUS = 10;
TPM_RESET_TRACE_PROCESSOR = 11;
- TPM_REGISTER_SQL_MODULE = 12;
+ TPM_REGISTER_SQL_PACKAGE = 13;
}
oneof type {
@@ -134,8 +136,8 @@
EnableMetatraceArgs enable_metatrace_args = 106;
// For TPM_RESET_TRACE_PROCESSOR.
ResetTraceProcessorArgs reset_trace_processor_args = 107;
- // For TPM_REGISTER_SQL_MODULE.
- RegisterSqlModuleArgs register_sql_module_args = 108;
+ // For TPM_REGISTER_SQL_PACKAGE.
+ RegisterSqlPackageArgs register_sql_package_args = 108;
// TraceProcessorMethod response args.
// For TPM_APPEND_TRACE_DATA.
@@ -150,8 +152,8 @@
DisableAndReadMetatraceResult metatrace = 209;
// For TPM_GET_STATUS.
StatusResult status = 210;
- // For TPM_REGISTER_SQL_MODULE.
- RegisterSqlModuleResult register_sql_module_result = 211;
+ // For TPM_REGISTER_SQL_PACKAGE.
+ RegisterSqlPackageResult register_sql_package_result = 211;
}
// Previously: RawQueryArgs for TPM_QUERY_RAW_DEPRECATED
@@ -335,15 +337,16 @@
optional bool ftrace_drop_until_all_cpus_valid = 4;
}
-message RegisterSqlModuleArgs {
+message RegisterSqlPackageArgs {
message Module {
optional string name = 1;
optional string sql = 2;
}
- optional string top_level_package_name = 1;
+ optional string package_name = 1;
repeated Module modules = 2;
- optional bool allow_module_override = 3;
+ optional bool allow_override = 3;
}
-message RegisterSqlModuleResult {
+
+message RegisterSqlPackageResult {
optional string error = 1;
}
\ No newline at end of file
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
index 0cc3646..c86d6c5 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/python/tools/check_ratchet.py b/python/tools/check_ratchet.py
index 573e6a3..0c845d2 100755
--- a/python/tools/check_ratchet.py
+++ b/python/tools/check_ratchet.py
@@ -37,7 +37,7 @@
from dataclasses import dataclass
EXPECTED_ANY_COUNT = 59
-EXPECTED_RUN_METRIC_COUNT = 5
+EXPECTED_RUN_METRIC_COUNT = 4
ROOT_DIR = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
diff --git a/src/shared_lib/test/api_integrationtest.cc b/src/shared_lib/test/api_integrationtest.cc
index 025ef37..d5c7d45 100644
--- a/src/shared_lib/test/api_integrationtest.cc
+++ b/src/shared_lib/test/api_integrationtest.cc
@@ -66,6 +66,7 @@
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::InSequence;
+using ::testing::IsNull;
using ::testing::NiceMock;
using ::testing::ResultOf;
using ::testing::Return;
@@ -966,6 +967,56 @@
PERFETTO_DS_TRACE(data_source_1, ctx) {}
}
+TEST_F(SharedLibDataSourceTest, GetInstanceLockedSuccess) {
+ bool ignored = false;
+ void* const kInstancePtr = &ignored;
+ EXPECT_CALL(ds2_callbacks_, OnSetup(_, _, _, _, kDataSource2UserArg, _))
+ .WillOnce(Return(kInstancePtr));
+ TracingSession tracing_session =
+ TracingSession::Builder().set_data_source_name(kDataSourceName2).Build();
+
+ void* arg = nullptr;
+ PERFETTO_DS_TRACE(data_source_2, ctx) {
+ arg = PerfettoDsImplGetInstanceLocked(data_source_2.impl, ctx.impl.inst_id);
+ if (arg) {
+ PerfettoDsImplReleaseInstanceLocked(data_source_2.impl, ctx.impl.inst_id);
+ }
+ }
+
+ EXPECT_EQ(arg, kInstancePtr);
+}
+
+TEST_F(SharedLibDataSourceTest, GetInstanceLockedFailure) {
+ bool ignored = false;
+ void* const kInstancePtr = &ignored;
+ EXPECT_CALL(ds2_callbacks_, OnSetup(_, _, _, _, kDataSource2UserArg, _))
+ .WillOnce(Return(kInstancePtr));
+ TracingSession tracing_session =
+ TracingSession::Builder().set_data_source_name(kDataSourceName2).Build();
+
+ WaitableEvent inside_tracing;
+ WaitableEvent stopped;
+
+ std::thread t([&] {
+ PERFETTO_DS_TRACE(data_source_2, ctx) {
+ inside_tracing.Notify();
+ stopped.WaitForNotification();
+ void* arg =
+ PerfettoDsImplGetInstanceLocked(data_source_2.impl, ctx.impl.inst_id);
+ if (arg) {
+ PerfettoDsImplReleaseInstanceLocked(data_source_2.impl,
+ ctx.impl.inst_id);
+ }
+ EXPECT_THAT(arg, IsNull());
+ }
+ });
+
+ inside_tracing.WaitForNotification();
+ tracing_session.StopBlocking();
+ stopped.Notify();
+ t.join();
+}
+
// Regression test for a `PerfettoDsImplReleaseInstanceLocked()`. Under very
// specific circumstances, that depends on the implementation details of
// `TracingMuxerImpl`, the following events can happen:
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 331634c..89c5ee8 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -168,6 +168,7 @@
"../protozero",
"db",
"importers/android_bugreport",
+ "importers/art_method",
"importers/common",
"importers/etw:full",
"importers/ftrace:full",
diff --git a/src/trace_processor/forwarding_trace_parser.cc b/src/trace_processor/forwarding_trace_parser.cc
index 4200209..babde0c 100644
--- a/src/trace_processor/forwarding_trace_parser.cc
+++ b/src/trace_processor/forwarding_trace_parser.cc
@@ -68,6 +68,7 @@
case kZipFile:
case kAndroidLogcatTraceType:
case kGeckoTraceType:
+ case kArtMethodTraceType:
return TraceSorter::SortingMode::kFullSort;
case kProtoTraceType:
diff --git a/src/trace_processor/importers/art_method/BUILD.gn b/src/trace_processor/importers/art_method/BUILD.gn
new file mode 100644
index 0000000..57f6c62
--- /dev/null
+++ b/src/trace_processor/importers/art_method/BUILD.gn
@@ -0,0 +1,45 @@
+# Copyright (C) 2024 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("../../../../gn/test.gni")
+
+source_set("art_method_event") {
+ sources = [ "art_method_event.h" ]
+ deps = [
+ "../../../../gn:default_deps",
+ "../../containers",
+ ]
+}
+
+source_set("art_method") {
+ sources = [
+ "art_method_parser_impl.cc",
+ "art_method_parser_impl.h",
+ "art_method_tokenizer.cc",
+ "art_method_tokenizer.h",
+ ]
+ deps = [
+ ":art_method_event",
+ "../../../../gn:default_deps",
+ "../../../../protos/perfetto/common:zero",
+ "../../../base",
+ "../../containers",
+ "../../importers/common",
+ "../../sorter",
+ "../../storage",
+ "../../types",
+ "../../util",
+ "../../util:trace_blob_view_reader",
+ ]
+}
diff --git a/src/trace_processor/importers/art_method/art_method_event.h b/src/trace_processor/importers/art_method/art_method_event.h
new file mode 100644
index 0000000..1a5edb2
--- /dev/null
+++ b/src/trace_processor/importers/art_method/art_method_event.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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_IMPORTERS_ART_METHOD_ART_METHOD_EVENT_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_EVENT_H_
+
+#include <cstdint>
+#include <optional>
+
+#include "src/trace_processor/containers/string_pool.h"
+
+namespace perfetto::trace_processor::art_method {
+
+struct alignas(8) ArtMethodEvent {
+ uint32_t tid;
+ StringPool::Id method;
+ enum { kEnter, kExit } action;
+ std::optional<StringPool::Id> pathname;
+ std::optional<uint32_t> line_number;
+};
+
+} // namespace perfetto::trace_processor::art_method
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_EVENT_H_
diff --git a/src/trace_processor/importers/art_method/art_method_parser_impl.cc b/src/trace_processor/importers/art_method/art_method_parser_impl.cc
new file mode 100644
index 0000000..f80d2f4
--- /dev/null
+++ b/src/trace_processor/importers/art_method/art_method_parser_impl.cc
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 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/importers/art_method/art_method_parser_impl.h"
+
+#include <cstdint>
+
+#include "src/trace_processor/importers/art_method/art_method_event.h"
+#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/slice_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/common/track_tracker.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::trace_processor::art_method {
+
+ArtMethodParserImpl::ArtMethodParserImpl(TraceProcessorContext* context)
+ : context_(context),
+ pathname_id_(context->storage->InternString("pathname")),
+ line_number_id_(context->storage->InternString("line_number")) {}
+
+ArtMethodParserImpl::~ArtMethodParserImpl() = default;
+
+void ArtMethodParserImpl::ParseArtMethodEvent(int64_t ts, ArtMethodEvent e) {
+ UniqueTid utid = context_->process_tracker->GetOrCreateThread(e.tid);
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ switch (e.action) {
+ case ArtMethodEvent::kEnter:
+ context_->slice_tracker->Begin(
+ ts, track_id, kNullStringId, e.method,
+ [this, &e](ArgsTracker::BoundInserter* i) {
+ if (e.pathname) {
+ i->AddArg(pathname_id_, Variadic::String(*e.pathname));
+ }
+ if (e.line_number) {
+ i->AddArg(line_number_id_, Variadic::Integer(*e.line_number));
+ }
+ });
+ break;
+ case ArtMethodEvent::kExit:
+ context_->slice_tracker->End(ts, track_id);
+ break;
+ }
+}
+
+} // namespace perfetto::trace_processor::art_method
diff --git a/src/trace_processor/importers/art_method/art_method_parser_impl.h b/src/trace_processor/importers/art_method/art_method_parser_impl.h
new file mode 100644
index 0000000..09759cd
--- /dev/null
+++ b/src/trace_processor/importers/art_method/art_method_parser_impl.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 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_IMPORTERS_ART_METHOD_ART_METHOD_PARSER_IMPL_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_PARSER_IMPL_H_
+
+#include <cstdint>
+
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/importers/art_method/art_method_event.h"
+#include "src/trace_processor/importers/common/trace_parser.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::art_method {
+
+class ArtMethodParserImpl : public ArtMethodParser {
+ public:
+ explicit ArtMethodParserImpl(TraceProcessorContext*);
+ ~ArtMethodParserImpl() override;
+
+ void ParseArtMethodEvent(int64_t ts, ArtMethodEvent) override;
+
+ private:
+ TraceProcessorContext* const context_;
+
+ StringPool::Id pathname_id_;
+ StringPool::Id line_number_id_;
+};
+
+} // namespace perfetto::trace_processor::art_method
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_PARSER_IMPL_H_
diff --git a/src/trace_processor/importers/art_method/art_method_tokenizer.cc b/src/trace_processor/importers/art_method/art_method_tokenizer.cc
new file mode 100644
index 0000000..fd32c78
--- /dev/null
+++ b/src/trace_processor/importers/art_method/art_method_tokenizer.cc
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2024 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/importers/art_method/art_method_tokenizer.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+
+#include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/ext/base/utils.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/art_method/art_method_event.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/trace_blob_view_reader.h"
+
+#include "protos/perfetto/common/builtin_clock.pbzero.h"
+
+namespace perfetto::trace_processor::art_method {
+namespace {
+
+constexpr uint32_t kTraceMagic = 0x574f4c53; // 'SLOW'
+
+std::string_view ReadLine(util::TraceBlobViewReader& reader,
+ util::TraceBlobViewReader::Iterator& it) {
+ size_t begin = it.file_offset();
+ if (!it.MaybeFindAndAdvance('\n')) {
+ return {};
+ }
+ auto x = reader.SliceOff(begin, it.file_offset() - begin);
+ std::string_view str(reinterpret_cast<const char*>(x->data()), x->size());
+ PERFETTO_CHECK(it.MaybeAdvance(1));
+ return str;
+}
+
+std::string ConstructPathname(const std::string& class_name,
+ const std::string& pathname) {
+ size_t index = class_name.rfind('/');
+ if (index != std::string::npos && base::EndsWith(pathname, ".java")) {
+ return class_name.substr(0, index + 1) + pathname;
+ }
+ return pathname;
+}
+
+uint64_t ToLong(const TraceBlobView& tbv) {
+ uint64_t x = 0;
+ memcpy(base::AssumeLittleEndian(&x), tbv.data(), tbv.size());
+ return x;
+}
+
+uint32_t ToInt(const TraceBlobView& tbv) {
+ uint32_t x = 0;
+ memcpy(base::AssumeLittleEndian(&x), tbv.data(), tbv.size());
+ return x;
+}
+
+uint16_t ToShort(const TraceBlobView& tbv) {
+ uint16_t x = 0;
+ memcpy(base::AssumeLittleEndian(&x), tbv.data(), tbv.size());
+ return x;
+}
+
+} // namespace
+
+ArtMethodTokenizer::ArtMethodTokenizer(TraceProcessorContext* ctx)
+ : context_(ctx) {}
+ArtMethodTokenizer::~ArtMethodTokenizer() = default;
+
+base::Status ArtMethodTokenizer::Parse(TraceBlobView blob) {
+ reader_.PushBack(std::move(blob));
+ auto it = reader_.GetIterator();
+ for (bool cnt = true; cnt;) {
+ switch (mode_) {
+ case kHeaderDetection: {
+ ASSIGN_OR_RETURN(cnt, ParseHeaderDetection(it));
+ break;
+ }
+ case kHeaderVersion: {
+ ASSIGN_OR_RETURN(cnt, ParseHeaderVersion(it));
+ break;
+ }
+ case kHeaderOptions: {
+ ASSIGN_OR_RETURN(cnt, ParseHeaderOptions(it));
+ break;
+ }
+ case kHeaderThreads: {
+ ASSIGN_OR_RETURN(cnt, ParseHeaderThreads(it));
+ break;
+ }
+ case kHeaderMethods: {
+ ASSIGN_OR_RETURN(cnt, ParseHeaderMethods(it));
+ break;
+ }
+ case kDataHeader: {
+ ASSIGN_OR_RETURN(cnt, ParseDataHeader(it));
+ break;
+ }
+ case kData: {
+ size_t s = it.file_offset();
+ for (size_t i = s;; i += record_size_) {
+ auto record = reader_.SliceOff(i, record_size_);
+ if (!record) {
+ PERFETTO_CHECK(it.MaybeAdvance(i - s));
+ cnt = false;
+ break;
+ }
+
+ ArtMethodEvent evt{};
+ evt.tid = version_ == 1 ? record->data()[0]
+ : ToShort(record->slice_off(0, 2));
+ uint32_t methodid_action = ToInt(record->slice_off(2, 4));
+ uint32_t ts_delta = clock_ == kDual ? ToInt(record->slice_off(10, 4))
+ : ToInt(record->slice_off(6, 4));
+
+ uint32_t action = methodid_action & 0x03;
+ uint32_t method_id = methodid_action & ~0x03u;
+
+ const auto& m = method_map_[method_id];
+ evt.method = m.name;
+ evt.pathname = m.pathname;
+ evt.line_number = m.line_number;
+ switch (action) {
+ case 0:
+ evt.action = ArtMethodEvent::kEnter;
+ break;
+ case 1:
+ case 2:
+ evt.action = ArtMethodEvent::kExit;
+ break;
+ }
+ ASSIGN_OR_RETURN(int64_t ts,
+ context_->clock_tracker->ToTraceTime(
+ protos::pbzero::BUILTIN_CLOCK_MONOTONIC,
+ (ts_ + ts_delta) * 1000));
+ context_->sorter->PushArtMethodEvent(ts, evt);
+ }
+ break;
+ }
+ }
+ }
+ reader_.PopFrontUntil(it.file_offset());
+ return base::OkStatus();
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::ParseHeaderDetection(Iterator& it) {
+ auto smagic = reader_.SliceOff(it.file_offset(), 4);
+ if (!smagic) {
+ return false;
+ }
+ uint32_t magic = ToInt(*smagic);
+ if (magic == kTraceMagic) {
+ return base::ErrStatus(
+ "ART Method trace is in streaming format: this is not supported");
+ }
+ auto line = ReadLine(reader_, it);
+ if (line.empty()) {
+ return false;
+ }
+ context_->clock_tracker->SetTraceTimeClock(
+ protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ RETURN_IF_ERROR(ParseHeaderSectionLine(line));
+ return true;
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::ParseHeaderVersion(Iterator& it) {
+ auto version_str = ReadLine(reader_, it);
+ if (version_str.empty()) {
+ return false;
+ }
+ auto version = base::StringToInt32(std::string(version_str));
+ if (!version || *version < 1 || *version > 3) {
+ return base::ErrStatus("ART Method trace: trace version (%s) not supported",
+ std::string(version_str).c_str());
+ }
+ version_ = static_cast<uint32_t>(*version);
+ mode_ = kHeaderOptions;
+ return true;
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::ParseHeaderOptions(Iterator& it) {
+ for (auto l = ReadLine(reader_, it); !l.empty(); l = ReadLine(reader_, it)) {
+ if (l[0] == '*') {
+ RETURN_IF_ERROR(ParseHeaderSectionLine(l));
+ return true;
+ }
+ auto res = base::SplitString(std::string(l), "=");
+ if (res.size() != 2) {
+ return base::ErrStatus("ART method tracing: unable to parse option");
+ }
+ if (res[0] == "clock") {
+ if (res[1] == "dual") {
+ clock_ = kDual;
+ } else if (res[1] == "wall") {
+ clock_ = kWall;
+ } else if (res[1] == "thread-cpu") {
+ return base::ErrStatus(
+ "ART method tracing: thread-cpu clock is *not* supported. Use wall "
+ "or dual clocks");
+ } else {
+ return base::ErrStatus("ART method tracing: unknown clock %s",
+ res[1].c_str());
+ }
+ }
+ }
+ return false;
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::ParseHeaderThreads(Iterator& it) {
+ for (auto l = ReadLine(reader_, it); !l.empty(); l = ReadLine(reader_, it)) {
+ if (l[0] == '*') {
+ RETURN_IF_ERROR(ParseHeaderSectionLine(l));
+ return true;
+ }
+ }
+ return false;
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::ParseHeaderMethods(Iterator& it) {
+ for (auto l = ReadLine(reader_, it); !l.empty(); l = ReadLine(reader_, it)) {
+ if (l[0] == '*') {
+ RETURN_IF_ERROR(ParseHeaderSectionLine(l));
+ return true;
+ }
+ auto tokens = base::SplitString(std::string(l), "\t");
+ auto id = base::StringToUInt32(tokens[0], 16);
+ if (!id) {
+ return base::ErrStatus(
+ "ART method trace: unable to parse method id as integer: %s",
+ tokens[0].c_str());
+ }
+
+ std::string class_name = tokens[1];
+ std::string method_name;
+ std::string signature;
+ std::optional<StringId> pathname;
+ std::optional<uint32_t> line_number;
+ if (tokens.size() == 6) {
+ method_name = tokens[2];
+ signature = tokens[3];
+ pathname = context_->storage->InternString(
+ base::StringView(ConstructPathname(class_name, tokens[4])));
+ line_number = base::StringToUInt32(tokens[5]);
+ } else if (tokens.size() > 2) {
+ if (base::StartsWith(tokens[3], "(")) {
+ method_name = tokens[2];
+ signature = tokens[3];
+ if (tokens.size() >= 5) {
+ pathname =
+ context_->storage->InternString(base::StringView(tokens[4]));
+ }
+ } else {
+ pathname = context_->storage->InternString(base::StringView(tokens[2]));
+ line_number = base::StringToUInt32(tokens[3]);
+ }
+ }
+ base::StackString<2048> slice_name("%s.%s: %s", class_name.c_str(),
+ method_name.c_str(), signature.c_str());
+ method_map_[*id] = {
+ context_->storage->InternString(slice_name.string_view()),
+ pathname,
+ line_number,
+ };
+ }
+ return false;
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::ParseDataHeader(Iterator& it) {
+ size_t begin = it.file_offset();
+ if (!it.MaybeAdvance(32)) {
+ return false;
+ }
+ auto header = reader_.SliceOff(begin, it.file_offset() - begin);
+ uint32_t magic = ToInt(header->slice_off(0, 4));
+ if (magic != kTraceMagic) {
+ return base::ErrStatus("ART Method trace: expected pre-data magic");
+ }
+ uint16_t version = ToShort(header->slice_off(4, 2));
+ if (version != version_) {
+ return base::ErrStatus(
+ "ART Method trace: trace version does not match data version");
+ }
+ ts_ = static_cast<int64_t>(ToLong(header->slice_off(8, 8)));
+ switch (version_) {
+ case 1:
+ record_size_ = 9;
+ break;
+ case 2:
+ record_size_ = 10;
+ break;
+ case 3:
+ record_size_ = ToShort(header->slice_off(16, 2));
+ break;
+ default:
+ PERFETTO_FATAL("Illegal version %u", version_);
+ }
+ mode_ = kData;
+ return true;
+}
+
+base::Status ArtMethodTokenizer::ParseHeaderSectionLine(std::string_view line) {
+ if (line == "*version") {
+ mode_ = kHeaderVersion;
+ return base::OkStatus();
+ }
+ if (line == "*threads") {
+ mode_ = kHeaderThreads;
+ return base::OkStatus();
+ }
+ if (line == "*methods") {
+ mode_ = kHeaderMethods;
+ return base::OkStatus();
+ }
+ if (line == "*end") {
+ mode_ = kDataHeader;
+ return base::OkStatus();
+ }
+ return base::ErrStatus(
+ "ART Method trace: unexpected line (%s) when expecting section header "
+ "(line starting with *)",
+ std::string(line).c_str());
+}
+
+base::Status ArtMethodTokenizer::NotifyEndOfFile() {
+ // DNS: also add a check here for whether our state machine reached the end
+ // too.
+ if (!reader_.empty() || mode_ != kData) {
+ return base::ErrStatus("ART Method trace: trace is incomplete");
+ }
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_processor::art_method
diff --git a/src/trace_processor/importers/art_method/art_method_tokenizer.h b/src/trace_processor/importers/art_method/art_method_tokenizer.h
new file mode 100644
index 0000000..91a0eed
--- /dev/null
+++ b/src/trace_processor/importers/art_method/art_method_tokenizer.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 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_IMPORTERS_ART_METHOD_ART_METHOD_TOKENIZER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_TOKENIZER_H_
+
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <string_view>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/trace_blob_view_reader.h"
+
+namespace perfetto::trace_processor::art_method {
+
+class ArtMethodTokenizer : public ChunkedTraceReader {
+ public:
+ explicit ArtMethodTokenizer(TraceProcessorContext*);
+ ~ArtMethodTokenizer() override;
+
+ base::Status Parse(TraceBlobView) override;
+ base::Status NotifyEndOfFile() override;
+
+ private:
+ using Iterator = util::TraceBlobViewReader::Iterator;
+ struct Method {
+ StringId name;
+ std::optional<StringId> pathname;
+ std::optional<uint32_t> line_number;
+ };
+
+ base::StatusOr<bool> ParseHeaderDetection(Iterator&);
+ base::StatusOr<bool> ParseHeaderVersion(Iterator&);
+ base::StatusOr<bool> ParseHeaderOptions(Iterator&);
+ base::StatusOr<bool> ParseHeaderThreads(Iterator&);
+ base::StatusOr<bool> ParseHeaderMethods(Iterator&);
+ base::StatusOr<bool> ParseDataHeader(Iterator&);
+
+ base::Status ParseHeaderSectionLine(std::string_view);
+
+ TraceProcessorContext* const context_;
+ util::TraceBlobViewReader reader_;
+ enum {
+ kHeaderDetection,
+ kHeaderVersion,
+ kHeaderOptions,
+ kHeaderThreads,
+ kHeaderMethods,
+ kDataHeader,
+ kData,
+ } mode_ = kHeaderDetection;
+ enum {
+ kWall,
+ kDual,
+ } clock_ = kWall;
+
+ uint32_t version_ = std::numeric_limits<uint32_t>::max();
+ int64_t ts_ = std::numeric_limits<int64_t>::max();
+ uint32_t record_size_ = std::numeric_limits<uint32_t>::max();
+ base::FlatHashMap<uint32_t, Method> method_map_;
+};
+
+} // namespace perfetto::trace_processor::art_method
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_TOKENIZER_H_
diff --git a/src/trace_processor/importers/common/trace_parser.cc b/src/trace_processor/importers/common/trace_parser.cc
index 01345cc..12f3589 100644
--- a/src/trace_processor/importers/common/trace_parser.cc
+++ b/src/trace_processor/importers/common/trace_parser.cc
@@ -27,6 +27,7 @@
ProtoTraceParser::~ProtoTraceParser() = default;
SpeRecordParser::~SpeRecordParser() = default;
GeckoTraceParser::~GeckoTraceParser() = default;
+ArtMethodParser::~ArtMethodParser() = default;
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/common/trace_parser.h b/src/trace_processor/importers/common/trace_parser.h
index 34475d6..735d27a 100644
--- a/src/trace_processor/importers/common/trace_parser.h
+++ b/src/trace_processor/importers/common/trace_parser.h
@@ -30,6 +30,9 @@
namespace gecko_importer {
struct GeckoEvent;
}
+namespace art_method {
+struct ArtMethodEvent;
+}
struct AndroidLogEvent;
class PacketSequenceStateGeneration;
@@ -97,6 +100,12 @@
virtual void ParseGeckoEvent(int64_t, gecko_importer::GeckoEvent) = 0;
};
+class ArtMethodParser {
+ public:
+ virtual ~ArtMethodParser();
+ virtual void ParseArtMethodEvent(int64_t, art_method::ArtMethodEvent) = 0;
+};
+
} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACE_PARSER_H_
diff --git a/src/trace_processor/importers/perf/spe_tokenizer.cc b/src/trace_processor/importers/perf/spe_tokenizer.cc
index 8186bbb..6c637b6 100644
--- a/src/trace_processor/importers/perf/spe_tokenizer.cc
+++ b/src/trace_processor/importers/perf/spe_tokenizer.cc
@@ -20,6 +20,7 @@
#include <cstring>
#include <memory>
#include <optional>
+#include <utility>
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
@@ -56,7 +57,7 @@
}
bool SpeTokenizer::ProcessRecord() {
- for (auto it = buffer_.begin(); it;) {
+ for (auto it = buffer_.GetIterator(); it;) {
uint8_t byte_0 = *it;
// Must be true (we passed the for loop condition).
it.MaybeAdvance(1);
diff --git a/src/trace_processor/rpc/rpc.cc b/src/trace_processor/rpc/rpc.cc
index 1f56688..88b7e82 100644
--- a/src/trace_processor/rpc/rpc.cc
+++ b/src/trace_processor/rpc/rpc.cc
@@ -324,10 +324,10 @@
resp.Send(rpc_response_fn_);
break;
}
- case RpcProto::TPM_REGISTER_SQL_MODULE: {
+ case RpcProto::TPM_REGISTER_SQL_PACKAGE: {
Response resp(tx_seq_id_++, req_type);
- base::Status status = RegisterSqlModule(req.register_sql_module_args());
- auto* res = resp->set_register_sql_module_result();
+ base::Status status = RegisterSqlPackage(req.register_sql_package_args());
+ auto* res = resp->set_register_sql_package_result();
if (!status.ok()) {
res->set_error(status.message());
}
@@ -410,16 +410,16 @@
ResetTraceProcessorInternal(config);
}
-base::Status Rpc::RegisterSqlModule(protozero::ConstBytes bytes) {
- protos::pbzero::RegisterSqlModuleArgs::Decoder args(bytes);
- SqlModule package;
- package.name = args.top_level_package_name().ToStdString();
- package.allow_module_override = args.allow_module_override();
+base::Status Rpc::RegisterSqlPackage(protozero::ConstBytes bytes) {
+ protos::pbzero::RegisterSqlPackageArgs::Decoder args(bytes);
+ SqlPackage package;
+ package.name = args.package_name().ToStdString();
+ package.allow_override = args.allow_override();
for (auto it = args.modules(); it; ++it) {
- protos::pbzero::RegisterSqlModuleArgs::Module::Decoder m(*it);
- package.files.emplace_back(m.name().ToStdString(), m.sql().ToStdString());
+ protos::pbzero::RegisterSqlPackageArgs::Module::Decoder m(*it);
+ package.modules.emplace_back(m.name().ToStdString(), m.sql().ToStdString());
}
- return trace_processor_->RegisterSqlModule(package);
+ return trace_processor_->RegisterSqlPackage(package);
}
void Rpc::MaybePrintProgress() {
diff --git a/src/trace_processor/rpc/rpc.h b/src/trace_processor/rpc/rpc.h
index 96bbfbc..fe28748 100644
--- a/src/trace_processor/rpc/rpc.h
+++ b/src/trace_processor/rpc/rpc.h
@@ -131,7 +131,7 @@
private:
void ParseRpcRequest(const uint8_t*, size_t);
void ResetTraceProcessor(const uint8_t*, size_t);
- base::Status RegisterSqlModule(protozero::ConstBytes);
+ base::Status RegisterSqlPackage(protozero::ConstBytes);
void ResetTraceProcessorInternal(const Config&);
void MaybePrintProgress();
Iterator QueryInternal(const uint8_t*, size_t);
diff --git a/src/trace_processor/sorter/BUILD.gn b/src/trace_processor/sorter/BUILD.gn
index eb2da54..327d80b 100644
--- a/src/trace_processor/sorter/BUILD.gn
+++ b/src/trace_processor/sorter/BUILD.gn
@@ -30,6 +30,7 @@
"../../../include/perfetto/trace_processor:storage",
"../../base",
"../importers/android_bugreport:android_log_event",
+ "../importers/art_method:art_method_event",
"../importers/common:parser_types",
"../importers/common:trace_parser_hdr",
"../importers/fuchsia:fuchsia_record",
diff --git a/src/trace_processor/sorter/trace_sorter.cc b/src/trace_processor/sorter/trace_sorter.cc
index e2fc178..0ee3ef7 100644
--- a/src/trace_processor/sorter/trace_sorter.cc
+++ b/src/trace_processor/sorter/trace_sorter.cc
@@ -28,6 +28,7 @@
#include "perfetto/public/compiler.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/importers/android_bugreport/android_log_event.h"
+#include "src/trace_processor/importers/art_method/art_method_event.h"
#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_record.h"
@@ -267,6 +268,10 @@
context.gecko_trace_parser->ParseGeckoEvent(
event.ts, token_buffer_.Extract<gecko_importer::GeckoEvent>(id));
return;
+ case TimestampedEvent::Type::kArtMethodEvent:
+ context.art_method_parser->ParseArtMethodEvent(
+ event.ts, token_buffer_.Extract<art_method::ArtMethodEvent>(id));
+ return;
case TimestampedEvent::Type::kInlineSchedSwitch:
case TimestampedEvent::Type::kInlineSchedWaking:
case TimestampedEvent::Type::kEtwEvent:
@@ -300,6 +305,7 @@
case TimestampedEvent::Type::kAndroidLogEvent:
case TimestampedEvent::Type::kLegacyV8CpuProfileEvent:
case TimestampedEvent::Type::kGeckoEvent:
+ case TimestampedEvent::Type::kArtMethodEvent:
PERFETTO_FATAL("Invalid event type");
}
PERFETTO_FATAL("For GCC");
@@ -335,6 +341,7 @@
case TimestampedEvent::Type::kAndroidLogEvent:
case TimestampedEvent::Type::kLegacyV8CpuProfileEvent:
case TimestampedEvent::Type::kGeckoEvent:
+ case TimestampedEvent::Type::kArtMethodEvent:
PERFETTO_FATAL("Invalid event type");
}
PERFETTO_FATAL("For GCC");
@@ -389,6 +396,10 @@
base::ignore_result(
token_buffer_.Extract<gecko_importer::GeckoEvent>(id));
return;
+ case TimestampedEvent::Type::kArtMethodEvent:
+ base::ignore_result(
+ token_buffer_.Extract<art_method::ArtMethodEvent>(id));
+ return;
}
PERFETTO_FATAL("For GCC");
}
diff --git a/src/trace_processor/sorter/trace_sorter.h b/src/trace_processor/sorter/trace_sorter.h
index 1ff3d18..4cb7951 100644
--- a/src/trace_processor/sorter/trace_sorter.h
+++ b/src/trace_processor/sorter/trace_sorter.h
@@ -35,6 +35,7 @@
#include "perfetto/trace_processor/ref_counted.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/importers/android_bugreport/android_log_event.h"
+#include "src/trace_processor/importers/art_method/art_method_event.h"
#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_record.h"
@@ -247,11 +248,18 @@
}
inline void PushGeckoEvent(int64_t timestamp,
- gecko_importer::GeckoEvent event) {
- TraceTokenBuffer::Id id = token_buffer_.Append(std::move(event));
+ const gecko_importer::GeckoEvent& event) {
+ TraceTokenBuffer::Id id = token_buffer_.Append(event);
AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kGeckoEvent, id);
}
+ inline void PushArtMethodEvent(int64_t timestamp,
+ const art_method::ArtMethodEvent& event) {
+ TraceTokenBuffer::Id id = token_buffer_.Append(event);
+ AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kArtMethodEvent,
+ id);
+ }
+
inline void PushInlineFtraceEvent(
uint32_t cpu,
int64_t timestamp,
@@ -333,11 +341,12 @@
kTracePacket,
kTrackEvent,
kGeckoEvent,
+ kArtMethodEvent,
kMax = kGeckoEvent,
};
// Number of bits required to store the max element in |Type|.
- static constexpr uint32_t kMaxTypeBits = 4;
+ static constexpr uint32_t kMaxTypeBits = 6;
static_assert(static_cast<uint8_t>(Type::kMax) <= (1 << kMaxTypeBits),
"Max type does not fit inside storage");
diff --git a/src/trace_processor/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc
index 52852cd..d2d53f3 100644
--- a/src/trace_processor/trace_database_integrationtest.cc
+++ b/src/trace_processor/trace_database_integrationtest.cc
@@ -767,11 +767,11 @@
}
TEST_F(TraceProcessorIntegrationTest, ErrorMessageModule) {
- SqlModule module;
+ SqlPackage module;
module.name = "foo";
- module.files.push_back(std::make_pair("foo.bar", "select t from slice"));
+ module.modules.push_back(std::make_pair("foo.bar", "select t from slice"));
- ASSERT_TRUE(Processor()->RegisterSqlModule(module).ok());
+ ASSERT_TRUE(Processor()->RegisterSqlPackage(module).ok());
auto it = Query("include perfetto module foo.bar;");
ASSERT_FALSE(it.Next());
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 5a60433..b7f013c 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -46,6 +46,8 @@
#include "perfetto/trace_processor/trace_processor.h"
#include "src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.h"
#include "src/trace_processor/importers/android_bugreport/android_log_reader.h"
+#include "src/trace_processor/importers/art_method/art_method_parser_impl.h"
+#include "src/trace_processor/importers/art_method/art_method_tokenizer.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/trace_file_tracker.h"
#include "src/trace_processor/importers/common/trace_parser.h"
@@ -442,6 +444,11 @@
#endif
}
+ context_.reader_registry->RegisterTraceReader<art_method::ArtMethodTokenizer>(
+ kArtMethodTraceType);
+ context_.art_method_parser =
+ std::make_unique<art_method::ArtMethodParserImpl>(&context_);
+
if (context_.config.analyze_trace_proto_content) {
context_.content_analyzer =
std::make_unique<ProtoContentAnalyzer>(&context_);
@@ -586,10 +593,10 @@
return field_idx != nullptr;
}
-base::Status TraceProcessorImpl::RegisterSqlModule(SqlModule sql_package) {
+base::Status TraceProcessorImpl::RegisterSqlPackage(SqlPackage sql_package) {
sql_modules::RegisteredPackage new_package;
std::string name = sql_package.name;
- if (engine_->FindPackage(name) && !sql_package.allow_module_override) {
+ if (engine_->FindPackage(name) && !sql_package.allow_override) {
return base::ErrStatus(
"Package '%s' is already registered. Choose a different name.\n"
"If you want to replace the existing package using trace processor "
@@ -598,7 +605,7 @@
"to pass the module path.",
name.c_str());
}
- for (auto const& module_name_and_sql : sql_package.files) {
+ for (auto const& module_name_and_sql : sql_package.modules) {
if (sql_modules::GetPackageName(module_name_and_sql.first) != name) {
return base::ErrStatus(
"Module name doesn't match the package name. First part of module "
@@ -608,7 +615,7 @@
new_package.modules.Insert(module_name_and_sql.first,
{module_name_and_sql.second, false});
}
- manually_registered_sql_packages_.push_back(SqlModule(sql_package));
+ manually_registered_sql_packages_.push_back(SqlPackage(sql_package));
engine_->RegisterPackage(name, std::move(new_package));
return base::OkStatus();
}
@@ -861,8 +868,8 @@
auto packages = GetStdlibPackages();
for (auto package = packages.GetIterator(); package; ++package) {
base::Status status =
- RegisterSqlModule({/*name=*/package.key(), /*modules=*/package.value(),
- /*allow_package_override=*/false});
+ RegisterSqlPackage({/*name=*/package.key(), /*modules=*/package.value(),
+ /*allow_override=*/false});
if (!status.ok())
PERFETTO_ELOG("%s", status.c_message());
}
@@ -1088,7 +1095,7 @@
// Reregister manually added stdlib packages.
for (const auto& package : manually_registered_sql_packages_) {
- RegisterSqlModule(package);
+ RegisterSqlPackage(package);
}
}
diff --git a/src/trace_processor/trace_processor_impl.h b/src/trace_processor/trace_processor_impl.h
index 473eefd..8f95290 100644
--- a/src/trace_processor/trace_processor_impl.h
+++ b/src/trace_processor/trace_processor_impl.h
@@ -67,7 +67,7 @@
base::Status RegisterMetric(const std::string& path,
const std::string& sql) override;
- base::Status RegisterSqlModule(SqlModule) override;
+ base::Status RegisterSqlPackage(SqlPackage) override;
base::Status ExtendMetricsProto(const uint8_t* data, size_t size) override;
@@ -97,6 +97,15 @@
base::Status DisableAndReadMetatrace(
std::vector<uint8_t>* trace_proto) override;
+ base::Status RegisterSqlModule(SqlModule module) override {
+ SqlPackage package;
+ package.name = std::move(module.name);
+ package.modules = std::move(module.files);
+ package.allow_override = module.allow_module_override;
+
+ return RegisterSqlPackage(package);
+ }
+
private:
// Needed for iterators to be able to access the context.
friend class IteratorImpl;
@@ -120,7 +129,7 @@
// Manually registeres SQL packages are stored here, to be able to restore
// them when running |RestoreInitialTables()|.
- std::vector<SqlModule> manually_registered_sql_packages_;
+ std::vector<SqlPackage> manually_registered_sql_packages_;
std::unordered_map<std::string, std::string> proto_field_to_sql_metric_path_;
std::unordered_map<std::string, std::string> proto_fn_name_to_path_;
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 77f0f53..42fc979 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -1256,9 +1256,9 @@
.first->push_back({import_key, file_contents});
}
for (auto module_it = modules.GetIterator(); module_it; ++module_it) {
- auto status = g_tp->RegisterSqlModule(
- {/*name=*/module_it.key(), /*files=*/module_it.value(),
- /*allow_module_override=*/allow_override});
+ auto status = g_tp->RegisterSqlPackage({/*name=*/module_it.key(),
+ /*files=*/module_it.value(),
+ /*allow_override=*/allow_override});
if (!status.ok())
return status;
}
@@ -1294,9 +1294,9 @@
.first->push_back({module_name, module_file});
}
for (auto package = packages.GetIterator(); package; ++package) {
- g_tp->RegisterSqlModule({/*name=*/package.key(),
- /*files=*/package.value(),
- /*allow_module_override=*/true});
+ g_tp->RegisterSqlPackage({/*name=*/package.key(),
+ /*files=*/package.value(),
+ /*allow_override=*/true});
}
return base::OkStatus();
diff --git a/src/trace_processor/trace_reader_registry.cc b/src/trace_processor/trace_reader_registry.cc
index 4b1b6c3..30f5205 100644
--- a/src/trace_processor/trace_reader_registry.cc
+++ b/src/trace_processor/trace_reader_registry.cc
@@ -52,6 +52,7 @@
case kAndroidLogcatTraceType:
case kAndroidDumpstateTraceType:
case kGeckoTraceType:
+ case kArtMethodTraceType:
return false;
}
PERFETTO_FATAL("For GCC");
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index 84682eb..7a84bb9 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -31,6 +31,7 @@
class AndroidLogEventParser;
class ArgsTracker;
class ArgsTranslationTable;
+class ArtMethodParser;
class AsyncTrackSetTracker;
class ChunkedTraceReader;
class ClockConverter;
@@ -175,6 +176,7 @@
std::unique_ptr<InstrumentsRowParser> instruments_row_parser;
std::unique_ptr<AndroidLogEventParser> android_log_event_parser;
std::unique_ptr<GeckoTraceParser> gecko_trace_parser;
+ std::unique_ptr<ArtMethodParser> art_method_parser;
// This field contains the list of proto descriptors that can be used by
// reflection-based parsers.
diff --git a/src/trace_processor/util/bump_allocator.h b/src/trace_processor/util/bump_allocator.h
index 985b5bc..9e92471 100644
--- a/src/trace_processor/util/bump_allocator.h
+++ b/src/trace_processor/util/bump_allocator.h
@@ -54,7 +54,7 @@
public:
// The limit on the total number of bits which can be used to represent
// the chunk id.
- static constexpr uint64_t kMaxIdBits = 60;
+ static constexpr uint64_t kMaxIdBits = 58;
// The limit on the total amount of memory which can be allocated.
static constexpr uint64_t kAllocLimit = 1ull << kMaxIdBits;
diff --git a/src/trace_processor/util/trace_blob_view_reader.h b/src/trace_processor/util/trace_blob_view_reader.h
index 69e5aa3..158881f 100644
--- a/src/trace_processor/util/trace_blob_view_reader.h
+++ b/src/trace_processor/util/trace_blob_view_reader.h
@@ -20,6 +20,7 @@
#include <cstddef>
#include <cstdint>
#include <cstdio>
+#include <cstring>
#include <optional>
#include "perfetto/base/logging.h"
@@ -46,12 +47,56 @@
public:
class Iterator {
public:
- Iterator(const Iterator&) = default;
+ ~Iterator() = default;
+
+ Iterator(const Iterator&) = delete;
+ Iterator& operator=(const Iterator&) = delete;
+
Iterator(Iterator&&) = default;
- Iterator& operator=(const Iterator&) = default;
Iterator& operator=(Iterator&&) = default;
- ~Iterator() = default;
+ // Tries to advance the iterator |size| bytes forward. Returns true if
+ // the advance was successful and false if it would overflow the iterator.
+ // If false is returned, the state of the iterator is not changed.
+ bool MaybeAdvance(size_t delta) {
+ file_offset_ += delta;
+ if (PERFETTO_LIKELY(file_offset_ < iter_->end_offset())) {
+ return true;
+ }
+ if (file_offset_ == end_offset_) {
+ return true;
+ }
+ if (file_offset_ > end_offset_) {
+ file_offset_ -= delta;
+ return false;
+ }
+ do {
+ ++iter_;
+ } while (file_offset_ >= iter_->end_offset());
+ return true;
+ }
+
+ // Tries to find a byte equal to |chr| in the iterator and, if found,
+ // advance to it. Returns true if the byte was found and could be advanced
+ // to and false if no such byte was found before the end of the iterator. If
+ // false is returned, the state of the iterator is not changed.
+ bool MaybeFindAndAdvance(uint8_t chr) {
+ size_t off = file_offset_;
+ while (off < end_offset_) {
+ size_t iter_off = off - iter_->start_offset;
+ size_t iter_rem = iter_->data.size() - iter_off;
+ const auto* p = reinterpret_cast<const uint8_t*>(
+ memchr(iter_->data.data() + iter_off, chr, iter_rem));
+ if (p) {
+ file_offset_ =
+ iter_->start_offset + static_cast<size_t>(p - iter_->data.data());
+ return true;
+ }
+ off = iter_->end_offset();
+ ++iter_;
+ }
+ return false;
+ }
uint8_t operator*() const {
PERFETTO_DCHECK(file_offset_ < iter_->end_offset());
@@ -62,45 +107,20 @@
size_t file_offset() const { return file_offset_; }
- bool MaybeAdvance(size_t delta) {
- if (delta == 0) {
- return true;
- }
- if (delta > end_offset_ - file_offset_) {
- return false;
- }
- file_offset_ += delta;
- if (PERFETTO_LIKELY(file_offset_ < iter_->end_offset())) {
- return true;
- }
- while (file_offset_ > iter_->end_offset()) {
- ++iter_;
- }
- if (file_offset_ == iter_->end_offset()) {
- ++iter_;
- }
-
- return true;
- }
-
private:
friend TraceBlobViewReader;
Iterator(base::CircularQueue<Entry>::Iterator iter,
size_t file_offset,
size_t end_offset)
- : iter_(std::move(iter)),
- file_offset_(file_offset),
- end_offset_(end_offset) {}
+ : iter_(iter), file_offset_(file_offset), end_offset_(end_offset) {}
+
base::CircularQueue<Entry>::Iterator iter_;
size_t file_offset_;
size_t end_offset_;
};
- Iterator begin() const {
- return Iterator(data_.begin(), start_offset(), end_offset());
- }
- Iterator end() const {
- return Iterator(data_.end(), end_offset(), end_offset());
+ Iterator GetIterator() const {
+ return {data_.begin(), start_offset(), end_offset()};
}
// Adds a `TraceBlobView` at the back.
@@ -129,6 +149,7 @@
//
// NOTE: If `offset` < 'file_offset()' this method will CHECK fail.
std::optional<TraceBlobView> SliceOff(size_t offset, size_t length) const;
+
// Returns the offset to the start of the available data.
size_t start_offset() const {
return data_.empty() ? end_offset_ : data_.front().start_offset;
diff --git a/src/trace_processor/util/trace_type.cc b/src/trace_processor/util/trace_type.cc
index 62f586f..e5690bb 100644
--- a/src/trace_processor/util/trace_type.cc
+++ b/src/trace_processor/util/trace_type.cc
@@ -37,10 +37,9 @@
constexpr char kFuchsiaMagic[] = {'\x10', '\x00', '\x04', '\x46',
'\x78', '\x54', '\x16', '\x00'};
constexpr char kPerfMagic[] = {'P', 'E', 'R', 'F', 'I', 'L', 'E', '2'};
-
constexpr char kZipMagic[] = {'P', 'K', '\x03', '\x04'};
-
constexpr char kGzipMagic[] = {'\x1f', '\x8b'};
+constexpr char kArtMethodStreamingMagic[] = {'S', 'L', 'O', 'W'};
constexpr uint8_t kTracePacketTag =
protozero::proto_utils::MakeTagLengthDelimited(
@@ -130,6 +129,8 @@
return "android_bugreport";
case kGeckoTraceType:
return "gecko";
+ case kArtMethodTraceType:
+ return "art_method";
case kUnknownTraceType:
return "unknown";
}
@@ -157,6 +158,10 @@
return kGzipTraceType;
}
+ if (MatchesMagic(data, size, kArtMethodStreamingMagic)) {
+ return kArtMethodTraceType;
+ }
+
std::string start(reinterpret_cast<const char*>(data),
std::min<size_t>(size, kGuessTraceMaxLookahead));
@@ -168,6 +173,10 @@
if (base::StartsWith(start_minus_white_space, "[{\""))
return kJsonTraceType;
+ // ART method traces (non-streaming).
+ if (base::StartsWith(start, "*version\n"))
+ return kArtMethodTraceType;
+
// Systrace with header but no leading HTML.
if (base::Contains(start, "# tracer"))
return kSystraceTraceType;
diff --git a/src/trace_processor/util/trace_type.h b/src/trace_processor/util/trace_type.h
index 963e8d8..5a29c61 100644
--- a/src/trace_processor/util/trace_type.h
+++ b/src/trace_processor/util/trace_type.h
@@ -39,6 +39,7 @@
kZipFile,
kInstrumentsXmlTraceType,
kGeckoTraceType,
+ kArtMethodTraceType,
};
constexpr size_t kGuessTraceMaxLookahead = 64;
diff --git a/test/data/art-method-tracing.trace.sha256 b/test/data/art-method-tracing.trace.sha256
new file mode 100644
index 0000000..38910a6
--- /dev/null
+++ b/test/data/art-method-tracing.trace.sha256
@@ -0,0 +1 @@
+ebd46d41eaa4656ad06535dacc1d3c6f6018a180f89c546515fed4f7e1df2337
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index c9e7a10..f8183da 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -60,6 +60,7 @@
from diff_tests.parser.android.tests_surfaceflinger_transactions import SurfaceFlingerTransactions
from diff_tests.parser.android.tests_viewcapture import ViewCapture
from diff_tests.parser.android.tests_windowmanager import WindowManager
+from diff_tests.parser.art_method.tests import ArtMethodParser
from diff_tests.parser.atrace.tests import Atrace
from diff_tests.parser.atrace.tests_error_handling import AtraceErrorHandling
from diff_tests.parser.chrome.tests import ChromeParser
@@ -67,9 +68,9 @@
from diff_tests.parser.chrome.tests_v8 import ChromeV8Parser
from diff_tests.parser.cros.tests import Cros
from diff_tests.parser.fs.tests import Fs
-from diff_tests.parser.gecko.tests import GeckoParser
from diff_tests.parser.ftrace.ftrace_crop_tests import FtraceCrop
from diff_tests.parser.fuchsia.tests import Fuchsia
+from diff_tests.parser.gecko.tests import GeckoParser
from diff_tests.parser.graphics.tests import GraphicsParser
from diff_tests.parser.graphics.tests_drm_related_ftrace_events import GraphicsDrmRelatedFtraceEvents
from diff_tests.parser.graphics.tests_gpu_trace import GraphicsGpuTrace
@@ -241,7 +242,9 @@
'AndroidInputEvent').fetch(),
*Instruments(index_path, 'parser/instruments', 'Instruments').fetch(),
*Gzip(index_path, 'parser/gzip', 'Gzip').fetch(),
- *GeckoParser(index_path, 'parser/gecko', 'Gecko').fetch(),
+ *GeckoParser(index_path, 'parser/gecko', 'GeckoParser').fetch(),
+ *ArtMethodParser(index_path, 'parser/art_method',
+ 'ArtMethodParser').fetch(),
]
metrics_tests = [
diff --git a/test/trace_processor/diff_tests/parser/art_method/tests.py b/test/trace_processor/diff_tests/parser/art_method/tests.py
new file mode 100644
index 0000000..4074b0f
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/art_method/tests.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+# Copyright (C) 2023 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 a
+#
+# 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.
+
+from python.generators.diff_tests.testing import DataPath
+from python.generators.diff_tests.testing import Csv
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class ArtMethodParser(TestSuite):
+
+ def test_art_method_smoke(self):
+ return DiffTestBlueprint(
+ trace=DataPath('art-method-tracing.trace'),
+ query="""
+ SELECT ts, dur, name, extract_arg(arg_set_id, 'pathname') AS pathname
+ FROM slice
+ LIMIT 10
+ """,
+ out=Csv('''
+ "ts","dur","name","pathname"
+ 430421819465000,-1,"com.android.internal.os.ZygoteInit.main: ([Ljava/lang/String;)V","ZygoteInit.java"
+ 430421819468000,-1,"com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run: ()V","RuntimeInit.java"
+ 430421819469000,-1,"java.lang.reflect.Method.invoke: (Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;","Method.java"
+ 430421819472000,-1,"android.app.ActivityThread.main: ([Ljava/lang/String;)V","ActivityThread.java"
+ 430421819473000,-1,"android.os.Looper.loop: ()V","Looper.java"
+ 430421819473000,-1,"android.os.Looper.loopOnce: (Landroid/os/Looper;JI)Z","Looper.java"
+ 430421819475000,-1,"android.os.MessageQueue.next: ()Landroid/os/Message;","MessageQueue.java"
+ 430421819476000,-1,"android.os.MessageQueue.nativePollOnce: (JI)V","MessageQueue.java"
+ 430421819490000,-1,"java.lang.Thread.run: ()V","Thread.java"
+ 430421819508000,-1,"java.lang.Daemons$Daemon.run: ()V","Daemons.java"
+ '''))
diff --git a/ui/src/base/utils.ts b/ui/src/base/utils.ts
index f2e90a5..b0c0fc6 100644
--- a/ui/src/base/utils.ts
+++ b/ui/src/base/utils.ts
@@ -36,3 +36,22 @@
// Make field K required in T
export type RequiredField<T, K extends keyof T> = Omit<T, K> &
Required<Pick<T, K>>;
+
+// The lowest common denoninator between Map<> and WeakMap<>.
+// This is just to avoid duplication of the getOrCreate below.
+interface MapLike<K, V> {
+ get(key: K): V | undefined;
+ set(key: K, value: V): this;
+}
+
+export function getOrCreate<K, V>(
+ map: MapLike<K, V>,
+ key: K,
+ factory: () => V,
+): V {
+ let value = map.get(key);
+ if (value !== undefined) return value;
+ value = factory();
+ map.set(key, value);
+ return value;
+}
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index c4ca69c..3cef09d 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -47,7 +47,6 @@
return {
version: STATE_VERSION,
nextId: '-1',
- queries: {},
recordConfig: AUTOLOAD_STARTED_CONFIG_FLAG.get()
? autosaveConfigStore.get()
@@ -55,8 +54,6 @@
displayConfigAsPbtxt: false,
lastLoadedConfig: {type: 'NONE'},
- traceConversionInProgress: false,
-
perfDebug: false,
sidebarVisible: true,
hoveredUtid: -1,
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 6173528..38cab82 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -174,8 +174,6 @@
debugTrackId?: string;
lastTrackReloadRequest?: number;
- queries: ObjectById<QueryConfig>;
- traceConversionInProgress: boolean;
flamegraphModalDismissed: boolean;
// Show track perf debugging overlay
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 891cbad..037b63a 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -13,21 +13,19 @@
// limitations under the License.
import {assertExists, assertTrue} from '../base/logging';
-import {Duration, time, Time, TimeSpan} from '../base/time';
+import {time, Time, TimeSpan} from '../base/time';
import {Actions} from '../common/actions';
import {cacheTrace} from '../common/cache_manager';
import {
getEnabledMetatracingCategories,
isMetatracingEnabled,
} from '../common/metatracing';
-import {EngineConfig, PendingDeeplinkState} from '../common/state';
+import {EngineConfig} from '../common/state';
import {featureFlags, Flag} from '../core/feature_flags';
-import {globals, QuantizedLoad, ThreadDesc} from '../frontend/globals';
+import {globals, ThreadDesc} from '../frontend/globals';
import {
- clearOverviewData,
publishHasFtrace,
publishMetricError,
- publishOverviewData,
publishThreads,
} from '../frontend/publish';
import {addQueryResultsTab} from '../public/lib/query_table/query_result_tab';
@@ -62,6 +60,7 @@
import {TraceImpl} from '../core/trace_impl';
import {SerializedAppState} from '../public/state_serialization_schema';
import {TraceSource} from '../public/trace_source';
+import {RouteArgs} from '../core/route_schema';
type States = 'init' | 'loading_trace' | 'ready';
@@ -299,7 +298,7 @@
await engine.restoreInitialTables();
}
for (const p of globals.extraSqlPackages) {
- await engine.registerSqlModules(p);
+ await engine.registerSqlPackages(p);
}
const traceDetails = await getTraceInfo(engine, traceSource);
@@ -338,10 +337,6 @@
decideTabs(trace);
await listThreads(engine);
- await loadTimelineOverview(
- engine,
- new TimeSpan(traceDetails.start, traceDetails.end),
- );
{
// Check if we have any ftrace events at all
@@ -357,7 +352,7 @@
const pendingDeeplink = AppImpl.instance.getAndClearInitialRouteArgs();
if (pendingDeeplink !== undefined) {
- await selectPendingDeeplink(trace, pendingDeeplink);
+ await selectInitialRouteArgs(trace, pendingDeeplink);
if (
pendingDeeplink.visStart !== undefined &&
pendingDeeplink.visEnd !== undefined
@@ -405,12 +400,9 @@
return trace;
}
-async function selectPendingDeeplink(
- trace: TraceImpl,
- link: PendingDeeplinkState,
-) {
+async function selectInitialRouteArgs(trace: TraceImpl, args: RouteArgs) {
const conditions = [];
- const {ts, dur} = link;
+ const {ts, dur} = args;
if (ts !== undefined) {
conditions.push(`ts = ${ts}`);
@@ -498,86 +490,6 @@
publishThreads(threads);
}
-async function loadTimelineOverview(engine: Engine, trace: TimeSpan) {
- clearOverviewData();
- const stepSize = Duration.max(1n, trace.duration / 100n);
- const hasSchedSql = 'select ts from sched limit 1';
- const hasSchedOverview = (await engine.query(hasSchedSql)).numRows() > 0;
- if (hasSchedOverview) {
- const stepPromises = [];
- for (
- let start = trace.start;
- start < trace.end;
- start = Time.add(start, stepSize)
- ) {
- const progress = start - trace.start;
- const ratio = Number(progress) / Number(trace.duration);
- updateStatus('Loading overview ' + `${Math.round(ratio * 100)}%`);
- const end = Time.add(start, stepSize);
- // The (async() => {})() queues all the 100 async promises in one batch.
- // Without that, we would wait for each step to be rendered before
- // kicking off the next one. That would interleave an animation frame
- // between each step, slowing down significantly the overall process.
- stepPromises.push(
- (async () => {
- const schedResult = await engine.query(
- `select cast(sum(dur) as float)/${stepSize} as load, cpu from sched ` +
- `where ts >= ${start} and ts < ${end} and utid != 0 ` +
- 'group by cpu order by cpu',
- );
- const schedData: {[key: string]: QuantizedLoad} = {};
- const it = schedResult.iter({load: NUM, cpu: NUM});
- for (; it.valid(); it.next()) {
- const load = it.load;
- const cpu = it.cpu;
- schedData[cpu] = {start, end, load};
- }
- publishOverviewData(schedData);
- })(),
- );
- } // for(start = ...)
- await Promise.all(stepPromises);
- return;
- } // if (hasSchedOverview)
-
- // Slices overview.
- const sliceResult = await engine.query(`select
- bucket,
- upid,
- ifnull(sum(utid_sum) / cast(${stepSize} as float), 0) as load
- from thread
- inner join (
- select
- ifnull(cast((ts - ${trace.start})/${stepSize} as int), 0) as bucket,
- sum(dur) as utid_sum,
- utid
- from slice
- inner join thread_track on slice.track_id = thread_track.id
- group by bucket, utid
- ) using(utid)
- where upid is not null
- group by bucket, upid`);
-
- const slicesData: {[key: string]: QuantizedLoad[]} = {};
- const it = sliceResult.iter({bucket: LONG, upid: NUM, load: NUM});
- for (; it.valid(); it.next()) {
- const bucket = it.bucket;
- const upid = it.upid;
- const load = it.load;
-
- const start = Time.add(trace.start, stepSize * bucket);
- const end = Time.add(start, stepSize);
-
- const upidStr = upid.toString();
- let loadArray = slicesData[upidStr];
- if (loadArray === undefined) {
- loadArray = slicesData[upidStr] = [];
- }
- loadArray.push({start, end, load});
- }
- publishOverviewData(slicesData);
-}
-
async function initialiseHelperViews(engine: Engine) {
updateStatus('Creating annotation counter track table');
// Create the helper tables for all the annotations related data.
diff --git a/ui/src/core/selection_manager.ts b/ui/src/core/selection_manager.ts
index 1f930d7..4fd43c2 100644
--- a/ui/src/core/selection_manager.ts
+++ b/ui/src/core/selection_manager.ts
@@ -15,18 +15,13 @@
import {assertTrue, assertUnreachable} from '../base/logging';
import {
Selection,
- LegacySelection,
Area,
SelectionOpts,
SelectionManager,
AreaSelectionAggregator,
SqlSelectionResolver,
} from '../public/selection';
-import {duration, Time, time, TimeSpan} from '../base/time';
-import {
- GenericSliceDetailsTabConfig,
- GenericSliceDetailsTabConfigBase,
-} from '../public/details_panel';
+import {TimeSpan} from '../base/time';
import {raf} from './raf_scheduler';
import {exists, Optional} from '../base/utils';
import {TrackManagerImpl} from './track_manager';
@@ -49,7 +44,6 @@
// requires querying the SQL engine, which is an async operation.
export class SelectionManagerImpl implements SelectionManager {
private _selection: Selection = {kind: 'empty'};
- private _selectedDetails?: LegacySelectionDetails;
private _aggregationManager: SelectionAggregationManager;
// Incremented every time _selection changes.
private readonly selectionResolvers = new Array<SqlSelectionResolver>();
@@ -177,62 +171,10 @@
});
}
- // There is no matching addLegacy as we did not support multi-single
- // selection with the legacy selection system.
- selectLegacy(legacySelection: LegacySelection, opts?: SelectionOpts): void {
- this.setSelection(
- {
- kind: 'legacy',
- legacySelection,
- },
- opts,
- );
- }
-
- selectGenericSlice(args: {
- id: number;
- sqlTableName: string;
- start: time;
- duration: duration;
- trackUri: string;
- detailsPanelConfig: {
- kind: string;
- config: GenericSliceDetailsTabConfigBase;
- };
- }): void {
- const detailsPanelConfig: GenericSliceDetailsTabConfig = {
- id: args.id,
- ...args.detailsPanelConfig.config,
- };
- this.setSelection({
- kind: 'legacy',
- legacySelection: {
- kind: 'GENERIC_SLICE',
- id: args.id,
- sqlTableName: args.sqlTableName,
- start: args.start,
- duration: args.duration,
- trackUri: args.trackUri,
- detailsPanelConfig: {
- kind: args.detailsPanelConfig.kind,
- config: detailsPanelConfig,
- },
- },
- });
- }
-
get selection(): Selection {
return this._selection;
}
- get legacySelection(): LegacySelection | null {
- return toLegacySelection(this._selection);
- }
-
- get legacySelectionDetails(): LegacySelectionDetails | undefined {
- return this._selectedDetails;
- }
-
registerSqlSelectionResolver(resolver: SqlSelectionResolver): void {
this.selectionResolvers.push(resolver);
}
@@ -321,19 +263,37 @@
switch (this.selection.kind) {
case 'track_event':
return this.selection.trackUri;
- case 'legacy':
- return this.selection.legacySelection.trackUri;
+ // TODO(stevegolton): Handle scrolling to area and note selections.
default:
return undefined;
}
})();
- const range = this.findTimeRangeOfSelection();
+ const range = this.findFocusRangeOfSelection();
this.scrollHelper.scrollTo({
time: range ? {...range} : undefined,
track: uri ? {uri: uri, expandGroup: true} : undefined,
});
}
+ // Finds the time range range that we should actually focus on - using dummy
+ // values for instant and incomplete slices, so we don't end up super zoomed
+ // in.
+ private findFocusRangeOfSelection(): Optional<TimeSpan> {
+ const sel = this.selection;
+ if (sel.kind === 'track_event') {
+ // The focus range of slices is different to that of the actual span
+ if (sel.dur === -1n) {
+ return TimeSpan.fromTimeAndDuration(sel.ts, INCOMPLETE_SLICE_DURATION);
+ } else if (sel.dur === 0n) {
+ return TimeSpan.fromTimeAndDuration(sel.ts, INSTANT_FOCUS_DURATION);
+ } else {
+ return TimeSpan.fromTimeAndDuration(sel.ts, sel.dur);
+ }
+ } else {
+ return this.findTimeRangeOfSelection();
+ }
+ }
+
findTimeRangeOfSelection(): Optional<TimeSpan> {
const sel = this.selection;
if (sel.kind === 'area') {
@@ -358,18 +318,6 @@
return TimeSpan.fromTimeAndDuration(sel.ts, sel.dur);
}
- const legacySel = this.legacySelection;
- if (!exists(legacySel)) {
- return undefined;
- }
-
- if (legacySel.kind === 'GENERIC_SLICE') {
- return findTimeRangeOfSlice({
- ts: legacySel.start,
- dur: legacySel.duration,
- });
- }
-
return undefined;
}
@@ -377,51 +325,3 @@
return this._aggregationManager;
}
}
-
-function toLegacySelection(selection: Selection): LegacySelection | null {
- switch (selection.kind) {
- case 'area':
- case 'track_event':
- case 'empty':
- case 'note':
- return null;
- case 'union':
- for (const child of selection.selections) {
- const result = toLegacySelection(child);
- if (result !== null) {
- return result;
- }
- }
- return null;
- case 'legacy':
- return selection.legacySelection;
- default:
- assertUnreachable(selection);
- return null;
- }
-}
-
-// Returns the start and end points of a slice-like object If slice is instant
-// or incomplete, dummy time will be returned which instead.
-function findTimeRangeOfSlice(slice: {ts?: time; dur?: duration}): TimeSpan {
- if (exists(slice.ts) && exists(slice.dur)) {
- if (slice.dur === -1n) {
- return TimeSpan.fromTimeAndDuration(slice.ts, INCOMPLETE_SLICE_DURATION);
- } else if (slice.dur === 0n) {
- return TimeSpan.fromTimeAndDuration(slice.ts, INSTANT_FOCUS_DURATION);
- } else {
- return TimeSpan.fromTimeAndDuration(slice.ts, slice.dur);
- }
- } else {
- // TODO(primiano): unclear why we dont return undefined here.
- return new TimeSpan(Time.INVALID, Time.INVALID);
- }
-}
-
-export interface LegacySelectionDetails {
- ts?: time;
- dur?: duration;
- // Additional information for sched selection, used to draw the wakeup arrow.
- wakeupTs?: time;
- wakerCpu?: number;
-}
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts b/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts
index 3f5888a..6e6b909 100644
--- a/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts
+++ b/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts
@@ -12,20 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {OnSliceClickArgs} from '../../frontend/base_slice_track';
-import {GenericSliceDetailsTab} from '../../frontend/generic_slice_details_tab';
import {NAMED_ROW} from '../../frontend/named_slice_track';
-import {NUM, STR} from '../../trace_processor/query_result';
+import {LONG, NUM, STR} from '../../trace_processor/query_result';
import {Slice} from '../../public/track';
import {
- CustomSqlDetailsPanelConfig,
CustomSqlImportConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../../frontend/tracks/custom_sql_table_slice_track';
-import {PageLoadDetailsPanel} from './page_load_details_panel';
-import {StartupDetailsPanel} from './startup_details_panel';
-import {WebContentInteractionPanel} from './web_content_interaction_details_panel';
+import {TrackEventDetails} from '../../public/selection';
+import {Duration, Time} from '../../base/time';
export const CRITICAL_USER_INTERACTIONS_KIND =
'org.chromium.CriticalUserInteraction.track';
@@ -42,28 +38,6 @@
type: string;
}
-enum CriticalUserInteractionType {
- UNKNOWN = 'Unknown',
- PAGE_LOAD = 'chrome_page_loads',
- STARTUP = 'chrome_startups',
- WEB_CONTENT_INTERACTION = 'chrome_web_content_interactions',
-}
-
-function convertToCriticalUserInteractionType(
- cujType: string,
-): CriticalUserInteractionType {
- switch (cujType) {
- case CriticalUserInteractionType.PAGE_LOAD:
- return CriticalUserInteractionType.PAGE_LOAD;
- case CriticalUserInteractionType.STARTUP:
- return CriticalUserInteractionType.STARTUP;
- case CriticalUserInteractionType.WEB_CONTENT_INTERACTION:
- return CriticalUserInteractionType.WEB_CONTENT_INTERACTION;
- default:
- return CriticalUserInteractionType.UNKNOWN;
- }
-}
-
export class CriticalUserInteractionTrack extends CustomSqlTableSliceTrack {
static readonly kind = `/critical_user_interactions`;
@@ -84,64 +58,34 @@
};
}
- getDetailsPanel(
- args: OnSliceClickArgs<CriticalUserInteractionSlice>,
- ): CustomSqlDetailsPanelConfig {
- let detailsPanel = {
- kind: GenericSliceDetailsTab.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Interaction',
- },
- };
+ async getSelectionDetails(
+ id: number,
+ ): Promise<TrackEventDetails | undefined> {
+ const query = `
+ SELECT
+ ts,
+ dur,
+ type
+ FROM (${this.getSqlSource()})
+ WHERE id = ${id}
+ `;
- switch (convertToCriticalUserInteractionType(args.slice.type)) {
- case CriticalUserInteractionType.PAGE_LOAD:
- detailsPanel = {
- kind: PageLoadDetailsPanel.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Page Load',
- },
- };
- break;
- case CriticalUserInteractionType.STARTUP:
- detailsPanel = {
- kind: StartupDetailsPanel.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Startup',
- },
- };
- break;
- case CriticalUserInteractionType.WEB_CONTENT_INTERACTION:
- detailsPanel = {
- kind: WebContentInteractionPanel.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Web Content Interaction',
- },
- };
- break;
- default:
- break;
+ const result = await this.engine.query(query);
+ if (result.numRows() === 0) {
+ return undefined;
}
- return detailsPanel;
- }
- onSliceClick(args: OnSliceClickArgs<CriticalUserInteractionSlice>) {
- const detailsPanelConfig = this.getDetailsPanel(args);
- this.trace.selection.selectGenericSlice({
- id: args.slice.scopedId,
- sqlTableName: this.tableName,
- start: args.slice.ts,
- duration: args.slice.dur,
- trackUri: this.uri,
- detailsPanelConfig: {
- kind: detailsPanelConfig.kind,
- config: detailsPanelConfig.config,
- },
+ const row = result.iter({
+ ts: LONG,
+ dur: LONG,
+ type: STR,
});
+
+ return {
+ ts: Time.fromRaw(row.ts),
+ dur: Duration.fromRaw(row.dur),
+ interactionType: row.type,
+ };
}
getSqlImports(): CustomSqlImportConfig {
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/index.ts b/ui/src/core_plugins/chrome_critical_user_interactions/index.ts
index 23560c9..1660f10 100644
--- a/ui/src/core_plugins/chrome_critical_user_interactions/index.ts
+++ b/ui/src/core_plugins/chrome_critical_user_interactions/index.ts
@@ -12,9 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {v4 as uuidv4} from 'uuid';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
-import {BottomTabToSCSAdapter} from '../../public/utils';
import {Trace} from '../../public/trace';
import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
import {PageLoadDetailsPanel} from './page_load_details_panel';
@@ -22,6 +19,8 @@
import {WebContentInteractionPanel} from './web_content_interaction_details_panel';
import {CriticalUserInteractionTrack} from './critical_user_interaction_track';
import {TrackNode} from '../../public/workspace';
+import {TrackEventSelection} from '../../public/selection';
+import {GenericSliceDetailsTab} from '../../frontend/generic_slice_details_tab';
class CriticalUserInteractionPlugin implements PerfettoPlugin {
async onTraceLoad(ctx: Trace): Promise<void> {
@@ -48,65 +47,24 @@
trace: ctx,
uri: CriticalUserInteractionTrack.kind,
}),
+ detailsPanel: (sel: TrackEventSelection) => {
+ switch (sel.interactionType) {
+ case 'chrome_page_loads':
+ return new PageLoadDetailsPanel(ctx, sel.eventId);
+ case 'chrome_startups':
+ return new StartupDetailsPanel(ctx, sel.eventId);
+ case 'chrome_web_content_interactions':
+ return new WebContentInteractionPanel(ctx, sel.eventId);
+ default:
+ return new GenericSliceDetailsTab(
+ ctx,
+ 'chrome_interactions',
+ sel.eventId,
+ 'Chrome Interaction',
+ );
+ }
+ },
});
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === PageLoadDetailsPanel.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new PageLoadDetailsPanel({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === StartupDetailsPanel.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new StartupDetailsPanel({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind ===
- WebContentInteractionPanel.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new WebContentInteractionPanel({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
}
}
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/page_load_details_panel.ts b/ui/src/core_plugins/chrome_critical_user_interactions/page_load_details_panel.ts
index e01e4a8..e2d0b54 100644
--- a/ui/src/core_plugins/chrome_critical_user_interactions/page_load_details_panel.ts
+++ b/ui/src/core_plugins/chrome_critical_user_interactions/page_load_details_panel.ts
@@ -13,29 +13,24 @@
// limitations under the License.
import m from 'mithril';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {
Details,
DetailsSchema,
} from '../../frontend/widgets/sql/details/details';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Trace} from '../../public/trace';
import d = DetailsSchema;
-export class PageLoadDetailsPanel extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'org.perfetto.PageLoadDetailsPanel';
+export class PageLoadDetailsPanel implements TrackEventDetailsPanel {
private data: Details;
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): PageLoadDetailsPanel {
- return new PageLoadDetailsPanel(args);
- }
-
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- this.data = new Details(this.trace, 'chrome_page_loads', this.config.id, {
+ constructor(
+ private readonly trace: Trace,
+ id: number,
+ ) {
+ this.data = new Details(this.trace, 'chrome_page_loads', id, {
'Navigation start': d.Timestamp('navigation_start_ts'),
'FCP event': d.Timestamp('fcp_ts'),
'FCP': d.Interval('navigation_start_ts', 'fcp'),
@@ -65,21 +60,13 @@
});
}
- viewTab() {
+ render() {
return m(
DetailsShell,
{
- title: this.getTitle(),
+ title: 'Chrome Page Load',
},
m(GridLayout, m(GridLayoutColumn, this.data.render())),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return this.data.isLoading();
- }
}
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/startup_details_panel.ts b/ui/src/core_plugins/chrome_critical_user_interactions/startup_details_panel.ts
index ec8667f..0c26623 100644
--- a/ui/src/core_plugins/chrome_critical_user_interactions/startup_details_panel.ts
+++ b/ui/src/core_plugins/chrome_critical_user_interactions/startup_details_panel.ts
@@ -14,8 +14,6 @@
import m from 'mithril';
import {duration, Time, time} from '../../base/time';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {DurationWidget} from '../../frontend/widgets/duration';
import {Timestamp} from '../../frontend/widgets/timestamp';
import {LONG, NUM, STR, STR_NULL} from '../../trace_processor/query_result';
@@ -25,6 +23,8 @@
import {SqlRef} from '../../widgets/sql_ref';
import {dictToTreeNodes, Tree} from '../../widgets/tree';
import {asUpid, Upid} from '../../trace_processor/sql_utils/core_types';
+import {Trace} from '../../public/trace';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
interface Data {
startupId: number;
@@ -35,24 +35,16 @@
upid: Upid;
}
-export class StartupDetailsPanel extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'org.perfetto.StartupDetailsPanel';
- private loaded = false;
- private data: Data | undefined;
+export class StartupDetailsPanel implements TrackEventDetailsPanel {
+ private data?: Data;
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): StartupDetailsPanel {
- return new StartupDetailsPanel(args);
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly id: number,
+ ) {}
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- this.loadData();
- }
-
- private async loadData() {
- const queryResult = await this.engine.query(`
+ async load() {
+ const queryResult = await this.trace.engine.query(`
SELECT
activity_id AS startupId,
name,
@@ -64,7 +56,7 @@
launch_cause AS launchCause,
browser_upid AS upid
FROM chrome_startups
- WHERE id = ${this.config.id};
+ WHERE id = ${this.id};
`);
const iter = queryResult.firstRow({
@@ -87,8 +79,6 @@
if (iter.launchCause) {
this.data.launchCause = iter.launchCause;
}
-
- this.loaded = true;
}
private getDetailsDictionary() {
@@ -106,20 +96,20 @@
}
details['SQL ID'] = m(SqlRef, {
table: 'chrome_startups',
- id: this.config.id,
+ id: this.id,
});
return details;
}
- viewTab() {
- if (this.isLoading()) {
+ render() {
+ if (!this.data) {
return m('h2', 'Loading');
}
return m(
DetailsShell,
{
- title: this.getTitle(),
+ title: 'Chrome Startup',
},
m(
GridLayout,
@@ -134,12 +124,4 @@
),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return !this.loaded;
- }
}
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/web_content_interaction_details_panel.ts b/ui/src/core_plugins/chrome_critical_user_interactions/web_content_interaction_details_panel.ts
index cb6a7fb..25e49aa 100644
--- a/ui/src/core_plugins/chrome_critical_user_interactions/web_content_interaction_details_panel.ts
+++ b/ui/src/core_plugins/chrome_critical_user_interactions/web_content_interaction_details_panel.ts
@@ -28,8 +28,6 @@
import m from 'mithril';
import {duration, Time, time} from '../../base/time';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {asUpid, Upid} from '../../trace_processor/sql_utils/core_types';
import {DurationWidget} from '../../frontend/widgets/duration';
import {Timestamp} from '../../frontend/widgets/timestamp';
@@ -39,6 +37,8 @@
import {Section} from '../../widgets/section';
import {SqlRef} from '../../widgets/sql_ref';
import {dictToTreeNodes, Tree} from '../../widgets/tree';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Trace} from '../../public/trace';
interface Data {
ts: time;
@@ -48,24 +48,16 @@
upid: Upid;
}
-export class WebContentInteractionPanel extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'org.perfetto.WebContentInteractionPanel';
- private loaded = false;
- private data: Data | undefined;
+export class WebContentInteractionPanel implements TrackEventDetailsPanel {
+ private data?: Data;
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): WebContentInteractionPanel {
- return new WebContentInteractionPanel(args);
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly id: number,
+ ) {}
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- this.loadData();
- }
-
- private async loadData() {
- const queryResult = await this.engine.query(`
+ async load() {
+ const queryResult = await this.trace.engine.query(`
SELECT
ts,
dur,
@@ -73,7 +65,7 @@
total_duration_ms AS totalDurationMs,
renderer_upid AS upid
FROM chrome_web_content_interactions
- WHERE id = ${this.config.id};
+ WHERE id = ${this.id};
`);
const iter = queryResult.firstRow({
@@ -91,8 +83,6 @@
totalDurationMs: iter.totalDurationMs,
upid: asUpid(iter.upid),
};
-
- this.loaded = true;
}
private getDetailsDictionary() {
@@ -107,20 +97,20 @@
});
details['SQL ID'] = m(SqlRef, {
table: 'chrome_web_content_interactions',
- id: this.config.id,
+ id: this.id,
});
return details;
}
- viewTab() {
- if (this.isLoading()) {
+ render() {
+ if (!this.data) {
return m('h2', 'Loading');
}
return m(
DetailsShell,
{
- title: this.getTitle(),
+ title: 'Chrome Web Content Interaction',
},
m(
GridLayout,
@@ -135,12 +125,4 @@
),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return !this.loaded;
- }
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts b/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
deleted file mode 100644
index 1df20a2..0000000
--- a/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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 {
- NAMED_ROW,
- NamedRow,
- NamedSliceTrack,
-} from '../../frontend/named_slice_track';
-import {NewTrackArgs} from '../../frontend/track';
-import {Slice} from '../../public/track';
-
-export class ChromeTasksScrollJankTrack extends NamedSliceTrack {
- constructor(args: NewTrackArgs) {
- super(args);
- }
-
- getRowSpec(): NamedRow {
- return NAMED_ROW;
- }
-
- rowToSlice(row: NamedRow): Slice {
- return this.rowToSliceBase(row);
- }
-
- getSqlSource(): string {
- return `
- select
- s2.ts as ts,
- s2.dur as dur,
- s2.id as id,
- 0 as depth,
- s1.full_name as name
- from chrome_tasks_delaying_input_processing s1
- join slice s2 on s2.id=s1.slice_id
- `;
- }
-}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/common.ts b/ui/src/core_plugins/chrome_scroll_jank/common.ts
deleted file mode 100644
index c6232a2..0000000
--- a/ui/src/core_plugins/chrome_scroll_jank/common.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (C) 2024 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 {ObjectByKey} from '../../common/state';
-import {featureFlags} from '../../core/feature_flags';
-import {CustomSqlDetailsPanelConfig} from '../../frontend/tracks/custom_sql_table_slice_track';
-
-export const ENABLE_CHROME_SCROLL_JANK_PLUGIN = featureFlags.register({
- id: 'enableChromeScrollJankPlugin',
- name: 'Enable Chrome Scroll Jank plugin',
- description: 'Adds new tracks for scroll jank in Chrome',
- defaultValue: false,
-});
-
-export interface ScrollJankTrackSpec {
- key: string;
- sqlTableName: string;
- detailsPanelConfig: CustomSqlDetailsPanelConfig;
-}
-
-// Global state for the scroll jank plugin.
-export class ScrollJankPluginState {
- private static instance?: ScrollJankPluginState;
- private tracks: ObjectByKey<ScrollJankTrackSpec>;
-
- private constructor() {
- this.tracks = {};
- }
-
- public static getInstance(): ScrollJankPluginState {
- if (!ScrollJankPluginState.instance) {
- ScrollJankPluginState.instance = new ScrollJankPluginState();
- }
-
- return ScrollJankPluginState.instance;
- }
-
- public registerTrack(args: {
- kind: string;
- trackUri: string;
- tableName: string;
- detailsPanelConfig: CustomSqlDetailsPanelConfig;
- }): void {
- this.tracks[args.kind] = {
- key: args.trackUri,
- sqlTableName: args.tableName,
- detailsPanelConfig: args.detailsPanelConfig,
- };
- }
-
- public unregisterTrack(kind: string): void {
- delete this.tracks[kind];
- }
-
- public getTrack(kind: string): ScrollJankTrackSpec | undefined {
- return this.tracks[kind];
- }
-}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/event_latency_details_panel.ts b/ui/src/core_plugins/chrome_scroll_jank/event_latency_details_panel.ts
index 2d629ea..ae6b128 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/event_latency_details_panel.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/event_latency_details_panel.ts
@@ -13,10 +13,8 @@
// limitations under the License.
import m from 'mithril';
-import {Duration, duration, time} from '../../base/time';
+import {Duration, duration, Time, time} from '../../base/time';
import {raf} from '../../core/raf_scheduler';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {hasArgs, renderArguments} from '../../frontend/slice_args';
import {renderDetails} from '../../frontend/slice_details';
import {
@@ -37,7 +35,7 @@
widgetColumn,
} from '../../frontend/tables/table';
import {TreeTable, TreeTableAttrs} from '../../frontend/widgets/treetable';
-import {NUM, STR} from '../../trace_processor/query_result';
+import {LONG, NUM, STR} from '../../trace_processor/query_result';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
import {Section} from '../../widgets/section';
@@ -51,13 +49,10 @@
getScrollJankCauseStage,
} from './scroll_jank_cause_link_utils';
import {ScrollJankCauseMap} from './scroll_jank_cause_map';
-import {
- getScrollJankSlices,
- getSliceForTrack,
- ScrollJankSlice,
-} from './scroll_jank_slice';
import {sliceRef} from '../../frontend/widgets/slice';
-import {SCROLL_JANK_V3_TRACK_KIND} from '../../public/track_kinds';
+import {JANKS_TRACK_URI, renderSliceRef} from './selection_utils';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Trace} from '../../public/trace';
// Given a node in the slice tree, return a path from root to it.
function getPath(slice: SliceTreeNode): string[] {
@@ -104,15 +99,17 @@
return `${delta > 0 ? '+' : ''}${Duration.humanise(delta)}`;
}
-export class EventLatencySliceDetailsPanel extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'dev.perfetto.EventLatencySliceDetailsPanel';
-
- private loaded = false;
+export class EventLatencySliceDetailsPanel implements TrackEventDetailsPanel {
private name = '';
private topEventLatencyId: SliceSqlId | undefined = undefined;
private sliceDetails?: SliceDetails;
- private jankySlice?: ScrollJankSlice;
+ private jankySlice?: {
+ ts: time;
+ dur: duration;
+ id: number;
+ causeOfJank: string;
+ };
// Whether this stage has caused jank. This is also true for top level
// EventLatency slices where a descendant is a cause of jank.
@@ -132,31 +129,24 @@
private tracksByTrackId: Map<number, string>;
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): EventLatencySliceDetailsPanel {
- return new EventLatencySliceDetailsPanel(args);
- }
-
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
-
+ constructor(
+ private readonly trace: Trace,
+ private readonly id: number,
+ ) {
this.tracksByTrackId = new Map<number, string>();
this.trace.tracks.getAllTracks().forEach((td) => {
td.tags?.trackIds?.forEach((trackId) => {
this.tracksByTrackId.set(trackId, td.uri);
});
});
-
- this.loadData();
}
- async loadData() {
- const queryResult = await this.engine.query(`
+ async load() {
+ const queryResult = await this.trace.engine.query(`
SELECT
name
- FROM ${this.config.sqlTableName}
- WHERE id = ${this.config.id}
+ FROM slice
+ WHERE id = ${this.id}
`);
const iter = queryResult.firstRow({
@@ -169,14 +159,12 @@
await this.loadJankSlice();
await this.loadRelevantThreads();
await this.loadEventLatencyBreakdown();
-
- this.loaded = true;
}
async loadSlice() {
this.sliceDetails = await getSlice(
- this.engine,
- asSliceSqlId(this.config.id),
+ this.trace.engine,
+ asSliceSqlId(this.id),
);
raf.scheduleRedraw();
}
@@ -194,14 +182,25 @@
);
}
- const possibleSlices = await getScrollJankSlices(
- this.engine,
- this.topEventLatencyId,
- );
- // We may not get any slices if the EventLatency doesn't indicate any
- // jank occurred.
- if (possibleSlices.length > 0) {
- this.jankySlice = possibleSlices[0];
+ const it = (
+ await this.trace.engine.query(`
+ SELECT ts, dur, id, cause_of_jank as causeOfJank
+ FROM chrome_janky_frame_presentation_intervals
+ WHERE event_latency_id = ${this.topEventLatencyId}`)
+ ).iter({
+ id: NUM,
+ ts: LONG,
+ dur: LONG,
+ causeOfJank: STR,
+ });
+
+ if (it.valid()) {
+ this.jankySlice = {
+ id: it.id,
+ ts: Time.fromRaw(it.ts),
+ dur: Duration.fromRaw(it.dur),
+ causeOfJank: it.causeOfJank,
+ };
}
}
@@ -214,7 +213,7 @@
if (this.sliceDetails.name === 'EventLatency' && !this.jankySlice) return;
const possibleScrollJankStage = await getScrollJankCauseStage(
- this.engine,
+ this.trace.engine,
this.topEventLatencyId,
);
if (this.sliceDetails.name === 'EventLatency') {
@@ -237,7 +236,7 @@
if (this.relevantThreadStage) {
this.relevantThreadTracks = await getEventLatencyCauseTracks(
- this.engine,
+ this.trace.engine,
this.relevantThreadStage,
);
}
@@ -248,7 +247,7 @@
return;
}
this.eventLatencyBreakdown = await getDescendantSliceTree(
- this.engine,
+ this.trace.engine,
this.topEventLatencyId,
);
@@ -264,7 +263,7 @@
AND HAS_DESCENDANT_SLICE_WITH_NAME(
id,
'SubmitCompositorFrameToPresentationCompositorFrame')`;
- const prevEventLatency = await getSliceFromConstraints(this.engine, {
+ const prevEventLatency = await getSliceFromConstraints(this.trace.engine, {
filters: [
`name = 'EventLatency'`,
`id < ${this.topEventLatencyId}`,
@@ -275,12 +274,12 @@
});
if (prevEventLatency.length > 0) {
this.prevEventLatencyBreakdown = await getDescendantSliceTree(
- this.engine,
+ this.trace.engine,
prevEventLatency[0].id,
);
}
- const nextEventLatency = await getSliceFromConstraints(this.engine, {
+ const nextEventLatency = await getSliceFromConstraints(this.trace.engine, {
filters: [
`name = 'EventLatency'`,
`id > ${this.topEventLatencyId}`,
@@ -291,7 +290,7 @@
});
if (nextEventLatency.length > 0) {
this.nextEventLatencyBreakdown = await getDescendantSliceTree(
- this.engine,
+ this.trace.engine,
nextEventLatency[0].id,
);
}
@@ -381,7 +380,7 @@
private async getOldestAncestorSliceId(): Promise<number> {
let eventLatencyId = -1;
if (!this.sliceDetails) return eventLatencyId;
- const queryResult = await this.engine.query(`
+ const queryResult = await this.trace.engine.query(`
SELECT
id
FROM ancestor_slice(${this.sliceDetails.id})
@@ -415,16 +414,15 @@
: 'EventLatency in context of other Input events',
right: this.sliceDetails ? '' : 'N/A',
}),
- m(TreeNode, {
- left: this.jankySlice
- ? getSliceForTrack(
- this.jankySlice,
- SCROLL_JANK_V3_TRACK_KIND,
- 'Jank Interval',
- )
- : 'Jank Interval',
- right: this.jankySlice ? '' : 'N/A',
- }),
+ this.jankySlice &&
+ m(TreeNode, {
+ left: renderSliceRef({
+ trace: this.trace,
+ id: this.jankySlice.id,
+ trackUri: JANKS_TRACK_URI,
+ title: this.jankySlice.causeOfJank,
+ }),
+ }),
),
);
}
@@ -498,7 +496,7 @@
);
}
- viewTab() {
+ render() {
if (this.sliceDetails) {
const slice = this.sliceDetails;
@@ -544,12 +542,4 @@
return m(DetailsShell, {title: 'Slice', description: 'Loading...'});
}
}
-
- isLoading() {
- return !this.loaded;
- }
-
- getTitle(): string {
- return `Current Selection`;
- }
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts b/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
index 8581258..59e10c7 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
@@ -12,20 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {globals} from '../../frontend/globals';
import {NamedRow} from '../../frontend/named_slice_track';
import {NewTrackArgs} from '../../frontend/track';
-import {CHROME_EVENT_LATENCY_TRACK_KIND} from '../../public/track_kinds';
import {Slice} from '../../public/track';
import {
- CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../../frontend/tracks/custom_sql_table_slice_track';
-import {EventLatencySliceDetailsPanel} from './event_latency_details_panel';
import {JANK_COLOR} from './jank_colors';
-import {ScrollJankPluginState} from './common';
-import {exists} from '../../base/utils';
export const JANKY_LATENCY_NAME = 'Janky EventLatency';
@@ -35,32 +29,12 @@
private baseTable: string,
) {
super(args);
- ScrollJankPluginState.getInstance().registerTrack({
- kind: CHROME_EVENT_LATENCY_TRACK_KIND,
- trackUri: this.uri,
- tableName: this.tableName,
- detailsPanelConfig: this.getDetailsPanel(),
- });
- }
-
- async onDestroy(): Promise<void> {
- await super.onDestroy();
- ScrollJankPluginState.getInstance().unregisterTrack(
- CHROME_EVENT_LATENCY_TRACK_KIND,
- );
}
getSqlSource(): string {
return `SELECT * FROM ${this.baseTable}`;
}
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- return {
- kind: EventLatencySliceDetailsPanel.kind,
- config: {title: '', sqlTableName: this.tableName},
- };
- }
-
getSqlDataSource(): CustomSqlTableDefConfig {
return {
sqlTableName: this.baseTable,
@@ -76,22 +50,6 @@
}
}
- onUpdatedSlices(slices: Slice[]) {
- for (const slice of slices) {
- const currentSelection = this.trace.selection.legacySelection;
- const isSelected =
- exists(currentSelection) &&
- currentSelection.kind === 'GENERIC_SLICE' &&
- currentSelection.id !== undefined &&
- currentSelection.id === slice.id;
-
- const highlighted = globals.state.highlightedSliceId === slice.id;
- const hasFocus = highlighted || isSelected;
- slice.isHighlighted = !!hasFocus;
- }
- super.onUpdatedSlices(slices);
- }
-
// At the moment we will just display the slice details. However, on select,
// this behavior should be customized to show jank-related data.
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/index.ts b/ui/src/core_plugins/chrome_scroll_jank/index.ts
index e67921f..ab0dde0 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/index.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/index.ts
@@ -12,24 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {v4 as uuidv4} from 'uuid';
import {uuidv4Sql} from '../../base/uuid';
import {generateSqlWithInternalLayout} from '../../common/internal_layout_utils';
import {featureFlags} from '../../core/feature_flags';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
-import {BottomTabToSCSAdapter} from '../../public/utils';
-import {
- CHROME_EVENT_LATENCY_TRACK_KIND,
- CHROME_TOPLEVEL_SCROLLS_KIND,
- CHROME_SCROLL_JANK_TRACK_KIND,
- SCROLL_JANK_V3_TRACK_KIND,
-} from '../../public/track_kinds';
-import {NUM} from '../../trace_processor/query_result';
import {Trace} from '../../public/trace';
import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {Engine} from '../../trace_processor/engine';
-import {ChromeTasksScrollJankTrack} from './chrome_tasks_scroll_jank_track';
-import {ENABLE_CHROME_SCROLL_JANK_PLUGIN} from './common';
import {EventLatencySliceDetailsPanel} from './event_latency_details_panel';
import {EventLatencyTrack, JANKY_LATENCY_NAME} from './event_latency_track';
import {ScrollDetailsPanel} from './scroll_details_panel';
@@ -38,9 +25,6 @@
import {TopLevelScrollTrack} from './scroll_track';
import {ScrollJankCauseMap} from './scroll_jank_cause_map';
import {TrackNode} from '../../public/workspace';
-import {getOrCreateGroupForThread} from '../../public/standard_groups';
-import {addQueryResultsTab} from '../../public/lib/query_table/query_result_tab';
-import {ThreadSliceDetailsPanel} from '../../frontend/thread_slice_details_tab';
const ENABLE_SCROLL_JANK_PLUGIN_V2 = featureFlags.register({
id: 'enableScrollJankPluginV2',
@@ -51,38 +35,6 @@
class ChromeScrollJankPlugin implements PerfettoPlugin {
async onTraceLoad(ctx: Trace): Promise<void> {
- if (ENABLE_CHROME_SCROLL_JANK_PLUGIN.get()) {
- await this.addChromeScrollJankTrack(ctx);
-
- if (!(await isChromeTrace(ctx.engine))) {
- return;
- }
-
- // Initialise the chrome_tasks_delaying_input_processing table. It will be
- // used in the tracks above.
- await ctx.engine.query(`
- INCLUDE PERFETTO MODULE deprecated.v42.common.slices;
- SELECT RUN_METRIC(
- 'chrome/chrome_tasks_delaying_input_processing.sql',
- 'duration_causing_jank_ms',
- /* duration_causing_jank_ms = */ '8');`);
-
- const query = `
- select
- s1.full_name,
- s1.duration_ms,
- s1.slice_id,
- s1.thread_dur_ms,
- s2.id,
- s2.ts,
- s2.dur,
- s2.track_id
- from chrome_tasks_delaying_input_processing s1
- join slice s2 on s1.slice_id=s2.id
- `;
- addQueryResultsTab(ctx, {query, title: 'Scroll Jank: long tasks'});
- }
-
if (ENABLE_SCROLL_JANK_PLUGIN_V2.get()) {
const group = new TrackNode({
title: 'Chrome Scroll Jank',
@@ -98,46 +50,6 @@
}
}
- private async addChromeScrollJankTrack(ctx: Trace): Promise<void> {
- const queryResult = await ctx.engine.query(`
- select
- utid,
- upid
- from thread
- where name='CrBrowserMain'
- `);
-
- if (queryResult.numRows() === 0) {
- return;
- }
-
- const it = queryResult.firstRow({
- utid: NUM,
- upid: NUM,
- });
-
- const {upid, utid} = it;
- const uri = 'perfetto.ChromeScrollJank';
- const title = 'Scroll Jank causes - long tasks';
- ctx.tracks.registerTrack({
- uri,
- title,
- tags: {
- kind: CHROME_SCROLL_JANK_TRACK_KIND,
- upid,
- utid,
- },
- track: new ChromeTasksScrollJankTrack({
- trace: ctx,
- uri,
- }),
- detailsPanel: () => new ThreadSliceDetailsPanel(ctx, 'slice'),
- });
- const group = getOrCreateGroupForThread(ctx.workspace, utid);
- const track = new TrackNode({uri, title});
- group.addChildInOrder(track);
- }
-
private async addTopLevelScrollTrack(
ctx: Trace,
group: TrackNode,
@@ -153,36 +65,17 @@
ctx.tracks.registerTrack({
uri,
title,
- tags: {
- kind: CHROME_TOPLEVEL_SCROLLS_KIND,
- },
track: new TopLevelScrollTrack({
trace: ctx,
uri,
}),
+ detailsPanel: (sel) => {
+ return new ScrollDetailsPanel(ctx, sel.eventId);
+ },
});
const track = new TrackNode({uri, title});
group.addChildInOrder(track);
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === ScrollDetailsPanel.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new ScrollDetailsPanel({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
}
private async addEventLatencyTrack(
@@ -291,34 +184,14 @@
ctx.tracks.registerTrack({
uri,
title,
- tags: {
- kind: CHROME_EVENT_LATENCY_TRACK_KIND,
- },
track: new EventLatencyTrack({trace: ctx, uri}, baseTable),
+ detailsPanel: (sel) => {
+ return new EventLatencySliceDetailsPanel(ctx, sel.eventId);
+ },
});
const track = new TrackNode({uri, title});
group.addChildInOrder(track);
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind ===
- EventLatencySliceDetailsPanel.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new EventLatencySliceDetailsPanel({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
}
private async addScrollJankV3ScrollTrack(
@@ -335,54 +208,18 @@
ctx.tracks.registerTrack({
uri,
title,
- tags: {
- kind: SCROLL_JANK_V3_TRACK_KIND,
- },
track: new ScrollJankV3Track({
trace: ctx,
uri,
}),
+ detailsPanel: (sel) => new ScrollJankV3DetailsPanel(ctx, sel.eventId),
});
const track = new TrackNode({uri, title});
group.addChildInOrder(track);
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === ScrollJankV3DetailsPanel.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new ScrollJankV3DetailsPanel({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
}
}
-async function isChromeTrace(engine: Engine) {
- const queryResult = await engine.query(`
- select utid, upid
- from thread
- where name='CrBrowserMain'
- `);
-
- const it = queryResult.iter({
- utid: NUM,
- upid: NUM,
- });
-
- return it.valid();
-}
-
export const plugin: PluginDescriptor = {
pluginId: 'perfetto.ChromeScrollJank',
plugin: ChromeScrollJankPlugin,
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_details_panel.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_details_panel.ts
index c184ba7..30ab521 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_details_panel.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_details_panel.ts
@@ -15,19 +15,21 @@
import m from 'mithril';
import {duration, Time, time} from '../../base/time';
import {exists} from '../../base/utils';
-import {raf} from '../../core/raf_scheduler';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {
ColumnDescriptor,
- numberColumn,
Table,
TableData,
widgetColumn,
} from '../../frontend/tables/table';
import {DurationWidget} from '../../frontend/widgets/duration';
import {Timestamp} from '../../frontend/widgets/timestamp';
-import {LONG, NUM, STR} from '../../trace_processor/query_result';
+import {
+ LONG,
+ LONG_NULL,
+ NUM,
+ NUM_NULL,
+ STR,
+} from '../../trace_processor/query_result';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
import {Section} from '../../widgets/section';
@@ -41,12 +43,9 @@
getPredictorJankDeltas,
getPresentedScrollDeltas,
} from './scroll_delta_graph';
-import {
- getScrollJankSlices,
- getSliceForTrack,
- ScrollJankSlice,
-} from './scroll_jank_slice';
-import {SCROLL_JANK_V3_TRACK_KIND} from '../../public/track_kinds';
+import {JANKS_TRACK_URI, renderSliceRef} from './selection_utils';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Trace} from '../../public/trace';
interface Data {
// Scroll ID.
@@ -71,32 +70,27 @@
interface JankSliceDetails {
cause: string;
- jankSlice: ScrollJankSlice;
- delayDur: duration;
- delayVsync: number;
+ id: number;
+ ts: time;
+ dur?: duration;
+ delayVsync?: number;
}
-export class ScrollDetailsPanel extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'org.perfetto.ScrollDetailsPanel';
- loaded = false;
- data: Data | undefined;
- metrics: Metrics = {};
- orderedJankSlices: JankSliceDetails[] = [];
- scrollDeltas: m.Child;
+export class ScrollDetailsPanel implements TrackEventDetailsPanel {
+ private data?: Data;
+ private metrics: Metrics = {};
+ private orderedJankSlices: JankSliceDetails[] = [];
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): ScrollDetailsPanel {
- return new ScrollDetailsPanel(args);
- }
+ // TODO(altimin): Don't store Mithril vnodes between render cycles.
+ private scrollDeltas: m.Child;
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- this.loadData();
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly id: number,
+ ) {}
- private async loadData() {
- const queryResult = await this.engine.query(`
+ async load() {
+ const queryResult = await this.trace.engine.query(`
WITH scrolls AS (
SELECT
id,
@@ -107,7 +101,7 @@
THEN gesture_scroll_begin_ts + dur
ELSE ts + dur
END AS end_ts
- FROM chrome_scrolls WHERE id = ${this.config.id})
+ FROM chrome_scrolls WHERE id = ${this.id})
SELECT
id,
start_ts AS ts,
@@ -126,8 +120,6 @@
};
await this.loadMetrics();
- this.loaded = true;
- raf.scheduleFullRedraw();
}
private async loadMetrics() {
@@ -139,7 +131,7 @@
private async loadInputEventCount() {
if (exists(this.data)) {
- const queryResult = await this.engine.query(`
+ const queryResult = await this.trace.engine.query(`
SELECT
COUNT(*) AS inputEventCount
FROM slice s
@@ -159,7 +151,7 @@
private async loadFrameStats() {
if (exists(this.data)) {
- const queryResult = await this.engine.query(`
+ const queryResult = await this.trace.engine.query(`
SELECT
IFNULL(frame_count, 0) AS frameCount,
IFNULL(missed_vsyncs, 0) AS missedVsyncs,
@@ -190,39 +182,36 @@
private async loadDelayData() {
if (exists(this.data)) {
- const queryResult = await this.engine.query(`
+ const queryResult = await this.trace.engine.query(`
SELECT
+ id,
+ ts,
+ dur,
IFNULL(sub_cause_of_jank, IFNULL(cause_of_jank, 'Unknown')) AS cause,
- IFNULL(event_latency_id, 0) AS eventLatencyId,
- IFNULL(dur, 0) AS delayDur,
- IFNULL(delayed_frame_count, 0) AS delayVsync
+ event_latency_id AS eventLatencyId,
+ delayed_frame_count AS delayVsync
FROM chrome_janky_frame_presentation_intervals s
WHERE s.ts >= ${this.data.ts}
AND s.ts + s.dur <= ${this.data.ts + this.data.dur}
- ORDER by delayDur DESC;
+ ORDER by dur DESC;
`);
- const iter = queryResult.iter({
+ const it = queryResult.iter({
+ id: NUM,
+ ts: LONG,
+ dur: LONG_NULL,
cause: STR,
- eventLatencyId: NUM,
- delayDur: LONG,
- delayVsync: NUM,
+ eventLatencyId: NUM_NULL,
+ delayVsync: NUM_NULL,
});
- for (; iter.valid(); iter.next()) {
- if (iter.delayDur <= 0) {
- break;
- }
- const jankSlices = await getScrollJankSlices(
- this.engine,
- iter.eventLatencyId,
- );
-
+ for (; it.valid(); it.next()) {
this.orderedJankSlices.push({
- cause: iter.cause,
- jankSlice: jankSlices[0],
- delayDur: iter.delayDur,
- delayVsync: iter.delayVsync,
+ id: it.id,
+ ts: Time.fromRaw(it.ts),
+ dur: it.dur ?? undefined,
+ cause: it.cause,
+ delayVsync: it.delayVsync ?? undefined,
});
}
}
@@ -230,17 +219,20 @@
private async loadScrollOffsets() {
if (exists(this.data)) {
- const inputDeltas = await getInputScrollDeltas(this.engine, this.data.id);
+ const inputDeltas = await getInputScrollDeltas(
+ this.trace.engine,
+ this.data.id,
+ );
const presentedDeltas = await getPresentedScrollDeltas(
- this.engine,
+ this.trace.engine,
this.data.id,
);
const predictorDeltas = await getPredictorJankDeltas(
- this.engine,
+ this.trace.engine,
this.data.id,
);
const jankIntervals = await getJankIntervals(
- this.engine,
+ this.trace.engine,
this.data.ts,
this.data.dur,
);
@@ -310,31 +302,27 @@
private getDelayTable(): m.Child {
if (this.orderedJankSlices.length > 0) {
- interface DelayData {
- jankLink: m.Child;
- dur: m.Child;
- delayedVSyncs: number;
- }
-
- const columns: ColumnDescriptor<DelayData>[] = [
- widgetColumn<DelayData>('Cause', (x) => x.jankLink),
- widgetColumn<DelayData>('Duration', (x) => x.dur),
- numberColumn<DelayData>('Delayed Vsyncs', (x) => x.delayedVSyncs),
+ const columns: ColumnDescriptor<JankSliceDetails>[] = [
+ widgetColumn<JankSliceDetails>('Cause', (jankSlice) =>
+ renderSliceRef({
+ trace: this.trace,
+ id: jankSlice.id,
+ trackUri: JANKS_TRACK_URI,
+ title: jankSlice.cause,
+ }),
+ ),
+ widgetColumn<JankSliceDetails>('Duration', (jankSlice) =>
+ jankSlice.dur !== undefined
+ ? m(DurationWidget, {dur: jankSlice.dur})
+ : 'NULL',
+ ),
+ widgetColumn<JankSliceDetails>(
+ 'Delayed Vsyncs',
+ (jankSlice) => jankSlice.delayVsync,
+ ),
];
- const data: DelayData[] = [];
- for (const jankSlice of this.orderedJankSlices) {
- data.push({
- jankLink: getSliceForTrack(
- jankSlice.jankSlice,
- SCROLL_JANK_V3_TRACK_KIND,
- jankSlice.cause,
- ),
- dur: m(DurationWidget, {dur: jankSlice.delayDur}),
- delayedVSyncs: jankSlice.delayVsync,
- });
- }
- const tableData = new TableData(data);
+ const tableData = new TableData(this.orderedJankSlices);
return m(Table, {
data: tableData,
@@ -391,8 +379,8 @@
);
}
- viewTab() {
- if (this.isLoading() || this.data == undefined) {
+ render() {
+ if (this.data == undefined) {
return m('h2', 'Loading');
}
@@ -400,13 +388,13 @@
'Scroll ID': this.data.id,
'Start time': m(Timestamp, {ts: this.data.ts}),
'Duration': m(DurationWidget, {dur: this.data.dur}),
- 'SQL ID': m(SqlRef, {table: 'chrome_scrolls', id: this.config.id}),
+ 'SQL ID': m(SqlRef, {table: 'chrome_scrolls', id: this.id}),
});
return m(
DetailsShell,
{
- title: this.getTitle(),
+ title: 'Scroll',
},
m(
GridLayout,
@@ -437,12 +425,4 @@
),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return !this.loaded;
- }
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_slice.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_slice.ts
deleted file mode 100644
index 2b3e33c..0000000
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_slice.ts
+++ /dev/null
@@ -1,214 +0,0 @@
-// Copyright (C) 2023 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 m from 'mithril';
-import {Icons} from '../../base/semantic_icons';
-import {duration, time, Time} from '../../base/time';
-import {globals} from '../../frontend/globals';
-import {SliceSqlId} from '../../trace_processor/sql_utils/core_types';
-import {Engine} from '../../trace_processor/engine';
-import {LONG, NUM} from '../../trace_processor/query_result';
-import {
- constraintsToQuerySuffix,
- SQLConstraints,
-} from '../../trace_processor/sql_utils';
-import {Anchor} from '../../widgets/anchor';
-import {ScrollJankPluginState, ScrollJankTrackSpec} from './common';
-import {
- CHROME_EVENT_LATENCY_TRACK_KIND,
- SCROLL_JANK_V3_TRACK_KIND,
-} from '../../public/track_kinds';
-import {scrollTo} from '../../public/scroll_helper';
-
-interface BasicSlice {
- // ID of slice.
- sliceId: number;
- // Timestamp of the beginning of this slice in nanoseconds.
- ts: time;
- // Duration of this slice in nanoseconds.
- dur: duration;
-}
-
-async function getSlicesFromTrack(
- engine: Engine,
- track: ScrollJankTrackSpec,
- constraints: SQLConstraints,
-): Promise<BasicSlice[]> {
- const query = await engine.query(`
- SELECT
- id AS sliceId,
- ts,
- dur AS dur
- FROM ${track.sqlTableName}
- ${constraintsToQuerySuffix(constraints)}`);
- const it = query.iter({
- sliceId: NUM,
- ts: LONG,
- dur: LONG,
- });
-
- const result: BasicSlice[] = [];
- for (; it.valid(); it.next()) {
- result.push({
- sliceId: it.sliceId as number,
- ts: Time.fromRaw(it.ts),
- dur: it.dur,
- });
- }
- return result;
-}
-
-export type ScrollJankSlice = BasicSlice;
-export async function getScrollJankSlices(
- engine: Engine,
- id: number,
-): Promise<ScrollJankSlice[]> {
- const track = ScrollJankPluginState.getInstance().getTrack(
- SCROLL_JANK_V3_TRACK_KIND,
- );
- if (track == undefined) {
- throw new Error(`${SCROLL_JANK_V3_TRACK_KIND} track is not registered.`);
- }
-
- const slices = await getSlicesFromTrack(engine, track, {
- filters: [`event_latency_id=${id}`],
- });
- return slices;
-}
-
-export type EventLatencySlice = BasicSlice;
-export async function getEventLatencySlice(
- engine: Engine,
- id: number,
-): Promise<EventLatencySlice | undefined> {
- const track = ScrollJankPluginState.getInstance().getTrack(
- CHROME_EVENT_LATENCY_TRACK_KIND,
- );
- if (track == undefined) {
- throw new Error(
- `${CHROME_EVENT_LATENCY_TRACK_KIND} track is not registered.`,
- );
- }
-
- const slices = await getSlicesFromTrack(engine, track, {
- filters: [`id=${id}`],
- });
- return slices[0];
-}
-
-export async function getEventLatencyDescendantSlice(
- engine: Engine,
- id: number,
- descendant: string | undefined,
-): Promise<EventLatencySlice | undefined> {
- const query = await engine.query(`
- SELECT
- id as sliceId,
- ts,
- dur as dur
- FROM descendant_slice(${id})
- WHERE name='${descendant}'`);
- const it = query.iter({
- sliceId: NUM,
- ts: LONG,
- dur: LONG,
- });
-
- const result: EventLatencySlice[] = [];
-
- for (; it.valid(); it.next()) {
- result.push({
- sliceId: it.sliceId as SliceSqlId,
- ts: Time.fromRaw(it.ts),
- dur: it.dur,
- });
- }
-
- const eventLatencyTrack = ScrollJankPluginState.getInstance().getTrack(
- CHROME_EVENT_LATENCY_TRACK_KIND,
- );
- if (eventLatencyTrack == undefined) {
- throw new Error(
- `${CHROME_EVENT_LATENCY_TRACK_KIND} track is not registered.`,
- );
- }
-
- if (result.length > 1) {
- throw new Error(`
- Slice table and track view ${eventLatencyTrack.sqlTableName} has more than one descendant of slice id ${id} with name ${descendant}`);
- }
- if (result.length === 0) {
- return undefined;
- }
- return result[0];
-}
-
-interface BasicScrollJankSliceRefAttrs {
- id: number;
- ts: time;
- dur: duration;
- name: string;
- kind: string;
-}
-
-export class ScrollJankSliceRef
- implements m.ClassComponent<BasicScrollJankSliceRefAttrs>
-{
- view(vnode: m.Vnode<BasicScrollJankSliceRefAttrs>) {
- return m(
- Anchor,
- {
- icon: Icons.UpdateSelection,
- onclick: () => {
- const track = ScrollJankPluginState.getInstance().getTrack(
- vnode.attrs.kind,
- );
- if (track == undefined) {
- throw new Error(`${vnode.attrs.kind} track is not registered.`);
- }
-
- const trackUri = track.key;
- globals.selectionManager.selectGenericSlice({
- id: vnode.attrs.id,
- sqlTableName: track.sqlTableName,
- start: vnode.attrs.ts,
- duration: vnode.attrs.dur,
- trackUri,
- detailsPanelConfig: track.detailsPanelConfig,
- });
-
- scrollTo({
- track: {uri: trackUri, expandGroup: true},
- time: {start: vnode.attrs.ts},
- });
- },
- },
- vnode.attrs.name,
- );
- }
-}
-
-export function getSliceForTrack(
- state: BasicSlice,
- trackKind: string,
- name: string,
-): m.Child {
- return m(ScrollJankSliceRef, {
- id: state.sliceId,
- ts: state.ts,
- dur: state.dur,
- name: name,
- kind: trackKind,
- });
-}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_details_panel.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_details_panel.ts
index 48fd988..280fd18 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_details_panel.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_details_panel.ts
@@ -16,8 +16,6 @@
import {duration, Time, time} from '../../base/time';
import {exists} from '../../base/utils';
import {raf} from '../../core/raf_scheduler';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {getSlice, SliceDetails} from '../../trace_processor/sql_utils/slice';
import {asSliceSqlId} from '../../trace_processor/sql_utils/core_types';
import {DurationWidget} from '../../frontend/widgets/duration';
@@ -30,13 +28,9 @@
import {SqlRef} from '../../widgets/sql_ref';
import {MultiParagraphText, TextParagraph} from '../../widgets/text_paragraph';
import {dictToTreeNodes, Tree, TreeNode} from '../../widgets/tree';
-import {
- EventLatencySlice,
- getEventLatencyDescendantSlice,
- getEventLatencySlice,
- getSliceForTrack,
-} from './scroll_jank_slice';
-import {CHROME_EVENT_LATENCY_TRACK_KIND} from '../../public/track_kinds';
+import {EVENT_LATENCY_TRACK_URI, renderSliceRef} from './selection_utils';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Trace} from '../../public/trace';
interface Data {
name: string;
@@ -64,10 +58,8 @@
return getSlice(engine, asSliceSqlId(id));
}
-export class ScrollJankV3DetailsPanel extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'org.perfetto.ScrollJankV3DetailsPanel';
- data: Data | undefined;
- loaded = false;
+export class ScrollJankV3DetailsPanel implements TrackEventDetailsPanel {
+ private data?: Data;
//
// Linking to associated slices
@@ -80,29 +72,34 @@
// Link to the Event Latency in the EventLatencyTrack (subset of event
// latencies associated with input events).
- private eventLatencySliceDetails?: EventLatencySlice;
+ private eventLatencySliceDetails?: {
+ ts: time;
+ dur: duration;
+ };
// Link to the scroll jank cause stage of the associated EventLatencyTrack
// slice. May be unknown.
- private causeSliceDetails?: EventLatencySlice;
+ private causeSliceDetails?: {
+ id: number;
+ ts: time;
+ dur: duration;
+ };
// Link to the scroll jank sub-cause stage of the associated EventLatencyTrack
// slice. Does not apply to all causes.
- private subcauseSliceDetails?: EventLatencySlice;
+ private subcauseSliceDetails?: {
+ id: number;
+ ts: time;
+ dur: duration;
+ };
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): ScrollJankV3DetailsPanel {
- return new ScrollJankV3DetailsPanel(args);
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly id: number,
+ ) {}
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- this.loadData();
- }
-
- private async loadData() {
- const queryResult = await this.engine.query(`
+ async load() {
+ const queryResult = await this.trace.engine.query(`
SELECT
IIF(
cause_of_jank IS NOT NULL,
@@ -117,7 +114,7 @@
IFNULL(cause_of_jank, "UNKNOWN") AS causeOfJank,
IFNULL(sub_cause_of_jank, "UNKNOWN") AS subcauseOfJank
FROM chrome_janky_frame_presentation_intervals
- WHERE id = ${this.config.id}`);
+ WHERE id = ${this.id}`);
const iter = queryResult.firstRow({
name: STR,
@@ -143,7 +140,6 @@
await this.loadJankyFrames();
await this.loadSlices();
- this.loaded = true;
raf.scheduleFullRedraw();
}
@@ -164,35 +160,62 @@
private async loadSlices() {
if (exists(this.data)) {
this.sliceDetails = await getSliceDetails(
- this.engine,
+ this.trace.engine,
this.data.eventLatencyId,
);
- this.eventLatencySliceDetails = await getEventLatencySlice(
- this.engine,
- this.data.eventLatencyId,
- );
+ const it = (
+ await this.trace.engine.query(`
+ SELECT ts, dur
+ FROM slice
+ WHERE id = ${this.data.eventLatencyId}
+ `)
+ ).iter({ts: LONG, dur: LONG});
+ this.eventLatencySliceDetails = {
+ ts: Time.fromRaw(it.ts),
+ dur: it.dur,
+ };
if (this.hasCause()) {
- this.causeSliceDetails = await getEventLatencyDescendantSlice(
- this.engine,
- this.data.eventLatencyId,
- this.data.jankCause,
- );
+ const it = (
+ await this.trace.engine.query(`
+ SELECT id, ts, dur
+ FROM descendant_slice(${this.data.eventLatencyId})
+ WHERE name = "${this.data.jankCause}"
+ `)
+ ).iter({id: NUM, ts: LONG, dur: LONG});
+
+ if (it.valid()) {
+ this.causeSliceDetails = {
+ id: it.id,
+ ts: Time.fromRaw(it.ts),
+ dur: it.dur,
+ };
+ }
}
if (this.hasSubcause()) {
- this.subcauseSliceDetails = await getEventLatencyDescendantSlice(
- this.engine,
- this.data.eventLatencyId,
- this.data.jankSubcause,
- );
+ const it = (
+ await this.trace.engine.query(`
+ SELECT id, ts, dur
+ FROM descendant_slice(${this.data.eventLatencyId})
+ WHERE name = "${this.data.jankSubcause}"
+ `)
+ ).iter({id: NUM, ts: LONG, dur: LONG});
+
+ if (it.valid()) {
+ this.subcauseSliceDetails = {
+ id: it.id,
+ ts: Time.fromRaw(it.ts),
+ dur: it.dur,
+ };
+ }
}
}
}
private async loadJankyFrames() {
if (exists(this.data)) {
- const queryResult = await this.engine.query(`
+ const queryResult = await this.trace.engine.query(`
SELECT
COUNT(*) AS jankyFrames
FROM chrome_frame_info_with_delay
@@ -263,33 +286,36 @@
const result: {[key: string]: m.Child} = {};
if (exists(this.sliceDetails) && exists(this.data)) {
- result['Janked Event Latency stage'] = exists(this.causeSliceDetails)
- ? getSliceForTrack(
- this.causeSliceDetails,
- CHROME_EVENT_LATENCY_TRACK_KIND,
- this.data.jankCause,
- )
- : this.data.jankCause;
+ result['Janked Event Latency stage'] =
+ exists(this.causeSliceDetails) &&
+ renderSliceRef({
+ trace: this.trace,
+ id: this.causeSliceDetails.id,
+ trackUri: EVENT_LATENCY_TRACK_URI,
+ title: this.data.jankCause,
+ });
if (this.hasSubcause()) {
- result['Sub-cause of Jank'] = exists(this.subcauseSliceDetails)
- ? getSliceForTrack(
- this.subcauseSliceDetails,
- CHROME_EVENT_LATENCY_TRACK_KIND,
- this.data.jankSubcause,
- )
- : this.data.jankSubcause;
+ result['Sub-cause of Jank'] =
+ exists(this.subcauseSliceDetails) &&
+ renderSliceRef({
+ trace: this.trace,
+ id: this.subcauseSliceDetails.id,
+ trackUri: EVENT_LATENCY_TRACK_URI,
+ title: this.data.jankCause,
+ });
}
const children = dictToTreeNodes(result);
if (exists(this.eventLatencySliceDetails)) {
children.unshift(
m(TreeNode, {
- left: getSliceForTrack(
- this.eventLatencySliceDetails,
- CHROME_EVENT_LATENCY_TRACK_KIND,
- 'Input EventLatency in context of ScrollUpdates',
- ),
+ left: renderSliceRef({
+ trace: this.trace,
+ id: this.data.eventLatencyId,
+ trackUri: EVENT_LATENCY_TRACK_URI,
+ title: this.data.jankCause,
+ }),
right: '',
}),
);
@@ -303,7 +329,7 @@
return dictToTreeNodes(result);
}
- viewTab() {
+ render() {
if (this.data === undefined) {
return m('h2', 'Loading');
}
@@ -313,7 +339,7 @@
return m(
DetailsShell,
{
- title: this.getTitle(),
+ title: 'EventLatency',
},
m(
GridLayout,
@@ -326,12 +352,4 @@
),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return !this.loaded;
- }
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts
index 81175e4..1df0c75 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts
@@ -12,35 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {globals} from '../../frontend/globals';
import {NamedRow} from '../../frontend/named_slice_track';
-import {NewTrackArgs} from '../../frontend/track';
-import {SCROLL_JANK_V3_TRACK_KIND} from '../../public/track_kinds';
import {Slice} from '../../public/track';
import {
- CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../../frontend/tracks/custom_sql_table_slice_track';
import {JANK_COLOR} from './jank_colors';
-import {ScrollJankV3DetailsPanel} from './scroll_jank_v3_details_panel';
import {getColorForSlice} from '../../core/colorizer';
-import {ScrollJankPluginState} from './common';
const UNKNOWN_SLICE_NAME = 'Unknown';
const JANK_SLICE_NAME = ' Jank';
export class ScrollJankV3Track extends CustomSqlTableSliceTrack {
- constructor(args: NewTrackArgs) {
- super(args);
- ScrollJankPluginState.getInstance().registerTrack({
- kind: SCROLL_JANK_V3_TRACK_KIND,
- trackUri: this.uri,
- tableName: this.tableName,
- detailsPanelConfig: this.getDetailsPanel(),
- });
- }
-
getSqlDataSource(): CustomSqlTableDefConfig {
return {
columns: [
@@ -58,23 +42,6 @@
};
}
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- return {
- kind: ScrollJankV3DetailsPanel.kind,
- config: {
- sqlTableName: 'chrome_janky_frame_presentation_intervals',
- title: 'Chrome Scroll Janks',
- },
- };
- }
-
- async onDestroy(): Promise<void> {
- await super.onDestroy();
- ScrollJankPluginState.getInstance().unregisterTrack(
- SCROLL_JANK_V3_TRACK_KIND,
- );
- }
-
rowToSlice(row: NamedRow): Slice {
const slice = super.rowToSlice(row);
@@ -92,20 +59,4 @@
return {...slice, colorScheme: getColorForSlice(stage)};
}
}
-
- onUpdatedSlices(slices: Slice[]) {
- for (const slice of slices) {
- const currentSelection = globals.selectionManager.legacySelection;
- const isSelected =
- currentSelection &&
- currentSelection.kind === 'GENERIC_SLICE' &&
- currentSelection.id !== undefined &&
- currentSelection.id === slice.id;
-
- const highlighted = globals.state.highlightedSliceId === slice.id;
- const hasFocus = highlighted || isSelected;
- slice.isHighlighted = !!hasFocus;
- }
- super.onUpdatedSlices(slices);
- }
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts
index 1ee7d2c..fcd20fd 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts
@@ -12,51 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {NewTrackArgs} from '../../frontend/track';
-import {CHROME_TOPLEVEL_SCROLLS_KIND} from '../../public/track_kinds';
import {
- CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../../frontend/tracks/custom_sql_table_slice_track';
-import {ScrollJankPluginState} from './common';
-import {ScrollDetailsPanel} from './scroll_details_panel';
export class TopLevelScrollTrack extends CustomSqlTableSliceTrack {
- public static kind = CHROME_TOPLEVEL_SCROLLS_KIND;
-
getSqlDataSource(): CustomSqlTableDefConfig {
return {
columns: [`printf("Scroll %s", CAST(id AS STRING)) AS name`, '*'],
sqlTableName: 'chrome_scrolls',
};
}
-
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- return {
- kind: ScrollDetailsPanel.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Top Level Scrolls',
- },
- };
- }
-
- constructor(args: NewTrackArgs) {
- super(args);
-
- ScrollJankPluginState.getInstance().registerTrack({
- kind: TopLevelScrollTrack.kind,
- trackUri: this.uri,
- tableName: this.tableName,
- detailsPanelConfig: this.getDetailsPanel(),
- });
- }
-
- async onDestroy(): Promise<void> {
- await super.onDestroy();
- ScrollJankPluginState.getInstance().unregisterTrack(
- TopLevelScrollTrack.kind,
- );
- }
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/selection_utils.ts b/ui/src/core_plugins/chrome_scroll_jank/selection_utils.ts
new file mode 100644
index 0000000..4b79e05
--- /dev/null
+++ b/ui/src/core_plugins/chrome_scroll_jank/selection_utils.ts
@@ -0,0 +1,42 @@
+// Copyright (C) 2024 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 m from 'mithril';
+import {Anchor} from '../../widgets/anchor';
+import {Icons} from '../../base/semantic_icons';
+import {Trace} from '../../public/trace';
+
+export const SCROLLS_TRACK_URI = 'perfetto.ChromeScrollJank#toplevelScrolls';
+export const EVENT_LATENCY_TRACK_URI = 'perfetto.ChromeScrollJank#eventLatency';
+export const JANKS_TRACK_URI = 'perfetto.ChromeScrollJank#scrollJankV3';
+
+export function renderSliceRef(args: {
+ trace: Trace;
+ id: number;
+ trackUri: string;
+ title: m.Children;
+}) {
+ return m(
+ Anchor,
+ {
+ icon: Icons.UpdateSelection,
+ onclick: () => {
+ args.trace.selection.selectTrackEvent(args.trackUri, args.id, {
+ scrollToSelection: true,
+ });
+ },
+ },
+ args.title,
+ );
+}
diff --git a/ui/src/core_plugins/chrome_tasks/details.ts b/ui/src/core_plugins/chrome_tasks/details.ts
index 56acae1..ac96447 100644
--- a/ui/src/core_plugins/chrome_tasks/details.ts
+++ b/ui/src/core_plugins/chrome_tasks/details.ts
@@ -13,25 +13,21 @@
// limitations under the License.
import m from 'mithril';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {
Details,
DetailsSchema,
} from '../../frontend/widgets/sql/details/details';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Trace} from '../../public/trace';
import d = DetailsSchema;
-export class ChromeTasksDetailsTab extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'org.chromium.ChromeTasks.TaskDetailsTab';
+export class ChromeTasksDetailsPanel implements TrackEventDetailsPanel {
+ private readonly data: Details;
- private data: Details;
-
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
-
- this.data = new Details(this.trace, 'chrome_tasks', this.config.id, {
+ constructor(trace: Trace, eventId: number) {
+ this.data = new Details(trace, 'chrome_tasks', eventId, {
'Task name': 'name',
'Start time': d.Timestamp('ts'),
'Duration': d.Interval('ts', 'dur'),
@@ -41,21 +37,13 @@
});
}
- viewTab() {
+ render() {
return m(
DetailsShell,
{
- title: this.getTitle(),
+ title: 'Chrome Tasks',
},
m(GridLayout, m(GridLayoutColumn, this.data.render())),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return this.data.isLoading();
- }
}
diff --git a/ui/src/core_plugins/chrome_tasks/index.ts b/ui/src/core_plugins/chrome_tasks/index.ts
index 513da9f..9c79a0d 100644
--- a/ui/src/core_plugins/chrome_tasks/index.ts
+++ b/ui/src/core_plugins/chrome_tasks/index.ts
@@ -12,18 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {uuidv4} from '../../base/uuid';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {addSqlTableTab} from '../../frontend/sql_table_tab_interface';
import {asUtid} from '../../trace_processor/sql_utils/core_types';
-import {BottomTabToSCSAdapter} from '../../public/utils';
import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result';
import {Trace} from '../../public/trace';
import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {ChromeTasksDetailsTab} from './details';
+import {ChromeTasksDetailsPanel} from './details';
import {chromeTasksTable} from './table';
import {ChromeTasksThreadTrack} from './track';
import {TrackNode} from '../../public/workspace';
+import {TrackEventSelection} from '../../public/selection';
class ChromeTasksPlugin implements PerfettoPlugin {
onActivate() {}
@@ -103,30 +101,14 @@
uri,
track: new ChromeTasksThreadTrack(ctx, uri, asUtid(utid)),
title,
+ detailsPanel: (sel: TrackEventSelection) => {
+ return new ChromeTasksDetailsPanel(ctx, sel.eventId);
+ },
});
const track = new TrackNode({uri, title});
group.addChildInOrder(track);
ctx.workspace.addChildInOrder(group);
}
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === ChromeTasksDetailsTab.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new ChromeTasksDetailsTab({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
}
}
diff --git a/ui/src/core_plugins/chrome_tasks/track.ts b/ui/src/core_plugins/chrome_tasks/track.ts
index e96735c..24203ea 100644
--- a/ui/src/core_plugins/chrome_tasks/track.ts
+++ b/ui/src/core_plugins/chrome_tasks/track.ts
@@ -14,11 +14,9 @@
import {Utid} from '../../trace_processor/sql_utils/core_types';
import {
- CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../../frontend/tracks/custom_sql_table_slice_track';
-import {ChromeTasksDetailsTab} from './details';
import {Trace} from '../../public/trace';
export class ChromeTasksThreadTrack extends CustomSqlTableSliceTrack {
@@ -37,14 +35,4 @@
whereClause: `utid = ${this.utid}`,
};
}
-
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- return {
- kind: ChromeTasksDetailsTab.kind,
- config: {
- sqlTableName: 'chrome_tasks',
- title: 'Chrome Tasks',
- },
- };
- }
}
diff --git a/ui/src/core_plugins/debug/index.ts b/ui/src/core_plugins/debug/index.ts
index 3a43977..3ff5fcb 100644
--- a/ui/src/core_plugins/debug/index.ts
+++ b/ui/src/core_plugins/debug/index.ts
@@ -12,16 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {uuidv4} from '../../base/uuid';
import {
addDebugCounterTrack,
addDebugSliceTrack,
} from '../../public/lib/debug_tracks/debug_tracks';
-import {BottomTabToSCSAdapter} from '../../public/utils';
import {Trace} from '../../public/trace';
import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {DebugSliceDetailsTab} from '../../public/lib/debug_tracks/details_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {Optional, exists} from '../../base/utils';
class DebugTracksPlugin implements PerfettoPlugin {
@@ -65,28 +61,6 @@
}
},
});
-
- // TODO(stevegolton): While debug tracks are in their current state, we rely
- // on this plugin to provide the details panel for them. In the future, this
- // details panel will become part of the debug track's definition.
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === DebugSliceDetailsTab.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new DebugSliceDetailsTab({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
}
}
diff --git a/ui/src/core_plugins/screenshots/index.ts b/ui/src/core_plugins/screenshots/index.ts
index b7e41e2..5c8ee65 100644
--- a/ui/src/core_plugins/screenshots/index.ts
+++ b/ui/src/core_plugins/screenshots/index.ts
@@ -12,14 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {uuidv4} from '../../base/uuid';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {TrackNode} from '../../public/workspace';
-import {BottomTabToSCSAdapter} from '../../public/utils';
import {NUM} from '../../trace_processor/query_result';
import {Trace} from '../../public/trace';
import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {ScreenshotTab} from './screenshot_panel';
+import {ScreenshotDetailsPanel} from './screenshot_panel';
import {ScreenshotsTrack} from './screenshots_track';
class ScreenshotsPlugin implements PerfettoPlugin {
@@ -45,28 +42,10 @@
tags: {
kind: ScreenshotsTrack.kind,
},
+ detailsPanel: () => new ScreenshotDetailsPanel(ctx.engine),
});
const trackNode = new TrackNode({uri, title, sortOrder: -60});
ctx.workspace.addChildInOrder(trackNode);
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === ScreenshotTab.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new ScreenshotTab({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
}
}
}
diff --git a/ui/src/core_plugins/screenshots/screenshot_panel.ts b/ui/src/core_plugins/screenshots/screenshot_panel.ts
index a1d8cd4..a5424af 100644
--- a/ui/src/core_plugins/screenshots/screenshot_panel.ts
+++ b/ui/src/core_plugins/screenshots/screenshot_panel.ts
@@ -15,44 +15,25 @@
import m from 'mithril';
import {assertTrue} from '../../base/logging';
import {exists} from '../../base/utils';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {getSlice, SliceDetails} from '../../trace_processor/sql_utils/slice';
import {asSliceSqlId} from '../../trace_processor/sql_utils/core_types';
import {Engine} from '../../trace_processor/engine';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {TrackEventSelection} from '../../public/selection';
-async function getSliceDetails(
- engine: Engine,
- id: number,
-): Promise<SliceDetails | undefined> {
- return getSlice(engine, asSliceSqlId(id));
-}
-
-export class ScreenshotTab extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'dev.perfetto.ScreenshotDetailsPanel';
-
+export class ScreenshotDetailsPanel implements TrackEventDetailsPanel {
private sliceDetails?: SliceDetails;
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): ScreenshotTab {
- return new ScreenshotTab(args);
- }
+ constructor(private readonly engine: Engine) {}
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- getSliceDetails(this.engine, this.config.id).then(
- (sliceDetails) => (this.sliceDetails = sliceDetails),
+ async load(selection: TrackEventSelection) {
+ this.sliceDetails = await getSlice(
+ this.engine,
+ asSliceSqlId(selection.eventId),
);
}
- renderTabCanvas() {}
-
- getTitle() {
- return this.config.title;
- }
-
- viewTab() {
+ render() {
if (
!exists(this.sliceDetails) ||
!exists(this.sliceDetails.args) ||
diff --git a/ui/src/core_plugins/screenshots/screenshots_track.ts b/ui/src/core_plugins/screenshots/screenshots_track.ts
index d88010e..e9decdc 100644
--- a/ui/src/core_plugins/screenshots/screenshots_track.ts
+++ b/ui/src/core_plugins/screenshots/screenshots_track.ts
@@ -13,11 +13,9 @@
// limitations under the License.
import {
- CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../../frontend/tracks/custom_sql_table_slice_track';
-import {ScreenshotTab} from './screenshot_panel';
export class ScreenshotsTrack extends CustomSqlTableSliceTrack {
static readonly kind = 'dev.perfetto.ScreenshotsTrack';
@@ -28,14 +26,4 @@
columns: ['*'],
};
}
-
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- return {
- kind: ScreenshotTab.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Screenshots',
- },
- };
- }
}
diff --git a/ui/src/core_plugins/test_plugin/index.ts b/ui/src/core_plugins/test_plugin/index.ts
index 2b7ccf1..0979836 100644
--- a/ui/src/core_plugins/test_plugin/index.ts
+++ b/ui/src/core_plugins/test_plugin/index.ts
@@ -19,6 +19,7 @@
SimpleSliceTrackConfig,
} from '../../frontend/simple_slice_track';
import {TrackNode} from '../../public/workspace';
+import {DebugSliceDetailsPanel} from '../../public/lib/debug_tracks/details_tab';
class Plugin implements PerfettoPlugin {
async onTraceLoad(ctx: Trace): Promise<void> {
@@ -49,10 +50,13 @@
const title = 'Test Track';
const uri = `/test_track`;
+ const track = new SimpleSliceTrack(ctx, {trackUri: uri}, config);
ctx.tracks.registerTrack({
uri,
title,
- track: new SimpleSliceTrack(ctx, {trackUri: uri}, config),
+ track,
+ detailsPanel: ({eventId}) =>
+ new DebugSliceDetailsPanel(ctx, track.sqlTableName, eventId),
});
this.addNestedTracks(ctx, uri);
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index 79aae89..18005df 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -21,7 +21,7 @@
import {cropText} from '../base/string_utils';
import {colorCompare} from '../public/color';
import {UNEXPECTED_PINK} from '../core/colorizer';
-import {LegacySelection, TrackEventDetails} from '../public/selection';
+import {TrackEventDetails} from '../public/selection';
import {featureFlags} from '../core/feature_flags';
import {raf} from '../core/raf_scheduler';
import {Track} from '../public/track';
@@ -277,10 +277,6 @@
}
}
- protected isSelectionHandled(_selection: LegacySelection): boolean {
- return false;
- }
-
private getTitleFont(): string {
const size = this.sliceLayout.titleSizePx ?? 12;
return `${size}px Roboto Condensed`;
@@ -397,21 +393,11 @@
visibleWindow.end.toTime('ceil'),
);
- let selectedId: number | undefined = undefined;
const selection = globals.selectionManager.selection;
- switch (selection.kind) {
- case 'track_event':
- if (selection.trackUri === this.uri) {
- selectedId = selection.eventId;
- }
- break;
- case 'legacy':
- const legacySelection = selection.legacySelection;
- if (this.isSelectionHandled(legacySelection)) {
- selectedId = (legacySelection as {id: number}).id;
- }
- break;
- }
+ const selectedId =
+ selection.kind === 'track_event' && selection.trackUri === this.uri
+ ? selection.eventId
+ : undefined;
if (selectedId === undefined) {
this.selectedSlice = undefined;
diff --git a/ui/src/frontend/generic_slice_details_tab.ts b/ui/src/frontend/generic_slice_details_tab.ts
index 289e217..2389134 100644
--- a/ui/src/frontend/generic_slice_details_tab.ts
+++ b/ui/src/frontend/generic_slice_details_tab.ts
@@ -13,8 +13,7 @@
// limitations under the License.
import m from 'mithril';
-import {GenericSliceDetailsTabConfig} from '../public/details_panel';
-import {raf} from '../core/raf_scheduler';
+import {Columns, TrackEventDetailsPanel} from '../public/details_panel';
import {ColumnType} from '../trace_processor/query_result';
import {sqlValueToReadableString} from '../trace_processor/sql_utils';
import {DetailsShell} from '../widgets/details_shell';
@@ -22,7 +21,7 @@
import {Section} from '../widgets/section';
import {SqlRef} from '../widgets/sql_ref';
import {dictToTree, Tree, TreeNode} from '../widgets/tree';
-import {BottomTab, NewBottomTabArgs} from '../public/lib/bottom_tab';
+import {Trace} from '../public/trace';
export {
ColumnConfig,
@@ -34,41 +33,36 @@
// A details tab, which fetches slice-like object from a given SQL table by id
// and renders it according to the provided config, specifying which columns
// need to be rendered and how.
-export class GenericSliceDetailsTab extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'dev.perfetto.GenericSliceDetailsTab';
+export class GenericSliceDetailsTab implements TrackEventDetailsPanel {
+ private data?: {[key: string]: ColumnType};
- data: {[key: string]: ColumnType} | undefined;
+ constructor(
+ private readonly trace: Trace,
+ private readonly sqlTableName: string,
+ private readonly id: number,
+ private readonly title: string,
+ private readonly columns?: Columns,
+ ) {}
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): GenericSliceDetailsTab {
- return new GenericSliceDetailsTab(args);
+ async load() {
+ const result = await this.trace.engine.query(
+ `select * from ${this.sqlTableName} where id = ${this.id}`,
+ );
+
+ this.data = result.firstRow({});
}
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
-
- this.engine
- .query(
- `select * from ${this.config.sqlTableName} where id = ${this.config.id}`,
- )
- .then((queryResult) => {
- this.data = queryResult.firstRow({});
- raf.scheduleFullRedraw();
- });
- }
-
- viewTab() {
- if (this.data === undefined) {
+ render() {
+ if (!this.data) {
return m('h2', 'Loading');
}
const args: {[key: string]: m.Child} = {};
- if (this.config.columns !== undefined) {
- for (const key of Object.keys(this.config.columns)) {
+ if (this.columns !== undefined) {
+ for (const key of Object.keys(this.columns)) {
let argKey = key;
- if (this.config.columns[key].displayName !== undefined) {
- argKey = this.config.columns[key].displayName!;
+ if (this.columns[key].displayName !== undefined) {
+ argKey = this.columns[key].displayName!;
}
args[argKey] = sqlValueToReadableString(this.data[key]);
}
@@ -83,7 +77,7 @@
return m(
DetailsShell,
{
- title: this.config.title,
+ title: this.title,
},
m(
GridLayout,
@@ -95,8 +89,8 @@
m(TreeNode, {
left: 'SQL ID',
right: m(SqlRef, {
- table: this.config.sqlTableName,
- id: this.config.id,
+ table: this.sqlTableName,
+ id: this.id,
}),
}),
]),
@@ -104,12 +98,4 @@
),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return this.data === undefined;
- }
}
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index e4a9a46..b58e5f9 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -14,7 +14,6 @@
import {assertExists} from '../base/logging';
import {createStore, Store} from '../base/store';
-import {time} from '../base/time';
import {Actions, DeferredAction} from '../common/actions';
import {CommandManagerImpl} from '../core/command_manager';
import {
@@ -37,13 +36,6 @@
type DispatchMultiple = (actions: DeferredAction[]) => void;
type TrackDataStore = Map<string, {}>;
-export interface QuantizedLoad {
- start: time;
- end: time;
- load: number;
-}
-type OverviewStore = Map<string, QuantizedLoad[]>;
-
export interface ThreadDesc {
utid: number;
tid: number;
@@ -68,7 +60,6 @@
// TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
private _trackDataStore?: TrackDataStore = undefined;
- private _overviewStore?: OverviewStore = undefined;
private _threadMap?: ThreadMap = undefined;
private _bufferUsage?: number = undefined;
private _recordingLog?: string = undefined;
@@ -122,7 +113,6 @@
// initialize() is only called ever once. (But then i'm going to kill this
// entire file soon).
this._trackDataStore = new Map<string, {}>();
- this._overviewStore = new Map<string, QuantizedLoad[]>();
this._threadMap = new Map<number, ThreadDesc>();
}
@@ -188,10 +178,6 @@
return this.trace.traceInfo;
}
- get overviewStore(): OverviewStore {
- return assertExists(this._overviewStore);
- }
-
get trackDataStore(): TrackDataStore {
return assertExists(this._trackDataStore);
}
diff --git a/ui/src/frontend/overview_timeline_panel.ts b/ui/src/frontend/overview_timeline_panel.ts
index 3545ba1..719746a 100644
--- a/ui/src/frontend/overview_timeline_panel.ts
+++ b/ui/src/frontend/overview_timeline_panel.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import m from 'mithril';
-import {Time, TimeSpan, time} from '../base/time';
+import {Duration, Time, TimeSpan, duration, time} from '../base/time';
import {colorForCpu} from '../core/colorizer';
import {timestampFormat, TimestampFormat} from '../core/timestamp_format';
import {
@@ -25,7 +25,6 @@
import {InnerDragStrategy} from './drag/inner_drag_strategy';
import {OuterDragStrategy} from './drag/outer_drag_strategy';
import {DragGestureHandler} from '../base/drag_gesture_handler';
-import {globals} from './globals';
import {
getMaxMajorTicks,
MIN_PX_PER_STEP,
@@ -36,23 +35,37 @@
import {Panel} from './panel_container';
import {TimeScale} from '../base/time_scale';
import {HighPrecisionTimeSpan} from '../base/high_precision_time_span';
+import {TraceImpl} from '../core/trace_impl';
+import {LONG, NUM} from '../trace_processor/query_result';
+import {raf} from '../core/raf_scheduler';
+import {getOrCreate} from '../base/utils';
+
+const tracesData = new WeakMap<TraceImpl, OverviewDataLoader>();
export class OverviewTimelinePanel implements Panel {
private static HANDLE_SIZE_PX = 5;
readonly kind = 'panel';
readonly selectable = false;
-
private width = 0;
private gesture?: DragGestureHandler;
private timeScale?: TimeScale;
private dragStrategy?: DragStrategy;
private readonly boundOnMouseMove = this.onMouseMove.bind(this);
+ private readonly overviewData: OverviewDataLoader;
+
+ constructor(private trace: TraceImpl) {
+ this.overviewData = getOrCreate(
+ tracesData,
+ trace,
+ () => new OverviewDataLoader(trace),
+ );
+ }
// Must explicitly type now; arguments types are no longer auto-inferred.
// https://github.com/Microsoft/TypeScript/issues/1373
onupdate({dom}: m.CVnodeDOM) {
this.width = dom.getBoundingClientRect().width;
- const traceTime = globals.traceContext;
+ const traceTime = this.trace.traceInfo;
if (this.width > TRACK_SHELL_WIDTH) {
const pxBounds = {left: TRACK_SHELL_WIDTH, right: this.width};
const hpTraceTime = HighPrecisionTimeSpan.fromTime(
@@ -103,16 +116,17 @@
renderCanvas(ctx: CanvasRenderingContext2D, size: Size2D) {
if (this.width === undefined) return;
if (this.timeScale === undefined) return;
+
const headerHeight = 20;
const tracksHeight = size.height - headerHeight;
const traceContext = new TimeSpan(
- globals.traceContext.start,
- globals.traceContext.end,
+ this.trace.traceInfo.start,
+ this.trace.traceInfo.end,
);
if (size.width > TRACK_SHELL_WIDTH && traceContext.duration > 0n) {
const maxMajorTicks = getMaxMajorTicks(this.width - TRACK_SHELL_WIDTH);
- const offset = globals.trace.timeline.timestampOffset();
+ const offset = this.trace.timeline.timestampOffset();
const tickGen = generateTicks(traceContext, maxMajorTicks, offset);
// Draw time labels
@@ -124,7 +138,7 @@
if (xPos > this.width) break;
if (type === TickType.MAJOR) {
ctx.fillRect(xPos - 1, 0, 1, headerHeight - 5);
- const domainTime = globals.trace.timeline.toDomainTime(time);
+ const domainTime = this.trace.timeline.toDomainTime(time);
renderTimestamp(ctx, domainTime, xPos + 5, 18, MIN_PX_PER_STEP);
} else if (type == TickType.MEDIUM) {
ctx.fillRect(xPos - 1, 0, 1, 8);
@@ -135,12 +149,13 @@
}
// Draw mini-tracks with quanitzed density for each process.
- if (globals.overviewStore.size > 0) {
- const numTracks = globals.overviewStore.size;
+ const overviewData = this.overviewData.overviewData;
+ if (overviewData.size > 0) {
+ const numTracks = overviewData.size;
let y = 0;
const trackHeight = (tracksHeight - 1) / numTracks;
- for (const key of globals.overviewStore.keys()) {
- const loads = globals.overviewStore.get(key)!;
+ for (const key of overviewData.keys()) {
+ const loads = overviewData.get(key)!;
for (let i = 0; i < loads.length; i++) {
const xStart = Math.floor(this.timeScale.timeToPx(loads[i].start));
const xEnd = Math.ceil(this.timeScale.timeToPx(loads[i].end));
@@ -159,9 +174,7 @@
ctx.fillRect(0, size.height - 1, this.width, 1);
// Draw semi-opaque rects that occlude the non-visible time range.
- const [vizStartPx, vizEndPx] = OverviewTimelinePanel.extractBounds(
- this.timeScale,
- );
+ const [vizStartPx, vizEndPx] = this.extractBounds(this.timeScale);
ctx.fillStyle = OVERVIEW_TIMELINE_NON_VISIBLE_COLOR;
ctx.fillRect(
@@ -203,9 +216,7 @@
private chooseCursor(x: number) {
if (this.timeScale === undefined) return 'default';
- const [startBound, endBound] = OverviewTimelinePanel.extractBounds(
- this.timeScale,
- );
+ const [startBound, endBound] = this.extractBounds(this.timeScale);
if (
OverviewTimelinePanel.inBorderRange(x, startBound) ||
OverviewTimelinePanel.inBorderRange(x, endBound)
@@ -227,7 +238,7 @@
onDragStart(x: number) {
if (this.timeScale === undefined) return;
- const pixelBounds = OverviewTimelinePanel.extractBounds(this.timeScale);
+ const pixelBounds = this.extractBounds(this.timeScale);
if (
OverviewTimelinePanel.inBorderRange(x, pixelBounds[0]) ||
OverviewTimelinePanel.inBorderRange(x, pixelBounds[1])
@@ -245,8 +256,8 @@
this.dragStrategy = undefined;
}
- private static extractBounds(timeScale: TimeScale): [number, number] {
- const vizTime = globals.timeline.visibleWindow;
+ private extractBounds(timeScale: TimeScale): [number, number] {
+ const vizTime = this.trace.timeline.visibleWindow;
return [
Math.floor(timeScale.hpTimeToPx(vizTime.start)),
Math.ceil(timeScale.hpTimeToPx(vizTime.end)),
@@ -308,3 +319,126 @@
const {dhhmmss} = timecode;
ctx.fillText(dhhmmss, x, y, minWidth);
}
+
+interface QuantizedLoad {
+ start: time;
+ end: time;
+ load: number;
+}
+
+// Kicks of a sequence of promises that load the overiew data in steps.
+// Each step schedules an animation frame.
+class OverviewDataLoader {
+ overviewData = new Map<string, QuantizedLoad[]>();
+
+ constructor(private trace: TraceImpl) {
+ this.beginLoad();
+ }
+
+ async beginLoad() {
+ const traceSpan = new TimeSpan(
+ this.trace.traceInfo.start,
+ this.trace.traceInfo.end,
+ );
+ const engine = this.trace.engine;
+ const stepSize = Duration.max(1n, traceSpan.duration / 100n);
+ const hasSchedSql = 'select ts from sched limit 1';
+ const hasSchedOverview = (await engine.query(hasSchedSql)).numRows() > 0;
+ if (hasSchedOverview) {
+ await this.loadSchedOverview(traceSpan, stepSize);
+ } else {
+ await this.loadSliceOverview(traceSpan, stepSize);
+ }
+ }
+
+ async loadSchedOverview(traceSpan: TimeSpan, stepSize: duration) {
+ const stepPromises = [];
+ for (
+ let start = traceSpan.start;
+ start < traceSpan.end;
+ start = Time.add(start, stepSize)
+ ) {
+ const progress = start - traceSpan.start;
+ const ratio = Number(progress) / Number(traceSpan.duration);
+ this.trace.omnibox.showStatusMessage(
+ 'Loading overview ' + `${Math.round(ratio * 100)}%`,
+ );
+ const end = Time.add(start, stepSize);
+ // The (async() => {})() queues all the 100 async promises in one batch.
+ // Without that, we would wait for each step to be rendered before
+ // kicking off the next one. That would interleave an animation frame
+ // between each step, slowing down significantly the overall process.
+ stepPromises.push(
+ (async () => {
+ const schedResult = await this.trace.engine.query(
+ `select cast(sum(dur) as float)/${stepSize} as load, cpu from sched ` +
+ `where ts >= ${start} and ts < ${end} and utid != 0 ` +
+ 'group by cpu order by cpu',
+ );
+ const schedData: {[key: string]: QuantizedLoad} = {};
+ const it = schedResult.iter({load: NUM, cpu: NUM});
+ for (; it.valid(); it.next()) {
+ const load = it.load;
+ const cpu = it.cpu;
+ schedData[cpu] = {start, end, load};
+ }
+ this.appendData(schedData);
+ })(),
+ );
+ } // for(start = ...)
+ await Promise.all(stepPromises);
+ }
+
+ async loadSliceOverview(traceSpan: TimeSpan, stepSize: duration) {
+ // Slices overview.
+ const sliceResult = await this.trace.engine.query(`select
+ bucket,
+ upid,
+ ifnull(sum(utid_sum) / cast(${stepSize} as float), 0) as load
+ from thread
+ inner join (
+ select
+ ifnull(cast((ts - ${traceSpan.start})/${stepSize} as int), 0) as bucket,
+ sum(dur) as utid_sum,
+ utid
+ from slice
+ inner join thread_track on slice.track_id = thread_track.id
+ group by bucket, utid
+ ) using(utid)
+ where upid is not null
+ group by bucket, upid`);
+
+ const slicesData: {[key: string]: QuantizedLoad[]} = {};
+ const it = sliceResult.iter({bucket: LONG, upid: NUM, load: NUM});
+ for (; it.valid(); it.next()) {
+ const bucket = it.bucket;
+ const upid = it.upid;
+ const load = it.load;
+
+ const start = Time.add(traceSpan.start, stepSize * bucket);
+ const end = Time.add(start, stepSize);
+
+ const upidStr = upid.toString();
+ let loadArray = slicesData[upidStr];
+ if (loadArray === undefined) {
+ loadArray = slicesData[upidStr] = [];
+ }
+ loadArray.push({start, end, load});
+ }
+ this.appendData(slicesData);
+ }
+
+ appendData(data: {[key: string]: QuantizedLoad | QuantizedLoad[]}) {
+ for (const [key, value] of Object.entries(data)) {
+ if (!this.overviewData.has(key)) {
+ this.overviewData.set(key, []);
+ }
+ if (value instanceof Array) {
+ this.overviewData.get(key)!.push(...value);
+ } else {
+ this.overviewData.get(key)!.push(value);
+ }
+ }
+ raf.scheduleRedraw();
+ }
+}
diff --git a/ui/src/frontend/publish.ts b/ui/src/frontend/publish.ts
index ab30a19..a32cb86 100644
--- a/ui/src/frontend/publish.ts
+++ b/ui/src/frontend/publish.ts
@@ -15,28 +15,7 @@
import {ConversionJobStatusUpdate} from '../common/conversion_jobs';
import {raf} from '../core/raf_scheduler';
import {HttpRpcState} from '../trace_processor/http_rpc_engine';
-import {globals, QuantizedLoad, ThreadDesc} from './globals';
-
-export function publishOverviewData(data: {
- [key: string]: QuantizedLoad | QuantizedLoad[];
-}) {
- for (const [key, value] of Object.entries(data)) {
- if (!globals.overviewStore.has(key)) {
- globals.overviewStore.set(key, []);
- }
- if (value instanceof Array) {
- globals.overviewStore.get(key)!.push(...value);
- } else {
- globals.overviewStore.get(key)!.push(value);
- }
- }
- raf.scheduleRedraw();
-}
-
-export function clearOverviewData() {
- globals.overviewStore.clear();
- raf.scheduleRedraw();
-}
+import {globals, ThreadDesc} from './globals';
export function publishTrackData(args: {id: string; data: {}}) {
globals.setTrackData(args.id, args.data);
diff --git a/ui/src/frontend/simple_slice_track.ts b/ui/src/frontend/simple_slice_track.ts
index 484b834..ccc5ceb 100644
--- a/ui/src/frontend/simple_slice_track.ts
+++ b/ui/src/frontend/simple_slice_track.ts
@@ -14,7 +14,6 @@
import {TrackContext} from '../public/track';
import {
- CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from './tracks/custom_sql_table_slice_track';
@@ -23,10 +22,7 @@
SqlDataSource,
} from '../public/lib/debug_tracks/debug_tracks';
import {uuidv4Sql} from '../base/uuid';
-import {
- ARG_PREFIX,
- DebugSliceDetailsTab,
-} from '../public/lib/debug_tracks/details_tab';
+import {ARG_PREFIX} from '../public/lib/debug_tracks/details_tab';
import {createPerfettoTable} from '../trace_processor/sql_utils';
import {Trace} from '../public/trace';
@@ -38,7 +34,7 @@
export class SimpleSliceTrack extends CustomSqlTableSliceTrack {
private config: SimpleSliceTrackConfig;
- private sqlTableName: string;
+ public readonly sqlTableName: string;
constructor(trace: Trace, ctx: TrackContext, config: SimpleSliceTrackConfig) {
super({
@@ -66,18 +62,6 @@
};
}
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- // We currently borrow the debug slice details tab.
- // TODO: Don't do this!
- return {
- kind: DebugSliceDetailsTab.kind,
- config: {
- sqlTableName: this.sqlTableName,
- title: 'Debug Slice',
- },
- };
- }
-
private createTableQuery(
data: SqlDataSource,
sliceColumns: SliceColumns,
diff --git a/ui/src/frontend/tracks/custom_sql_table_slice_track.ts b/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
index f97142b..6b7fa59 100644
--- a/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
+++ b/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
@@ -13,10 +13,7 @@
// limitations under the License.
import {generateSqlWithInternalLayout} from '../../common/internal_layout_utils';
-import {LegacySelection} from '../../public/selection';
-import {OnSliceClickArgs} from '../base_slice_track';
import {GenericSliceDetailsTabConfigBase} from '../generic_slice_details_tab';
-import {globals} from '../globals';
import {NAMED_ROW, NamedRow, NamedSliceTrack} from '../named_slice_track';
import {NewTrackArgs} from '../track';
import {createView} from '../../trace_processor/sql_utils';
@@ -69,11 +66,6 @@
| CustomSqlTableDefConfig
| Promise<CustomSqlTableDefConfig>;
- // Override by subclasses.
- abstract getDetailsPanel(
- args: OnSliceClickArgs<Slice>,
- ): CustomSqlDetailsPanelConfig;
-
getSqlImports(): CustomSqlImportConfig {
return {
modules: [] as string[],
@@ -109,32 +101,6 @@
return `SELECT * FROM ${this.tableName}`;
}
- isSelectionHandled(selection: LegacySelection) {
- if (selection.kind !== 'GENERIC_SLICE') {
- return false;
- }
- return selection.trackUri === this.uri;
- }
-
- onSliceClick(args: OnSliceClickArgs<Slice>) {
- if (this.getDetailsPanel(args) === undefined) {
- return;
- }
-
- const detailsPanelConfig = this.getDetailsPanel(args);
- globals.selectionManager.selectGenericSlice({
- id: args.slice.id,
- sqlTableName: this.tableName,
- start: args.slice.ts,
- duration: args.slice.dur,
- trackUri: this.uri,
- detailsPanelConfig: {
- kind: detailsPanelConfig.kind,
- config: detailsPanelConfig.config,
- },
- });
- }
-
async loadImports() {
for (const importModule of this.getSqlImports().modules) {
await this.engine.query(`INCLUDE PERFETTO MODULE ${importModule};`);
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 46908d9..68645c4 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -91,7 +91,7 @@
// Used to prevent global deselection if a pan/drag select occurred.
private keepCurrentSelection = false;
- private overviewTimelinePanel = new OverviewTimelinePanel();
+ private overviewTimelinePanel: OverviewTimelinePanel;
private timeAxisPanel = new TimeAxisPanel();
private timeSelectionPanel = new TimeSelectionPanel();
private notesPanel = new NotesPanel();
@@ -102,6 +102,7 @@
constructor(vnode: m.CVnode<PageWithTraceAttrs>) {
this.tickmarkPanel = new TickmarkPanel(vnode.attrs.trace);
+ this.overviewTimelinePanel = new OverviewTimelinePanel(vnode.attrs.trace);
}
oncreate(vnode: m.CVnodeDOM<PageWithTraceAttrs>) {
diff --git a/ui/src/plugins/com.android.InputEvents/index.ts b/ui/src/plugins/com.android.InputEvents/index.ts
index 7a529d3..9116ad3 100644
--- a/ui/src/plugins/com.android.InputEvents/index.ts
+++ b/ui/src/plugins/com.android.InputEvents/index.ts
@@ -21,6 +21,7 @@
} from '../../frontend/simple_slice_track';
import {TrackNode} from '../../public/workspace';
import {getOrCreateUserInteractionGroup} from '../../public/standard_groups';
+import {DebugSliceDetailsPanel} from '../../public/lib/debug_tracks/details_tab';
class InputEvents implements PerfettoPlugin {
private readonly SQL_SOURCE = `
@@ -33,12 +34,12 @@
`;
async onTraceLoad(ctx: Trace): Promise<void> {
- const cnt = await(ctx.engine.query(`
+ const cnt = await ctx.engine.query(`
SELECT
count(*) as cnt
FROM slice
WHERE name GLOB 'UnwantedInteractionBlocker::notifyMotion*'
- `));
+ `);
if (cnt.firstRow({cnt: LONG}).cnt == 0n) {
return;
}
@@ -51,13 +52,16 @@
columns: {ts: 'ts', dur: 'dur', name: 'name'},
argColumns: [],
};
- await ctx.engine.query("INCLUDE PERFETTO MODULE android.input;");
+ await ctx.engine.query('INCLUDE PERFETTO MODULE android.input;');
const uri = 'com.android.InputEvents#InputEventsTrack';
const title = 'Input Events';
+ const track = new SimpleSliceTrack(ctx, {trackUri: uri}, config);
ctx.tracks.registerTrack({
uri,
title: title,
- track: new SimpleSliceTrack(ctx, {trackUri: uri}, config)
+ track,
+ detailsPanel: ({eventId}) =>
+ new DebugSliceDetailsPanel(ctx, track.sqlTableName, eventId),
});
const node = new TrackNode({uri, title});
const group = getOrCreateUserInteractionGroup(ctx.workspace);
diff --git a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
index 6a2dd1e..0968acd 100644
--- a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
@@ -25,6 +25,7 @@
SimpleCounterTrackConfig,
} from '../../frontend/simple_counter_track';
import {TrackNode} from '../../public/workspace';
+import {DebugSliceDetailsPanel} from '../../public/lib/debug_tracks/details_tab';
interface ContainedTrace {
uuid: string;
@@ -1184,13 +1185,16 @@
};
const uri = `/long_battery_tracing_${name}`;
+ const track = new SimpleSliceTrack(ctx, {trackUri: uri}, config);
ctx.tracks.registerTrack({
uri,
title: name,
- track: new SimpleSliceTrack(ctx, {trackUri: uri}, config),
+ track,
+ detailsPanel: ({eventId}) =>
+ new DebugSliceDetailsPanel(ctx, track.sqlTableName, eventId),
});
- const track = new TrackNode({uri, title: name});
- this.addTrack(ctx, track, groupName);
+ const trackNode = new TrackNode({uri, title: name});
+ this.addTrack(ctx, trackNode, groupName);
}
addCounterTrack(
diff --git a/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts b/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts
index e734528..a53dd2a 100644
--- a/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts
@@ -20,6 +20,7 @@
SimpleSliceTrackConfig,
} from '../../frontend/simple_slice_track';
import {TrackNode} from '../../public/workspace';
+import {DebugSliceDetailsPanel} from '../../public/lib/debug_tracks/details_tab';
class AndroidStartup implements PerfettoPlugin {
async onTraceLoad(ctx: Trace): Promise<void> {
const e = ctx.engine;
@@ -43,13 +44,16 @@
};
const uri = `/android_startups`;
const title = 'Android App Startups';
+ const track = new SimpleSliceTrack(ctx, {trackUri: uri}, config);
ctx.tracks.registerTrack({
uri,
title: 'Android App Startups',
- track: new SimpleSliceTrack(ctx, {trackUri: uri}, config),
+ track,
+ detailsPanel: ({eventId}) =>
+ new DebugSliceDetailsPanel(ctx, track.sqlTableName, eventId),
});
- const track = new TrackNode({title, uri});
- ctx.workspace.addChildInOrder(track);
+ const trackNode = new TrackNode({title, uri});
+ ctx.workspace.addChildInOrder(trackNode);
}
}
diff --git a/ui/src/plugins/dev.perfetto.TraceMetadata/index.ts b/ui/src/plugins/dev.perfetto.TraceMetadata/index.ts
index 4199c44..fc660e2 100644
--- a/ui/src/plugins/dev.perfetto.TraceMetadata/index.ts
+++ b/ui/src/plugins/dev.perfetto.TraceMetadata/index.ts
@@ -17,6 +17,7 @@
import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
import {SimpleSliceTrack} from '../../frontend/simple_slice_track';
import {TrackNode} from '../../public/workspace';
+import {DebugSliceDetailsPanel} from '../../public/lib/debug_tracks/details_tab';
class TraceMetadata implements PerfettoPlugin {
async onTraceLoad(ctx: Trace): Promise<void> {
const res = await ctx.engine.query(`
@@ -28,27 +29,30 @@
}
const uri = `/clock_snapshots`;
const title = 'Clock Snapshots';
+ const track = new SimpleSliceTrack(
+ ctx,
+ {trackUri: uri},
+ {
+ data: {
+ sqlSource: `
+ select ts, 0 as dur, 'Snapshot' as name
+ from clock_snapshot
+ `,
+ columns: ['ts', 'dur', 'name'],
+ },
+ columns: {ts: 'ts', dur: 'dur', name: 'name'},
+ argColumns: [],
+ },
+ );
ctx.tracks.registerTrack({
uri,
title,
- track: new SimpleSliceTrack(
- ctx,
- {trackUri: uri},
- {
- data: {
- sqlSource: `
- select ts, 0 as dur, 'Snapshot' as name
- from clock_snapshot
- `,
- columns: ['ts', 'dur', 'name'],
- },
- columns: {ts: 'ts', dur: 'dur', name: 'name'},
- argColumns: [],
- },
- ),
+ track,
+ detailsPanel: ({eventId}) =>
+ new DebugSliceDetailsPanel(ctx, track.sqlTableName, eventId),
});
- const track = new TrackNode({uri, title});
- ctx.workspace.addChildInOrder(track);
+ const trackNode = new TrackNode({uri, title});
+ ctx.workspace.addChildInOrder(trackNode);
}
}
diff --git a/ui/src/protos/index.ts b/ui/src/protos/index.ts
index 32f020b..6782599 100644
--- a/ui/src/protos/index.ts
+++ b/ui/src/protos/index.ts
@@ -71,8 +71,8 @@
import QueryServiceStateResponse = protos.perfetto.protos.QueryServiceStateResponse;
import ReadBuffersRequest = protos.perfetto.protos.ReadBuffersRequest;
import ReadBuffersResponse = protos.perfetto.protos.ReadBuffersResponse;
-import RegisterSqlModuleArgs = protos.perfetto.protos.RegisterSqlModuleArgs;
-import RegisterSqlModuleResult = protos.perfetto.protos.RegisterSqlModuleResult;
+import RegisterSqlPackageArgs = protos.perfetto.protos.RegisterSqlPackageArgs;
+import RegisterSqlPackageResult = protos.perfetto.protos.RegisterSqlPackageResult;
import ResetTraceProcessorArgs = protos.perfetto.protos.ResetTraceProcessorArgs;
import StatCounters = protos.perfetto.protos.SysStatsConfig.StatCounters;
import StatusResult = protos.perfetto.protos.StatusResult;
@@ -140,8 +140,8 @@
QueryServiceStateResponse,
ReadBuffersRequest,
ReadBuffersResponse,
- RegisterSqlModuleArgs,
- RegisterSqlModuleResult,
+ RegisterSqlPackageArgs,
+ RegisterSqlPackageResult,
ResetTraceProcessorArgs,
StatCounters,
StatusResult,
diff --git a/ui/src/public/lib/debug_tracks/debug_tracks.ts b/ui/src/public/lib/debug_tracks/debug_tracks.ts
index f85290f..387b8f2 100644
--- a/ui/src/public/lib/debug_tracks/debug_tracks.ts
+++ b/ui/src/public/lib/debug_tracks/debug_tracks.ts
@@ -19,9 +19,10 @@
sqlValueToReadableString,
} from '../../../trace_processor/sql_utils';
import {DebugCounterTrack} from './counter_track';
-import {ARG_PREFIX} from './details_tab';
+import {ARG_PREFIX, DebugSliceDetailsPanel} from './details_tab';
import {TrackNode} from '../../workspace';
import {Trace} from '../../trace';
+import {TrackEventSelection} from '../../selection';
let trackCounter = 0; // For reproducible ids.
@@ -123,6 +124,9 @@
uri,
title: trackName,
track: new DebugSliceTrack(trace, {trackUri: uri}, tableName),
+ detailsPanel: (sel: TrackEventSelection) => {
+ return new DebugSliceDetailsPanel(trace, tableName, sel.eventId);
+ },
});
// Create the actions to add this track to the tracklist
diff --git a/ui/src/public/lib/debug_tracks/details_tab.ts b/ui/src/public/lib/debug_tracks/details_tab.ts
index 615f842..fe7c61e 100644
--- a/ui/src/public/lib/debug_tracks/details_tab.ts
+++ b/ui/src/public/lib/debug_tracks/details_tab.ts
@@ -14,8 +14,6 @@
import m from 'mithril';
import {duration, Time, time} from '../../../base/time';
-import {BottomTab, NewBottomTabArgs} from '../bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../../frontend/generic_slice_details_tab';
import {hasArgs, renderArguments} from '../../../frontend/slice_args';
import {getSlice, SliceDetails} from '../../../trace_processor/sql_utils/slice';
import {
@@ -49,6 +47,8 @@
import {getThreadName} from '../../../trace_processor/sql_utils/thread';
import {getProcessName} from '../../../trace_processor/sql_utils/process';
import {sliceRef} from '../../../frontend/widgets/slice';
+import {TrackEventDetailsPanel} from '../../details_panel';
+import {Trace} from '../../trace';
export const ARG_PREFIX = 'arg_';
@@ -78,10 +78,8 @@
return children;
}
-export class DebugSliceDetailsTab extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'dev.perfetto.DebugSliceDetailsTab';
-
- data?: {
+export class DebugSliceDetailsPanel implements TrackEventDetailsPanel {
+ private data?: {
name: string;
ts: time;
dur: duration;
@@ -91,14 +89,14 @@
// tables. These values will be set if the relevant columns exist and
// are consistent (e.g. 'ts' and 'dur' for this slice correspond to values
// in these well-known tables).
- threadState?: ThreadState;
- slice?: SliceDetails;
+ private threadState?: ThreadState;
+ private slice?: SliceDetails;
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): DebugSliceDetailsTab {
- return new DebugSliceDetailsTab(args);
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly tableName: string,
+ private readonly eventId: number,
+ ) {}
private async maybeLoadThreadState(
id: number | undefined,
@@ -110,7 +108,7 @@
if (id === undefined) return undefined;
if (utid === undefined) return undefined;
- const threadState = await getThreadState(this.engine, id);
+ const threadState = await getThreadState(this.trace.engine, id);
if (threadState === undefined) return undefined;
if (
table === 'thread_state' ||
@@ -150,7 +148,7 @@
if (id === undefined) return undefined;
if (table !== 'slice' && trackId === undefined) return undefined;
- const slice = await getSlice(this.engine, asSliceSqlId(id));
+ const slice = await getSlice(this.trace.engine, asSliceSqlId(id));
if (slice === undefined) return undefined;
if (
table === 'slice' ||
@@ -193,9 +191,9 @@
);
}
- private async loadData() {
- const queryResult = await this.engine.query(
- `select * from ${this.config.sqlTableName} where id = ${this.config.id}`,
+ async load() {
+ const queryResult = await this.trace.engine.query(
+ `select * from ${this.tableName} where id = ${this.eventId}`,
);
const row = queryResult.firstRow({
ts: LONG,
@@ -237,12 +235,7 @@
this.trace.scheduleRedraw();
}
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- this.loadData();
- }
-
- viewTab() {
+ render() {
if (this.data === undefined) {
return m('h2', 'Loading');
}
@@ -250,7 +243,7 @@
'Name': this.data['name'] as string,
'Start time': m(Timestamp, {ts: timeFromSql(this.data['ts'])}),
'Duration': m(DurationWidget, {dur: durationFromSql(this.data['dur'])}),
- 'Debug slice id': `${this.config.sqlTableName}[${this.config.id}]`,
+ 'Debug slice id': `${this.tableName}[${this.eventId}]`,
});
details.push(this.renderThreadStateInfo());
details.push(this.renderSliceInfo());
diff --git a/ui/src/public/lib/debug_tracks/slice_track.ts b/ui/src/public/lib/debug_tracks/slice_track.ts
index 4cfc74f..214f32b 100644
--- a/ui/src/public/lib/debug_tracks/slice_track.ts
+++ b/ui/src/public/lib/debug_tracks/slice_track.ts
@@ -14,12 +14,10 @@
import m from 'mithril';
import {
- CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../../../frontend/tracks/custom_sql_table_slice_track';
import {TrackContext} from '../../track';
-import {DebugSliceDetailsTab} from './details_tab';
import {Button} from '../../../widgets/button';
import {Icons} from '../../../base/semantic_icons';
import {Trace} from '../../trace';
@@ -41,16 +39,6 @@
};
}
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- return {
- kind: DebugSliceDetailsTab.kind,
- config: {
- sqlTableName: this.sqlTableName,
- title: 'Debug Slice',
- },
- };
- }
-
getTrackShellButtons(): m.Children {
return m(Button, {
onclick: () => {
diff --git a/ui/src/public/selection.ts b/ui/src/public/selection.ts
index 4965910..69a1a87 100644
--- a/ui/src/public/selection.ts
+++ b/ui/src/public/selection.ts
@@ -16,12 +16,10 @@
import {Optional} from '../base/utils';
import {Engine} from '../trace_processor/engine';
import {ColumnDef, Sorting, ThreadStateExtra} from './aggregation';
-import {GenericSliceDetailsTabConfigBase} from './details_panel';
import {TrackDescriptor} from './track';
export interface SelectionManager {
readonly selection: Selection;
- readonly legacySelection: LegacySelection | null;
findTimeRangeOfSelection(): Optional<TimeSpan>;
clear(): void;
@@ -49,14 +47,6 @@
selectSqlEvent(sqlTableName: string, id: number, opts?: SelectionOpts): void;
/**
- * Select a legacy selection.
- *
- * @param selection - The legacy selection to select.
- * @param opts - Additional options.
- */
- selectLegacy(selection: LegacySelection, opts?: SelectionOpts): void;
-
- /**
* Create an area selection for the purposes of aggregation.
*
* @param args - The area to select.
@@ -67,20 +57,6 @@
scrollToCurrentSelection(): void;
registerAreaSelectionAggreagtor(aggr: AreaSelectionAggregator): void;
- // TODO(primiano): I don't undertsand what this generic slice is, but now
- // is exposed to plugins. For now i'm just carrying it forward.
- selectGenericSlice(args: {
- id: number;
- sqlTableName: string;
- start: time;
- duration: duration;
- trackUri: string;
- detailsPanelConfig: {
- kind: string;
- config: GenericSliceDetailsTabConfigBase;
- };
- }): void;
-
/**
* Register a new SQL selection resolver.
*
@@ -108,8 +84,7 @@
| AreaSelection
| NoteSelection
| UnionSelection
- | EmptySelection
- | LegacySelectionWrapper;
+ | EmptySelection;
/** Defines how changes to selection affect the rest of the UI state */
export interface SelectionOpts {
@@ -118,32 +93,6 @@
scrollToSelection?: boolean; // Default: false.
}
-// LEGACY Selection types:
-
-export interface LegacySelectionWrapper {
- readonly kind: 'legacy';
- readonly legacySelection: LegacySelection;
-}
-
-export type LegacySelection = GenericSliceSelection & {
- trackUri?: string;
-};
-
-export interface GenericSliceSelection {
- readonly kind: 'GENERIC_SLICE';
- readonly id: number;
- readonly sqlTableName: string;
- readonly start: time;
- readonly duration: duration;
- // NOTE: this config can be expanded for multiple details panel types.
- readonly detailsPanelConfig: {
- readonly kind: string;
- readonly config: GenericSliceDetailsTabConfigBase;
- };
-}
-
-// New Selection types:
-
export interface TrackEventSelection extends TrackEventDetails {
readonly kind: 'track_event';
readonly trackUri: string;
@@ -165,6 +114,7 @@
readonly utid?: number;
readonly tableName?: string;
readonly profileType?: ProfileType;
+ readonly interactionType?: string;
}
export interface Area {
diff --git a/ui/src/public/track.ts b/ui/src/public/track.ts
index 5f32e6b..d12faed 100644
--- a/ui/src/public/track.ts
+++ b/ui/src/public/track.ts
@@ -106,7 +106,7 @@
// Optional: A factory that returns a details panel object. This is called
// each time the selection is changed (and the selection is relevant to this
// track).
- readonly detailsPanel?: (id: TrackEventSelection) => TrackEventDetailsPanel;
+ readonly detailsPanel?: (sel: TrackEventSelection) => TrackEventDetailsPanel;
}
/**
diff --git a/ui/src/public/track_kinds.ts b/ui/src/public/track_kinds.ts
index 05671c9..e5df1ae 100644
--- a/ui/src/public/track_kinds.ts
+++ b/ui/src/public/track_kinds.ts
@@ -27,12 +27,4 @@
export const CPUSS_ESTIMATE_TRACK_KIND = 'CpuSubsystemEstimateTrack';
export const CPU_PROFILE_TRACK_KIND = 'CpuProfileTrack';
export const HEAP_PROFILE_TRACK_KIND = 'HeapProfileTrack';
-export const CHROME_TOPLEVEL_SCROLLS_KIND =
- 'org.chromium.TopLevelScrolls.scrolls';
-export const CHROME_EVENT_LATENCY_TRACK_KIND =
- 'org.chromium.ScrollJank.event_latencies';
-export const SCROLL_JANK_V3_TRACK_KIND =
- 'org.chromium.ScrollJank.scroll_jank_v3_track';
-export const CHROME_SCROLL_JANK_TRACK_KIND =
- 'org.chromium.ScrollJank.BrowserUIThreadLongTasks';
export const ANDROID_LOGS_TRACK_KIND = 'AndroidLogTrack';
diff --git a/ui/src/public/utils.ts b/ui/src/public/utils.ts
index 6e6fe02..208961a 100644
--- a/ui/src/public/utils.ts
+++ b/ui/src/public/utils.ts
@@ -13,11 +13,9 @@
// limitations under the License.
import m from 'mithril';
-import {LegacySelection, Selection} from '../public/selection';
import {BottomTab} from './lib/bottom_tab';
import {Tab} from './tab';
import {exists} from '../base/utils';
-import {DetailsPanel} from './details_panel';
import {Trace} from './trace';
import {TimeSpan} from '../base/time';
@@ -98,10 +96,6 @@
return 'Unknown';
}
-export interface BottomTabAdapterAttrs {
- tabFactory: (sel: LegacySelection) => BottomTab | undefined;
-}
-
/**
* This adapter wraps a BottomTab, converting it into a the new "current
* selection" API.
@@ -130,34 +124,6 @@
},
})
*/
-export class BottomTabToSCSAdapter implements DetailsPanel {
- private oldSelection?: Selection;
- private bottomTab?: BottomTab;
- private attrs: BottomTabAdapterAttrs;
-
- constructor(attrs: BottomTabAdapterAttrs) {
- this.attrs = attrs;
- }
-
- render(selection: Selection): m.Children {
- // Detect selection changes, assuming selection is immutable
- if (selection !== this.oldSelection) {
- this.oldSelection = selection;
- if (selection.kind === 'legacy') {
- this.bottomTab = this.attrs.tabFactory(selection.legacySelection);
- } else {
- this.bottomTab = undefined;
- }
- }
-
- return this.bottomTab?.renderPanel();
- }
-
- // Note: Must be called after render()
- isLoading(): boolean {
- return this.bottomTab?.isLoading() ?? false;
- }
-}
/**
* This adapter wraps a BottomTab, converting it to work with the Tab API.
diff --git a/ui/src/trace_processor/engine.ts b/ui/src/trace_processor/engine.ts
index 26197f1..7d4af6d 100644
--- a/ui/src/trace_processor/engine.ts
+++ b/ui/src/trace_processor/engine.ts
@@ -22,7 +22,7 @@
MetatraceCategories,
QueryArgs,
QueryResult as ProtoQueryResult,
- RegisterSqlModuleArgs,
+ RegisterSqlPackageArgs,
ResetTraceProcessorArgs,
TraceProcessorRpc,
TraceProcessorRpcStream,
@@ -127,7 +127,7 @@
private pendingRestoreTables = new Array<Deferred<void>>();
private pendingComputeMetrics = new Array<Deferred<string | Uint8Array>>();
private pendingReadMetatrace?: Deferred<DisableAndReadMetatraceResult>;
- private pendingRegisterSqlModule?: Deferred<void>;
+ private pendingRegisterSqlPackage?: Deferred<void>;
private _isMetatracingEnabled = false;
private _numRequestsPending = 0;
private _failed: Optional<string> = undefined;
@@ -265,9 +265,9 @@
assertExists(this.pendingReadMetatrace).resolve(metatraceRes);
this.pendingReadMetatrace = undefined;
break;
- case TPM.TPM_REGISTER_SQL_MODULE:
- const registerResult = assertExists(rpc.registerSqlModuleResult);
- const res = assertExists(this.pendingRegisterSqlModule);
+ case TPM.TPM_REGISTER_SQL_PACKAGE:
+ const registerResult = assertExists(rpc.registerSqlPackageResult);
+ const res = assertExists(this.pendingRegisterSqlPackage);
if (exists(registerResult.error) && registerResult.error.length > 0) {
res.reject(registerResult.error);
} else {
@@ -476,23 +476,23 @@
return result;
}
- registerSqlModules(p: {
+ registerSqlPackages(p: {
name: string;
modules: {name: string; sql: string}[];
}): Promise<void> {
- if (this.pendingRegisterSqlModule) {
+ if (this.pendingRegisterSqlPackage) {
return Promise.reject(new Error('Already finalising a metatrace'));
}
const result = defer<void>();
const rpc = TraceProcessorRpc.create();
- rpc.request = TPM.TPM_REGISTER_SQL_MODULE;
- const args = (rpc.registerSqlModuleArgs = new RegisterSqlModuleArgs());
- args.topLevelPackageName = p.name;
+ rpc.request = TPM.TPM_REGISTER_SQL_PACKAGE;
+ const args = (rpc.registerSqlPackageArgs = new RegisterSqlPackageArgs());
+ args.packageName = p.name;
args.modules = p.modules;
- args.allowModuleOverride = true;
- this.pendingRegisterSqlModule = result;
+ args.allowOverride = true;
+ this.pendingRegisterSqlPackage = result;
this.rpcSendRequest(rpc);
return result;
}