Merge "Support delegating socket connection" into main
diff --git a/Android.bp b/Android.bp
index 4ebb8f6..deb19b0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5238,6 +5238,7 @@
"protos/perfetto/trace/android/initial_display_state.proto",
"protos/perfetto/trace/android/network_trace.proto",
"protos/perfetto/trace/android/packages_list.proto",
+ "protos/perfetto/trace/android/shell_transition.proto",
"protos/perfetto/trace/android/surfaceflinger_common.proto",
"protos/perfetto/trace/android/surfaceflinger_layers.proto",
"protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -5267,6 +5268,7 @@
"external/perfetto/protos/perfetto/trace/android/initial_display_state.gen.cc",
"external/perfetto/protos/perfetto/trace/android/network_trace.gen.cc",
"external/perfetto/protos/perfetto/trace/android/packages_list.gen.cc",
+ "external/perfetto/protos/perfetto/trace/android/shell_transition.gen.cc",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.gen.cc",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_layers.gen.cc",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_transactions.gen.cc",
@@ -5296,6 +5298,7 @@
"external/perfetto/protos/perfetto/trace/android/initial_display_state.gen.h",
"external/perfetto/protos/perfetto/trace/android/network_trace.gen.h",
"external/perfetto/protos/perfetto/trace/android/packages_list.gen.h",
+ "external/perfetto/protos/perfetto/trace/android/shell_transition.gen.h",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.gen.h",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_layers.gen.h",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_transactions.gen.h",
@@ -5320,6 +5323,7 @@
"protos/perfetto/trace/android/initial_display_state.proto",
"protos/perfetto/trace/android/network_trace.proto",
"protos/perfetto/trace/android/packages_list.proto",
+ "protos/perfetto/trace/android/shell_transition.proto",
"protos/perfetto/trace/android/surfaceflinger_common.proto",
"protos/perfetto/trace/android/surfaceflinger_layers.proto",
"protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -5348,6 +5352,7 @@
"external/perfetto/protos/perfetto/trace/android/initial_display_state.pb.cc",
"external/perfetto/protos/perfetto/trace/android/network_trace.pb.cc",
"external/perfetto/protos/perfetto/trace/android/packages_list.pb.cc",
+ "external/perfetto/protos/perfetto/trace/android/shell_transition.pb.cc",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.pb.cc",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_layers.pb.cc",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_transactions.pb.cc",
@@ -5376,6 +5381,7 @@
"external/perfetto/protos/perfetto/trace/android/initial_display_state.pb.h",
"external/perfetto/protos/perfetto/trace/android/network_trace.pb.h",
"external/perfetto/protos/perfetto/trace/android/packages_list.pb.h",
+ "external/perfetto/protos/perfetto/trace/android/shell_transition.pb.h",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.pb.h",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_layers.pb.h",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_transactions.pb.h",
@@ -5390,6 +5396,7 @@
genrule {
name: "perfetto_protos_perfetto_trace_android_winscope_descriptor",
srcs: [
+ "protos/perfetto/trace/android/shell_transition.proto",
"protos/perfetto/trace/android/surfaceflinger_common.proto",
"protos/perfetto/trace/android/surfaceflinger_layers.proto",
"protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -5418,6 +5425,7 @@
"protos/perfetto/trace/android/initial_display_state.proto",
"protos/perfetto/trace/android/network_trace.proto",
"protos/perfetto/trace/android/packages_list.proto",
+ "protos/perfetto/trace/android/shell_transition.proto",
"protos/perfetto/trace/android/surfaceflinger_common.proto",
"protos/perfetto/trace/android/surfaceflinger_layers.proto",
"protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -5447,6 +5455,7 @@
"external/perfetto/protos/perfetto/trace/android/initial_display_state.pbzero.cc",
"external/perfetto/protos/perfetto/trace/android/network_trace.pbzero.cc",
"external/perfetto/protos/perfetto/trace/android/packages_list.pbzero.cc",
+ "external/perfetto/protos/perfetto/trace/android/shell_transition.pbzero.cc",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.pbzero.cc",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_layers.pbzero.cc",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_transactions.pbzero.cc",
@@ -5476,6 +5485,7 @@
"external/perfetto/protos/perfetto/trace/android/initial_display_state.pbzero.h",
"external/perfetto/protos/perfetto/trace/android/network_trace.pbzero.h",
"external/perfetto/protos/perfetto/trace/android/packages_list.pbzero.h",
+ "external/perfetto/protos/perfetto/trace/android/shell_transition.pbzero.h",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.pbzero.h",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_layers.pbzero.h",
"external/perfetto/protos/perfetto/trace/android/surfaceflinger_transactions.pbzero.h",
@@ -5695,6 +5705,7 @@
"protos/perfetto/trace/android/initial_display_state.proto",
"protos/perfetto/trace/android/network_trace.proto",
"protos/perfetto/trace/android/packages_list.proto",
+ "protos/perfetto/trace/android/shell_transition.proto",
"protos/perfetto/trace/android/surfaceflinger_common.proto",
"protos/perfetto/trace/android/surfaceflinger_layers.proto",
"protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -11258,6 +11269,8 @@
filegroup {
name: "perfetto_src_trace_processor_importers_proto_winscope_full",
srcs: [
+ "src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc",
+ "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc",
"src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc",
"src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc",
"src/trace_processor/importers/proto/winscope/winscope_args_parser.cc",
@@ -13109,6 +13122,7 @@
"protos/perfetto/trace/android/initial_display_state.proto",
"protos/perfetto/trace/android/network_trace.proto",
"protos/perfetto/trace/android/packages_list.proto",
+ "protos/perfetto/trace/android/shell_transition.proto",
"protos/perfetto/trace/android/surfaceflinger_common.proto",
"protos/perfetto/trace/android/surfaceflinger_layers.proto",
"protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -14460,7 +14474,7 @@
host: {
static_libs: [
"libprotobuf-cpp-full",
- "libsqlite",
+ "libsqlite_static_noicu",
"libz",
"sqlite_ext_percentile",
],
@@ -14612,7 +14626,7 @@
":perfetto_src_traceconv_utils",
],
static_libs: [
- "libsqlite",
+ "libsqlite_static_noicu",
"libz",
"perfetto_src_trace_processor_demangle",
"sqlite_ext_percentile",
diff --git a/BUILD b/BUILD
index f869fb1..df6a288 100644
--- a/BUILD
+++ b/BUILD
@@ -1579,6 +1579,10 @@
perfetto_filegroup(
name = "src_trace_processor_importers_proto_winscope_full",
srcs = [
+ "src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc",
+ "src/trace_processor/importers/proto/winscope/shell_transitions_parser.h",
+ "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc",
+ "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h",
"src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc",
"src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h",
"src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc",
@@ -4262,6 +4266,7 @@
"protos/perfetto/trace/android/initial_display_state.proto",
"protos/perfetto/trace/android/network_trace.proto",
"protos/perfetto/trace/android/packages_list.proto",
+ "protos/perfetto/trace/android/shell_transition.proto",
"protos/perfetto/trace/android/surfaceflinger_common.proto",
"protos/perfetto/trace/android/surfaceflinger_layers.proto",
"protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -4278,6 +4283,7 @@
perfetto_proto_library(
name = "protos_perfetto_trace_android_winscope_deps_protos",
srcs = [
+ "protos/perfetto/trace/android/shell_transition.proto",
"protos/perfetto/trace/android/surfaceflinger_common.proto",
"protos/perfetto/trace/android/surfaceflinger_layers.proto",
"protos/perfetto/trace/android/surfaceflinger_transactions.proto",
diff --git a/include/perfetto/base/build_config.h b/include/perfetto/base/build_config.h
index cd41e86..a0e6bb9 100644
--- a/include/perfetto/base/build_config.h
+++ b/include/perfetto/base/build_config.h
@@ -133,12 +133,10 @@
// http://msdn.microsoft.com/en-us/library/b0084kay.aspx
// http://www.agner.org/optimize/calling_conventions.pdf
// or with gcc, run: "echo | gcc -E -dM -"
-#if defined(_M_X64) || defined(__x86_64__)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_ARM64() 0
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_X86_64() 1
-#elif defined(__aarch64__) || defined(_M_ARM64)
+#if defined(__aarch64__) || defined(_M_ARM64)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_ARM64() 1
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_X86_64() 0
+#else
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_ARM64() 0
#endif
// perfetto_build_flags.h contains the tweakable build flags defined via GN.
diff --git a/protos/perfetto/trace/android/BUILD.gn b/protos/perfetto/trace/android/BUILD.gn
index c5e842f..509c448 100644
--- a/protos/perfetto/trace/android/BUILD.gn
+++ b/protos/perfetto/trace/android/BUILD.gn
@@ -28,6 +28,7 @@
"initial_display_state.proto",
"network_trace.proto",
"packages_list.proto",
+ "shell_transition.proto",
"surfaceflinger_common.proto",
"surfaceflinger_layers.proto",
"surfaceflinger_transactions.proto",
@@ -37,6 +38,7 @@
perfetto_proto_library("winscope_deps") {
proto_generators = [ "source_set" ]
sources = [
+ "shell_transition.proto",
"surfaceflinger_common.proto",
"surfaceflinger_layers.proto",
"surfaceflinger_transactions.proto",
diff --git a/protos/perfetto/trace/android/shell_transition.proto b/protos/perfetto/trace/android/shell_transition.proto
new file mode 100644
index 0000000..9a34d3a
--- /dev/null
+++ b/protos/perfetto/trace/android/shell_transition.proto
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+// ShellTransition messages record information about the shell transitions in
+// the system. This is used to track the animations that are created and execute
+// through the shell transition system.
+message ShellTransition {
+ // The unique identifier of the transition.
+ optional int32 id = 1;
+
+ // The time the transition was created on the WM side
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 create_time_ns = 2;
+ // The time the transition was sent from the WM side to shell
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 send_time_ns = 3;
+ // The time the transition was dispatched by shell to execute
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 dispatch_time_ns = 4;
+ // If the transition merge was accepted by the transition handler, this
+ // contains the time the transition was merged into transition with id
+ // `merge_target`.
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 merge_time_ns = 5;
+ // The time shell proposed the transition should be merged to the transition
+ // handler into transition with id `merge_target`.
+ // (using SystemClock.elapsedRealtimeNanos()).
+ optional int64 merge_request_time_ns = 6;
+ // If the transition was aborted on the shell side, this is the time that
+ // occured.
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 shell_abort_time_ns = 7;
+ // If the transition was aborted on the wm side, this is the time that
+ // occured.
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 wm_abort_time_ns = 8;
+ // The time WM considers the transition to be complete.
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 finish_time_ns = 9;
+
+ // The id of the transaction that WM proposed to use as the starting
+ // transaction. It contains all the layer changes required to setup the
+ // transition and should be executed right at the start of the transition
+ // by the transition handler.
+ optional uint64 start_transaction_id = 10;
+ // The if of the transaction that WM proposed to use as the finish
+ // transaction. It contains all the layer changes required to set the final
+ // state of the transition.
+ optional uint64 finish_transaction_id = 11;
+
+ // The id of the handler that executed the transition. A HandlerMappings
+ // message in the trace will contain the mapping of id to a string
+ // representation of the handler.
+ optional int32 handler = 12;
+ // The transition type of this transition (e.g. TO_FRONT, OPEN, CLOSE).
+ optional int32 type = 13;
+
+ // The list of targets that are part of this transition.
+ repeated Target targets = 14;
+ // The id of the transition we have requested to merge or have merged this
+ // transition into.
+ optional int32 merge_target = 15;
+
+ // The flags set on this transition.
+ optional int32 flags = 16;
+ // The time the starting window was removed. Tracked because this can
+ // happen after the transition finishes, but the app may not yet be visible
+ // until the starting window is removed. So in a sense the transition is not
+ // finished until the starting window is removed. (b/284302118)
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 starting_window_remove_time_ns = 17;
+
+ // Contains the information about the windows targeted in a transition.
+ message Target {
+ // The transition mode of this target (e.g. TO_FRONT, CLOSE...)
+ optional int32 mode = 1;
+ // The layer id of this target.
+ optional int32 layer_id = 2;
+ // The window id of this target.
+ optional int32 window_id = 3;
+ // The flags set on this target.
+ optional int32 flags = 4;
+ }
+}
+
+// Contains mappings from handler ids to string representation of the handlers.
+message ShellHandlerMappings {
+ repeated ShellHandlerMapping mapping = 1;
+}
+
+message ShellHandlerMapping {
+ // The id of the handler used in the ShellTransition message.
+ optional int32 id = 1;
+ // A human readable and meaningful string representation of the handler.
+ optional string name = 2;
+}
diff --git a/protos/perfetto/trace/android/winscope.proto b/protos/perfetto/trace/android/winscope.proto
index 78fbdcc..296e3ee 100644
--- a/protos/perfetto/trace/android/winscope.proto
+++ b/protos/perfetto/trace/android/winscope.proto
@@ -18,6 +18,7 @@
package perfetto.protos;
+import "protos/perfetto/trace/android/shell_transition.proto";
import "protos/perfetto/trace/android/surfaceflinger_layers.proto";
import "protos/perfetto/trace/android/surfaceflinger_transactions.proto";
@@ -26,4 +27,5 @@
message WinscopeTraceData {
optional LayersSnapshotProto layers_snapshot = 1;
optional TransactionTraceEntry transactions = 2;
+ optional ShellTransition shell_transition = 3;
}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 2ae554d..2c09723 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -4284,6 +4284,104 @@
// End of protos/perfetto/trace/android/packages_list.proto
+// Begin of protos/perfetto/trace/android/shell_transition.proto
+
+// ShellTransition messages record information about the shell transitions in
+// the system. This is used to track the animations that are created and execute
+// through the shell transition system.
+message ShellTransition {
+ // The unique identifier of the transition.
+ optional int32 id = 1;
+
+ // The time the transition was created on the WM side
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 create_time_ns = 2;
+ // The time the transition was sent from the WM side to shell
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 send_time_ns = 3;
+ // The time the transition was dispatched by shell to execute
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 dispatch_time_ns = 4;
+ // If the transition merge was accepted by the transition handler, this
+ // contains the time the transition was merged into transition with id
+ // `merge_target`.
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 merge_time_ns = 5;
+ // The time shell proposed the transition should be merged to the transition
+ // handler into transition with id `merge_target`.
+ // (using SystemClock.elapsedRealtimeNanos()).
+ optional int64 merge_request_time_ns = 6;
+ // If the transition was aborted on the shell side, this is the time that
+ // occured.
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 shell_abort_time_ns = 7;
+ // If the transition was aborted on the wm side, this is the time that
+ // occured.
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 wm_abort_time_ns = 8;
+ // The time WM considers the transition to be complete.
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 finish_time_ns = 9;
+
+ // The id of the transaction that WM proposed to use as the starting
+ // transaction. It contains all the layer changes required to setup the
+ // transition and should be executed right at the start of the transition
+ // by the transition handler.
+ optional uint64 start_transaction_id = 10;
+ // The if of the transaction that WM proposed to use as the finish
+ // transaction. It contains all the layer changes required to set the final
+ // state of the transition.
+ optional uint64 finish_transaction_id = 11;
+
+ // The id of the handler that executed the transition. A HandlerMappings
+ // message in the trace will contain the mapping of id to a string
+ // representation of the handler.
+ optional int32 handler = 12;
+ // The transition type of this transition (e.g. TO_FRONT, OPEN, CLOSE).
+ optional int32 type = 13;
+
+ // The list of targets that are part of this transition.
+ repeated Target targets = 14;
+ // The id of the transition we have requested to merge or have merged this
+ // transition into.
+ optional int32 merge_target = 15;
+
+ // The flags set on this transition.
+ optional int32 flags = 16;
+ // The time the starting window was removed. Tracked because this can
+ // happen after the transition finishes, but the app may not yet be visible
+ // until the starting window is removed. So in a sense the transition is not
+ // finished until the starting window is removed. (b/284302118)
+ // (using SystemClock.elapsedRealtimeNanos())
+ optional int64 starting_window_remove_time_ns = 17;
+
+ // Contains the information about the windows targeted in a transition.
+ message Target {
+ // The transition mode of this target (e.g. TO_FRONT, CLOSE...)
+ optional int32 mode = 1;
+ // The layer id of this target.
+ optional int32 layer_id = 2;
+ // The window id of this target.
+ optional int32 window_id = 3;
+ // The flags set on this target.
+ optional int32 flags = 4;
+ }
+}
+
+// Contains mappings from handler ids to string representation of the handlers.
+message ShellHandlerMappings {
+ repeated ShellHandlerMapping mapping = 1;
+}
+
+message ShellHandlerMapping {
+ // The id of the handler used in the ShellTransition message.
+ optional int32 id = 1;
+ // A human readable and meaningful string representation of the handler.
+ optional string name = 2;
+}
+
+// End of protos/perfetto/trace/android/shell_transition.proto
+
// Begin of protos/perfetto/trace/android/surfaceflinger_common.proto
message RegionProto {
@@ -13376,6 +13474,8 @@
// Winscope traces
LayersSnapshotProto surfaceflinger_layers_snapshot = 93;
TransactionTraceEntry surfaceflinger_transactions = 94;
+ ShellTransition shell_transition = 96;
+ ShellHandlerMappings shell_handler_mappings = 97;
// Events from the Windows etw infrastructure.
EtwTraceEventBundle etw_events = 95;
diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto
index 9966894..86a90fe 100644
--- a/protos/perfetto/trace/trace_packet.proto
+++ b/protos/perfetto/trace/trace_packet.proto
@@ -29,6 +29,7 @@
import "protos/perfetto/trace/android/initial_display_state.proto";
import "protos/perfetto/trace/android/network_trace.proto";
import "protos/perfetto/trace/android/packages_list.proto";
+import "protos/perfetto/trace/android/shell_transition.proto";
import "protos/perfetto/trace/android/surfaceflinger_layers.proto";
import "protos/perfetto/trace/android/surfaceflinger_transactions.proto";
import "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto";
@@ -216,6 +217,8 @@
// Winscope traces
LayersSnapshotProto surfaceflinger_layers_snapshot = 93;
TransactionTraceEntry surfaceflinger_transactions = 94;
+ ShellTransition shell_transition = 96;
+ ShellHandlerMappings shell_handler_mappings = 97;
// Events from the Windows etw infrastructure.
EtwTraceEventBundle etw_events = 95;
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 90cde7d..a68feef 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -1460,7 +1460,7 @@
message StartUp {
// This enum must be kept up to date with LaunchCauseMetrics.LaunchCause.
- enum LauchCauseType {
+ enum LaunchCauseType {
OTHER = 0;
CUSTOM_TAB = 1;
TWA = 2;
@@ -1483,7 +1483,8 @@
}
optional int64 activity_id = 1;
- optional LauchCauseType launch_cause = 2;
+ // deprecated field 2.
+ optional LaunchCauseType launch_cause = 3;
}
message WebContentInteraction {
diff --git a/src/trace_processor/importers/common/args_tracker.h b/src/trace_processor/importers/common/args_tracker.h
index 4751fef..2a3a8b5 100644
--- a/src/trace_processor/importers/common/args_tracker.h
+++ b/src/trace_processor/importers/common/args_tracker.h
@@ -139,6 +139,12 @@
context_->storage->mutable_surfaceflinger_transactions_table(), id);
}
+ BoundInserter AddArgsTo(tables::WindowManagerShellTransitionsTable::Id id) {
+ return AddArgsTo(
+ context_->storage->mutable_window_manager_shell_transitions_table(),
+ id);
+ }
+
BoundInserter AddArgsTo(MetadataId id) {
auto* table = context_->storage->mutable_metadata_table();
uint32_t row = *table->id().IndexOf(id);
diff --git a/src/trace_processor/importers/proto/winscope/BUILD.gn b/src/trace_processor/importers/proto/winscope/BUILD.gn
index 2d48f0c..7b04e85 100644
--- a/src/trace_processor/importers/proto/winscope/BUILD.gn
+++ b/src/trace_processor/importers/proto/winscope/BUILD.gn
@@ -16,30 +16,35 @@
source_set("full") {
sources = [
+ "shell_transitions_parser.cc",
+ "shell_transitions_parser.h",
+ "shell_transitions_tracker.cc",
+ "shell_transitions_tracker.h",
"surfaceflinger_layers_parser.cc",
"surfaceflinger_layers_parser.h",
"surfaceflinger_transactions_parser.cc",
"surfaceflinger_transactions_parser.h",
- "winscope_args_parser.h",
"winscope_args_parser.cc",
+ "winscope_args_parser.h",
"winscope_module.cc",
"winscope_module.h",
]
deps = [
":gen_cc_winscope_descriptor",
+ "../:proto_importer_module",
"../../../../../gn:default_deps",
- "../../../../../protos/perfetto/trace/android:zero",
"../../../../../protos/perfetto/trace:zero",
+ "../../../../../protos/perfetto/trace/android:zero",
"../../../storage",
"../../../tables",
"../../../types",
"../../common",
"../../common:parser_types",
- "../:proto_importer_module",
]
}
perfetto_cc_proto_descriptor("gen_cc_winscope_descriptor") {
descriptor_name = "winscope.descriptor"
- descriptor_target = "../../../../../protos/perfetto/trace/android:winscope_descriptor"
+ descriptor_target =
+ "../../../../../protos/perfetto/trace/android:winscope_descriptor"
}
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc
new file mode 100644
index 0000000..68d2a82
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/proto/winscope/shell_transitions_parser.h"
+#include "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h"
+
+#include "protos/perfetto/trace/android/shell_transition.pbzero.h"
+#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h"
+#include "src/trace_processor/importers/proto/winscope/winscope_args_parser.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+ShellTransitionsParser::ShellTransitionsParser(TraceProcessorContext* context)
+ : context_(context), args_parser_{pool_} {
+ pool_.AddFromFileDescriptorSet(kWinscopeDescriptor.data(),
+ kWinscopeDescriptor.size());
+}
+
+void ShellTransitionsParser::ParseTransition(protozero::ConstBytes blob) {
+ protos::pbzero::ShellTransition::Decoder transition(blob);
+
+ auto row_id =
+ ShellTransitionsTracker::GetOrCreate(context_)->InternTransition(
+ transition.id());
+
+ auto* window_manager_shell_transitions_table =
+ context_->storage->mutable_window_manager_shell_transitions_table();
+ auto row = window_manager_shell_transitions_table->FindById(row_id).value();
+
+ if (transition.has_dispatch_time_ns()) {
+ row.set_ts(transition.dispatch_time_ns());
+ }
+
+ auto inserter = context_->args_tracker->AddArgsTo(row_id);
+ WinscopeArgsParser writer(inserter, *context_->storage.get());
+ base::Status status = args_parser_.ParseMessage(
+ blob, kShellTransitionsProtoName, nullptr /* parse all fields */, writer);
+
+ if (!status.ok()) {
+ context_->storage->IncrementStats(
+ stats::winscope_shell_transitions_parse_errors);
+ }
+}
+
+void ShellTransitionsParser::ParseHandlerMappings(protozero::ConstBytes blob) {
+ auto* shell_handlers_table =
+ context_->storage
+ ->mutable_window_manager_shell_transition_handlers_table();
+
+ protos::pbzero::ShellHandlerMappings::Decoder handler_mappings(blob);
+ for (auto it = handler_mappings.mapping(); it; ++it) {
+ protos::pbzero::ShellHandlerMapping::Decoder mapping(it.field().as_bytes());
+
+ tables::WindowManagerShellTransitionHandlersTable::Row row;
+ row.handler_id = mapping.id();
+ row.handler_name = context_->storage->InternString(
+ base::StringView(mapping.name().ToStdString()));
+ shell_handlers_table->Insert(row);
+ }
+}
+
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_parser.h b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.h
new file mode 100644
index 0000000..44b86d1
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.h
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_PARSER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_PARSER_H_
+
+#include "src/trace_processor/util/descriptors.h"
+#include "src/trace_processor/util/proto_to_args_parser.h"
+
+namespace perfetto {
+
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+class ShellTransitionsParser {
+ public:
+ explicit ShellTransitionsParser(TraceProcessorContext*);
+ void ParseTransition(protozero::ConstBytes);
+ void ParseHandlerMappings(protozero::ConstBytes);
+
+ private:
+ static constexpr auto* kShellTransitionsProtoName =
+ ".perfetto.protos.ShellTransition";
+
+ TraceProcessorContext* const context_;
+ DescriptorPool pool_;
+ util::ProtoToArgsParser args_parser_;
+};
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_PARSER_H_
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc
new file mode 100644
index 0000000..6025d91
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#include "shell_transitions_tracker.h"
+#include "perfetto/ext/base/crash_keys.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/storage/metadata.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+ShellTransitionsTracker::ShellTransitionsTracker(TraceProcessorContext* context)
+ : context_(context) {}
+
+ShellTransitionsTracker::~ShellTransitionsTracker() = default;
+
+tables::WindowManagerShellTransitionsTable::Id
+ShellTransitionsTracker::InternTransition(int32_t transition_id) {
+ auto pos = transition_id_to_row_mapping_.find(transition_id);
+ if (pos != transition_id_to_row_mapping_.end()) {
+ return pos->second;
+ }
+
+ auto* window_manager_shell_transitions_table =
+ context_->storage->mutable_window_manager_shell_transitions_table();
+
+ tables::WindowManagerShellTransitionsTable::Row row;
+ row.transition_id = transition_id;
+ auto row_id = window_manager_shell_transitions_table->Insert(row).id;
+
+ transition_id_to_row_mapping_.insert({transition_id, row_id});
+
+ return row_id;
+}
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h
new file mode 100644
index 0000000..07ef736
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_TRACKER_H_
+
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Tracks information in the transition table.
+class ShellTransitionsTracker : public Destructible {
+ public:
+ explicit ShellTransitionsTracker(TraceProcessorContext*);
+ virtual ~ShellTransitionsTracker() override;
+
+ static ShellTransitionsTracker* GetOrCreate(TraceProcessorContext* context) {
+ if (!context->shell_transitions_tracker) {
+ context->shell_transitions_tracker.reset(
+ new ShellTransitionsTracker(context));
+ }
+ return static_cast<ShellTransitionsTracker*>(
+ context->shell_transitions_tracker.get());
+ }
+
+ tables::WindowManagerShellTransitionsTable::Id InternTransition(
+ int32_t transition_id);
+
+ private:
+ TraceProcessorContext* context_;
+ std::unordered_map<int32_t, tables::WindowManagerShellTransitionsTable::Id>
+ transition_id_to_row_mapping_;
+};
+
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_TRACKER_H_
diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.cc b/src/trace_processor/importers/proto/winscope/winscope_module.cc
index de9e224..7f6c154 100644
--- a/src/trace_processor/importers/proto/winscope/winscope_module.cc
+++ b/src/trace_processor/importers/proto/winscope/winscope_module.cc
@@ -23,11 +23,14 @@
WinscopeModule::WinscopeModule(TraceProcessorContext* context)
: surfaceflinger_layers_parser_(context),
- surfaceflinger_transactions_parser_(context) {
+ surfaceflinger_transactions_parser_(context),
+ shell_transitions_parser_(context) {
RegisterForField(TracePacket::kSurfaceflingerLayersSnapshotFieldNumber,
context);
RegisterForField(TracePacket::kSurfaceflingerTransactionsFieldNumber,
context);
+ RegisterForField(TracePacket::kShellTransitionFieldNumber, context);
+ RegisterForField(TracePacket::kShellHandlerMappingsFieldNumber, context);
}
void WinscopeModule::ParseTracePacketData(const TracePacket::Decoder& decoder,
@@ -43,6 +46,13 @@
surfaceflinger_transactions_parser_.Parse(
timestamp, decoder.surfaceflinger_transactions());
return;
+ case TracePacket::kShellTransitionFieldNumber:
+ shell_transitions_parser_.ParseTransition(decoder.shell_transition());
+ return;
+ case TracePacket::kShellHandlerMappingsFieldNumber:
+ shell_transitions_parser_.ParseHandlerMappings(
+ decoder.shell_handler_mappings());
+ return;
}
}
diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.h b/src/trace_processor/importers/proto/winscope/winscope_module.h
index d9428d0..fffe42c 100644
--- a/src/trace_processor/importers/proto/winscope/winscope_module.h
+++ b/src/trace_processor/importers/proto/winscope/winscope_module.h
@@ -21,6 +21,7 @@
#include "perfetto/base/build_config.h"
#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/proto/proto_importer_module.h"
+#include "src/trace_processor/importers/proto/winscope/shell_transitions_parser.h"
#include "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h"
#include "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.h"
@@ -41,6 +42,7 @@
private:
SurfaceFlingerLayersParser surfaceflinger_layers_parser_;
SurfaceFlingerTransactionsParser surfaceflinger_transactions_parser_;
+ ShellTransitionsParser shell_transitions_parser_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index d583632..1e40489 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -262,6 +262,11 @@
kSingle, kInfo, kAnalysis, \
"SurfaceFlinger transactions packet has unknown fields, which results " \
"in some arguments missing. You may need a newer version of trace " \
+ "processor to parse them."), \
+ F(winscope_shell_transitions_parse_errors, \
+ kSingle, kInfo, kAnalysis, \
+ "Shell transition packet has unknown fields, which results " \
+ "in some arguments missing. You may need a newer version of trace " \
"processor to parse them.")
// clang-format on
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index e75233e..79463f0 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -738,6 +738,24 @@
return &surfaceflinger_transactions_table_;
}
+ const tables::WindowManagerShellTransitionsTable&
+ window_manager_shell_transitions_table() const {
+ return window_manager_shell_transitions_table_;
+ }
+ tables::WindowManagerShellTransitionsTable*
+ mutable_window_manager_shell_transitions_table() {
+ return &window_manager_shell_transitions_table_;
+ }
+
+ const tables::WindowManagerShellTransitionHandlersTable&
+ window_manager_shell_transition_handlers_table() const {
+ return window_manager_shell_transition_handlers_table_;
+ }
+ tables::WindowManagerShellTransitionHandlersTable*
+ mutable_window_manager_shell_transition_handlers_table() {
+ return &window_manager_shell_transition_handlers_table_;
+ }
+
const tables::ExperimentalProtoPathTable& experimental_proto_path_table()
const {
return experimental_proto_path_table_;
@@ -995,6 +1013,10 @@
tables::SurfaceFlingerLayerTable surfaceflinger_layer_table_{&string_pool_};
tables::SurfaceFlingerTransactionsTable surfaceflinger_transactions_table_{
&string_pool_};
+ tables::WindowManagerShellTransitionsTable
+ window_manager_shell_transitions_table_{&string_pool_};
+ tables::WindowManagerShellTransitionHandlersTable
+ window_manager_shell_transition_handlers_table_{&string_pool_};
tables::ExperimentalProtoPathTable experimental_proto_path_table_{
&string_pool_};
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 16981fa..989d5e2 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -120,6 +120,10 @@
default;
SurfaceFlingerLayerTable::~SurfaceFlingerLayerTable() = default;
SurfaceFlingerTransactionsTable::~SurfaceFlingerTransactionsTable() = default;
+WindowManagerShellTransitionsTable::~WindowManagerShellTransitionsTable() =
+ default;
+WindowManagerShellTransitionHandlersTable::
+ ~WindowManagerShellTransitionHandlersTable() = default;
} // namespace tables
diff --git a/src/trace_processor/tables/winscope_tables.py b/src/trace_processor/tables/winscope_tables.py
index 22f2080..12e7cef 100644
--- a/src/trace_processor/tables/winscope_tables.py
+++ b/src/trace_processor/tables/winscope_tables.py
@@ -18,6 +18,7 @@
from python.generators.trace_processor_table.public import CppTableId
from python.generators.trace_processor_table.public import TableDoc
from python.generators.trace_processor_table.public import CppUint32
+from python.generators.trace_processor_table.public import CppString
SURFACE_FLINGER_LAYERS_SNAPSHOT_TABLE = Table(
python_module=__file__,
@@ -60,16 +61,53 @@
C('arg_set_id', CppUint32()),
],
tabledoc=TableDoc(
- doc='SurfaceFlinger transactions. Each row contains a set of transactions that SurfaceFlinger committed together.',
+ doc='SurfaceFlinger transactions. Each row contains a set of ' +
+ 'transactions that SurfaceFlinger committed together.',
group='Winscope',
columns={
'ts': 'Timestamp of the transactions commit',
'arg_set_id': 'Extra args parsed from the proto message',
}))
+WINDOW_MANAGER_SHELL_TRANSITIONS_TABLE = Table(
+ python_module=__file__,
+ class_name='WindowManagerShellTransitionsTable',
+ sql_name='window_manager_shell_transitions',
+ columns=[
+ C('ts', CppInt64()),
+ C('transition_id', CppInt64()),
+ C('arg_set_id', CppUint32()),
+ ],
+ tabledoc=TableDoc(
+ doc='Window Manager Shell Transitions',
+ group='Winscope',
+ columns={
+ 'ts': 'The timestamp the transition started playing',
+ 'transition_id': 'The id of the transition',
+ 'arg_set_id': 'Extra args parsed from the proto message',
+ }))
+
+WINDOW_MANAGER_SHELL_TRANSITION_HANDLERS_TABLE = Table(
+ python_module=__file__,
+ class_name='WindowManagerShellTransitionHandlersTable',
+ sql_name='window_manager_shell_transition_handlers',
+ columns=[
+ C('handler_id', CppInt64()),
+ C('handler_name', CppString()),
+ ],
+ tabledoc=TableDoc(
+ doc='Window Manager Shell Transition Handlers',
+ group='Winscope',
+ columns={
+ 'handler_id': 'The id of the handler',
+ 'handler_name': 'The name of the handler',
+ }))
+
# Keep this list sorted.
ALL_TABLES = [
SURFACE_FLINGER_LAYERS_SNAPSHOT_TABLE,
SURFACE_FLINGER_LAYER_TABLE,
SURFACE_FLINGER_TRANSACTIONS_TABLE,
+ WINDOW_MANAGER_SHELL_TRANSITIONS_TABLE,
+ WINDOW_MANAGER_SHELL_TRANSITION_HANDLERS_TABLE,
]
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 976357c..db65dae 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -854,6 +854,10 @@
RegisterStaticTable(storage->surfaceflinger_layer_table());
RegisterStaticTable(storage->surfaceflinger_transactions_table());
+ RegisterStaticTable(storage->window_manager_shell_transitions_table());
+ RegisterStaticTable(
+ storage->window_manager_shell_transition_handlers_table());
+
RegisterStaticTable(storage->metadata_table());
RegisterStaticTable(storage->cpu_table());
RegisterStaticTable(storage->cpu_freq_table());
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index c2644ee..1eb16e4 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -123,6 +123,7 @@
std::unique_ptr<Destructible> i2c_tracker; // I2CTracker
std::unique_ptr<Destructible> perf_data_tracker; // PerfDataTracker
std::unique_ptr<Destructible> content_analyzer;
+ std::unique_ptr<Destructible> shell_transitions_tracker;
// These fields are trace readers which will be called by |forwarding_parser|
// once the format of the trace is discovered. They are placed here as they
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 0aea755..e585f47 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -51,6 +51,7 @@
from diff_tests.parser.android.tests_games import AndroidGames
from diff_tests.parser.android.tests_surfaceflinger_layers import SurfaceFlingerLayers
from diff_tests.parser.android.tests_surfaceflinger_transactions import SurfaceFlingerTransactions
+from diff_tests.parser.android.tests_shell_transitions import ShellTransitions
from diff_tests.parser.android_fs.tests import AndroidFs
from diff_tests.parser.atrace.tests import Atrace
from diff_tests.parser.atrace.tests_error_handling import AtraceErrorHandling
@@ -171,6 +172,8 @@
'SurfaceFlingerLayers').fetch(),
*SurfaceFlingerTransactions(index_path, 'parser/android',
'SurfaceFlingerTransactions').fetch(),
+ *ShellTransitions(index_path, 'parser/android',
+ 'ShellTransitions').fetch(),
*TrackEvent(index_path, 'parser/track_event', 'TrackEvent').fetch(),
*TranslatedArgs(index_path, 'parser/translated_args',
'TranslatedArgs').fetch(),
diff --git a/test/trace_processor/diff_tests/parser/android/shell_handlers.textproto b/test/trace_processor/diff_tests/parser/android/shell_handlers.textproto
new file mode 100644
index 0000000..9b0182a
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/shell_handlers.textproto
@@ -0,0 +1,21 @@
+packet {
+ trusted_uid: 10225
+ trusted_packet_sequence_id: 12
+ previous_packet_dropped: true
+ trusted_pid: 3981
+ first_packet_on_sequence: true
+ shell_handler_mappings {
+ mapping {
+ id: 1
+ name: "DefaultTransitionHandler"
+ }
+ mapping {
+ id: 2
+ name: "RecentsTransitionHandler"
+ }
+ mapping {
+ id: 3
+ name: "FreeformTaskTransitionHandler"
+ }
+ }
+}
diff --git a/test/trace_processor/diff_tests/parser/android/shell_transitions.textproto b/test/trace_processor/diff_tests/parser/android/shell_transitions.textproto
new file mode 100644
index 0000000..b92eb39
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/shell_transitions.textproto
@@ -0,0 +1,167 @@
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ previous_packet_dropped: true
+ trusted_pid: 1305
+ first_packet_on_sequence: true
+ shell_transition {
+ id: 7
+ create_time_ns: 76799049027
+ send_time_ns: 76875395422
+ start_transaction_id: 5604932321952
+ finish_transaction_id: 5604932321954
+ }
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1305
+ shell_transition {
+ id: 10
+ create_time_ns: 77854865352
+ send_time_ns: 77894307328
+ start_transaction_id: 5604932322158
+ finish_transaction_id: 5604932322159
+ }
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ trusted_pid: 1305
+ shell_transition {
+ id: 11
+ create_time_ns: 82498121051
+ send_time_ns: 82535513345
+ start_transaction_id: 5604932322346
+ finish_transaction_id: 5604932322347
+ }
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 3
+ previous_packet_dropped: true
+ trusted_pid: 1305
+ first_packet_on_sequence: true
+ shell_transition {
+ id: 8
+ create_time_ns: 76955664017
+ send_time_ns: 77277756832
+ start_transaction_id: 5604932322028
+ finish_transaction_id: 5604932322029
+ }
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 4
+ previous_packet_dropped: true
+ trusted_pid: 1305
+ first_packet_on_sequence: true
+ shell_transition {
+ id: 7
+ starting_window_remove_time_ns: 77706603918
+ }
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 5
+ previous_packet_dropped: true
+ trusted_pid: 1305
+ first_packet_on_sequence: true
+ shell_transition {
+ id: 9
+ create_time_ns: 77825423417
+ send_time_ns: 77843436723
+ start_transaction_id: 5604932322137
+ finish_transaction_id: 5604932322138
+ }
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 5
+ trusted_pid: 1305
+ shell_transition {
+ id: 9
+ finish_time_ns: 77873935462
+ }
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 5
+ trusted_pid: 1305
+ shell_transition {
+ id: 10
+ finish_time_ns: 78621610429
+ }
+}
+packet {
+ trusted_uid: 10241
+ trusted_packet_sequence_id: 6
+ previous_packet_dropped: true
+ trusted_pid: 2528
+ first_packet_on_sequence: true
+ shell_transition {
+ id: 7
+ dispatch_time_ns: 76879063147
+ handler: 2
+ }
+}
+packet {
+ trusted_uid: 10241
+ trusted_packet_sequence_id: 6
+ trusted_pid: 2528
+ shell_transition {
+ id: 8
+ merge_time_ns: 77278725500
+ merge_target: 7
+ }
+}
+packet {
+ trusted_uid: 10241
+ trusted_packet_sequence_id: 6
+ trusted_pid: 2528
+ shell_transition {
+ id: 8
+ dispatch_time_ns: 77320527177
+ handler: 3
+ }
+}
+packet {
+ trusted_uid: 10241
+ trusted_packet_sequence_id: 6
+ trusted_pid: 2528
+ shell_transition {
+ id: 9
+ dispatch_time_ns: 77876414832
+ handler: 3
+ }
+}
+packet {
+ trusted_uid: 10241
+ trusted_packet_sequence_id: 6
+ trusted_pid: 2528
+ shell_transition {
+ id: 10
+ dispatch_time_ns: 77899001013
+ handler: 4
+ }
+}
+packet {
+ trusted_uid: 10241
+ trusted_packet_sequence_id: 6
+ trusted_pid: 2528
+ shell_transition {
+ id: 11
+ dispatch_time_ns: 82536817137
+ handler: 2
+ }
+}
+packet {
+ trusted_uid: 10241
+ trusted_packet_sequence_id: 6
+ trusted_pid: 2528
+ shell_transition {
+ id: 12
+ merge_time_ns: 82697060749
+ merge_target: 11
+ }
+}
diff --git a/test/trace_processor/diff_tests/parser/android/shell_transitions_simple_merge.textproto b/test/trace_processor/diff_tests/parser/android/shell_transitions_simple_merge.textproto
new file mode 100644
index 0000000..6c9cb65
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/shell_transitions_simple_merge.textproto
@@ -0,0 +1,62 @@
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 2
+ previous_packet_dropped: true
+ trusted_pid: 1336
+ first_packet_on_sequence: true
+ shell_transition {
+ id: 15
+ create_time_ns: 2187614568227
+ send_time_ns: 2187671767120
+ start_transaction_id: 5738076308937
+ finish_transaction_id: 5738076308938
+ type: 1
+ targets {
+ mode: 1
+ layer_id: 244
+ window_id: 219481253
+ flags: 0
+ }
+ targets {
+ mode: 4
+ layer_id: 47
+ window_id: 54474511
+ flags: 1
+ }
+ flags: 0
+ }
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 3
+ previous_packet_dropped: true
+ trusted_pid: 1336
+ first_packet_on_sequence: true
+ shell_transition {
+ id: 15
+ finish_time_ns: 2188195968659
+ }
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 5
+ previous_packet_dropped: true
+ trusted_pid: 1336
+ first_packet_on_sequence: true
+ shell_transition {
+ id: 15
+ starting_window_remove_time_ns: 2188652838898
+ }
+}
+packet {
+ trusted_uid: 10225
+ trusted_packet_sequence_id: 12
+ previous_packet_dropped: true
+ trusted_pid: 3981
+ first_packet_on_sequence: true
+ shell_transition {
+ id: 15
+ dispatch_time_ns: 2187673373973
+ handler: 2
+ }
+}
diff --git a/test/trace_processor/diff_tests/parser/android/tests_shell_transitions.py b/test/trace_processor/diff_tests/parser/android/tests_shell_transitions.py
new file mode 100644
index 0000000..a8e0328
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/tests_shell_transitions.py
@@ -0,0 +1,91 @@
+#!/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 Path
+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 ShellTransitions(TestSuite):
+
+ def test_has_expected_transition_rows(self):
+ return DiffTestBlueprint(
+ trace=Path('shell_transitions.textproto'),
+ query="""
+ SELECT
+ id, transition_id
+ FROM
+ window_manager_shell_transitions;
+ """,
+ out=Csv("""
+ "id","transition_id"
+ 0,7
+ 1,10
+ 2,11
+ 3,8
+ 4,9
+ 5,12
+ """))
+
+ def test_has_expected_transition_args(self):
+ return DiffTestBlueprint(
+ trace=Path('shell_transitions_simple_merge.textproto'),
+ query="""
+ SELECT
+ args.key, args.display_value
+ FROM
+ window_manager_shell_transitions JOIN args ON window_manager_shell_transitions.arg_set_id = args.arg_set_id
+ WHERE window_manager_shell_transitions.transition_id = 15
+ ORDER BY args.key;
+ """,
+ out=Csv("""
+ "key","display_value"
+ "create_time_ns","2187614568227"
+ "dispatch_time_ns","2187673373973"
+ "finish_time_ns","2188195968659"
+ "finish_transaction_id","5738076308938"
+ "flags","0"
+ "handler","2"
+ "id","15"
+ "send_time_ns","2187671767120"
+ "start_transaction_id","5738076308937"
+ "starting_window_remove_time_ns","2188652838898"
+ "targets[0].flags","0"
+ "targets[0].layer_id","244"
+ "targets[0].mode","1"
+ "targets[0].window_id","219481253"
+ "targets[1].flags","1"
+ "targets[1].layer_id","47"
+ "targets[1].mode","4"
+ "targets[1].window_id","54474511"
+ "type","1"
+ """))
+
+ def test_has_shell_handlers(self):
+ return DiffTestBlueprint(
+ trace=Path('shell_handlers.textproto'),
+ query="""
+ SELECT
+ handler_id, handler_name
+ FROM
+ window_manager_shell_transition_handlers;
+ """,
+ out=Csv("""
+ "handler_id","handler_name"
+ 1,"DefaultTransitionHandler"
+ 2,"RecentsTransitionHandler"
+ 3,"FreeformTaskTransitionHandler"
+ """))
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 11f6d16..6d00144 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -335,7 +335,7 @@
def enable_sqlite(module):
if module.type == 'cc_binary_host':
- module.static_libs.add('libsqlite')
+ module.static_libs.add('libsqlite_static_noicu')
module.static_libs.add('sqlite_ext_percentile')
elif module.host_supported:
# Copy what the sqlite3 command line tool does.
@@ -344,7 +344,7 @@
module.android.shared_libs.add('liblog')
module.android.shared_libs.add('libutils')
module.android.static_libs.add('sqlite_ext_percentile')
- module.host.static_libs.add('libsqlite')
+ module.host.static_libs.add('libsqlite_static_noicu')
module.host.static_libs.add('sqlite_ext_percentile')
else:
module.shared_libs.add('libsqlite')
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 91a0c0e..8ee9b76 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -6,7 +6,7 @@
},
{
"name": "canary",
- "rev": "c69b33b9abcc20fad9ad5f39de883216e4b43130"
+ "rev": "9ca89e30931314dec4af1131d516e07e39d8657d"
},
{
"name": "autopush",
diff --git a/ui/src/common/basic_async_track.ts b/ui/src/common/basic_async_track.ts
index 41dfe3d..d5e0dda 100644
--- a/ui/src/common/basic_async_track.ts
+++ b/ui/src/common/basic_async_track.ts
@@ -18,8 +18,7 @@
import {raf} from '../core/raf_scheduler';
import {globals} from '../frontend/globals';
import {PxSpan, TimeScale} from '../frontend/time_scale';
-import {SliceRect} from '../frontend/track';
-import {Track, TrackContext} from '../public';
+import {SliceRect, Track, TrackContext} from '../public';
import {TrackData} from './track_data';
diff --git a/ui/src/common/color.ts b/ui/src/common/color.ts
new file mode 100644
index 0000000..127bfbb
--- /dev/null
+++ b/ui/src/common/color.ts
@@ -0,0 +1,290 @@
+// 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 {hsluvToRgb} from 'hsluv';
+
+import {clamp} from '../base/math_utils';
+
+// This file contains a library for working with colors in various color spaces
+// and formats.
+
+const LIGHTNESS_MIN = 0;
+const LIGHTNESS_MAX = 100;
+
+const SATURATION_MIN = 0;
+const SATURATION_MAX = 100;
+
+// Most color formats can be defined using 3 numbers in a standardized order, so
+// this tuple serves as a compact way to store various color formats.
+// E.g. HSL, RGB
+type ColorTuple = [number, number, number];
+
+// Definition of an HSL color with named fields.
+interface HSL {
+ readonly h: number; // 0-360
+ readonly s: number; // 0-100
+ readonly l: number; // 0-100
+}
+
+// Defines an interface to an immutable color object, which can be defined in
+// any arbitrary format or color space and provides function to modify the color
+// and conversions to CSS compatible style strings.
+// Because this color object is effectively immutable, a new color object is
+// returned when modifying the color, rather than editing the current object
+// in-place.
+// Also, because these objects are immutable, it's expected that readonly
+// properties such as |cssString| are efficient, as they can be computed at
+// creation time, so they may be used in the hot path (render loop).
+export interface Color {
+ readonly cssString: string;
+
+ // The perceived brightness of the color using a weighted average of the
+ // r, g and b channels based on human perception.
+ readonly perceivedBrightness: number;
+
+ // Bring up the lightness by |percent| percent.
+ lighten(percent: number, max?: number): Color;
+
+ // Bring down the lightness by |percent| percent.
+ darken(percent: number, min?: number): Color;
+
+ // Bring up the saturation by |percent| percent.
+ saturate(percent: number, max?: number): Color;
+
+ // Bring down the saturation by |percent| percent.
+ desaturate(percent: number, min?: number): Color;
+
+ // Set one or more HSL values.
+ setHSL(hsl: Partial<HSL>): Color;
+
+ setAlpha(alpha: number|undefined): Color;
+}
+
+// Common base class for HSL colors. Avoids code duplication.
+abstract class HSLColorBase<T extends Color> {
+ readonly hsl: ColorTuple;
+ readonly alpha?: number;
+
+ // Values are in the range:
+ // Hue: 0-360
+ // Saturation: 0-100
+ // Lightness: 0-100
+ // Alpha: 0-1
+ constructor(init: ColorTuple|HSL|string, alpha?: number) {
+ if (Array.isArray(init)) {
+ this.hsl = init;
+ } else if (typeof init === 'string') {
+ const rgb = hexToRgb(init);
+ this.hsl = rgbToHsl(rgb);
+ } else {
+ this.hsl = [init.h, init.s, init.l];
+ }
+ this.alpha = alpha;
+ }
+
+ // Subclasses should implement this to teach the base class how to create a
+ // new object of the subclass type.
+ abstract create(hsl: ColorTuple|HSL, alpha?: number): T;
+
+ lighten(amount: number, max = LIGHTNESS_MAX): T {
+ const [h, s, l] = this.hsl;
+ const newLightness = clamp(l + amount, LIGHTNESS_MIN, max);
+ return this.create([h, s, newLightness], this.alpha);
+ }
+
+ darken(amount: number, min = LIGHTNESS_MIN): T {
+ const [h, s, l] = this.hsl;
+ const newLightness = clamp(l - amount, min, LIGHTNESS_MAX);
+ return this.create([h, s, newLightness], this.alpha);
+ }
+
+ saturate(amount: number, max = SATURATION_MAX): T {
+ const [h, s, l] = this.hsl;
+ const newSaturation = clamp(s + amount, SATURATION_MIN, max);
+ return this.create([h, newSaturation, l], this.alpha);
+ }
+
+ desaturate(amount: number, min = SATURATION_MIN): T {
+ const [h, s, l] = this.hsl;
+ const newSaturation = clamp(s - amount, min, SATURATION_MAX);
+ return this.create([h, newSaturation, l], this.alpha);
+ }
+
+ setHSL(hsl: Partial<HSL>): T {
+ const [h, s, l] = this.hsl;
+ return this.create({h, s, l, ...hsl}, this.alpha);
+ }
+
+ setAlpha(alpha: number|undefined): T {
+ return this.create(this.hsl, alpha);
+ }
+}
+
+// Describes a color defined in standard HSL color space.
+export class HSLColor extends HSLColorBase<HSLColor> implements Color {
+ readonly cssString: string;
+ readonly perceivedBrightness: number;
+
+ // Values are in the range:
+ // Hue: 0-360
+ // Saturation: 0-100
+ // Lightness: 0-100
+ // Alpha: 0-1
+ constructor(hsl: ColorTuple|HSL|string, alpha?: number) {
+ super(hsl, alpha);
+
+ const [r, g, b] = hslToRgb(...this.hsl);
+
+ this.perceivedBrightness = perceivedBrightness(r, g, b);
+
+ if (this.alpha === undefined) {
+ this.cssString = `rgb(${r} ${g} ${b})`;
+ } else {
+ this.cssString = `rgb(${r} ${g} ${b} / ${this.alpha})`;
+ }
+ }
+
+ create(values: ColorTuple|HSL, alpha?: number|undefined): HSLColor {
+ return new HSLColor(values, alpha);
+ }
+}
+
+// Describes a color defined in HSLuv color space.
+// See: https://www.hsluv.org/
+export class HSLuvColor extends HSLColorBase<HSLuvColor> implements Color {
+ readonly cssString: string;
+ readonly perceivedBrightness: number;
+
+ constructor(hsl: ColorTuple|HSL, alpha?: number) {
+ super(hsl, alpha);
+
+ const rgb = hsluvToRgb(this.hsl);
+ const r = Math.floor(rgb[0] * 255);
+ const g = Math.floor(rgb[1] * 255);
+ const b = Math.floor(rgb[2] * 255);
+
+ this.perceivedBrightness = perceivedBrightness(r, g, b);
+
+ if (this.alpha === undefined) {
+ this.cssString = `rgb(${r} ${g} ${b})`;
+ } else {
+ this.cssString = `rgb(${r} ${g} ${b} / ${this.alpha})`;
+ }
+ }
+
+ create(raw: ColorTuple|HSL, alpha?: number|undefined): HSLuvColor {
+ return new HSLuvColor(raw, alpha);
+ }
+}
+
+// Hue: 0-360
+// Saturation: 0-100
+// Lightness: 0-100
+// RGB: 0-255
+export function hslToRgb(h: number, s: number, l: number): ColorTuple {
+ h = h;
+ s = s / SATURATION_MAX;
+ l = l / LIGHTNESS_MAX;
+
+ const c = (1 - Math.abs(2 * l - 1)) * s;
+ const x = c * (1 - Math.abs((h / 60) % 2 - 1));
+ const m = l - c / 2;
+
+ let [r, g, b] = [0, 0, 0];
+
+ if (0 <= h && h < 60) {
+ [r, g, b] = [c, x, 0];
+ } else if (60 <= h && h < 120) {
+ [r, g, b] = [x, c, 0];
+ } else if (120 <= h && h < 180) {
+ [r, g, b] = [0, c, x];
+ } else if (180 <= h && h < 240) {
+ [r, g, b] = [0, x, c];
+ } else if (240 <= h && h < 300) {
+ [r, g, b] = [x, 0, c];
+ } else if (300 <= h && h < 360) {
+ [r, g, b] = [c, 0, x];
+ }
+
+ // Convert to 0-255 range
+ r = Math.round((r + m) * 255);
+ g = Math.round((g + m) * 255);
+ b = Math.round((b + m) * 255);
+
+ return [r, g, b];
+}
+
+export function hexToRgb(hex: string): ColorTuple {
+ // Convert hex to RGB first
+ let r: number = 0;
+ let g: number = 0;
+ let b: number = 0;
+
+ if (hex.length === 4) {
+ r = parseInt(hex[1] + hex[1], 16);
+ g = parseInt(hex[2] + hex[2], 16);
+ b = parseInt(hex[3] + hex[3], 16);
+ } else if (hex.length === 7) {
+ r = parseInt(hex.substring(1, 3), 16);
+ g = parseInt(hex.substring(3, 5), 16);
+ b = parseInt(hex.substring(5, 7), 16);
+ }
+
+ return [r, g, b];
+}
+
+export function rgbToHsl(rgb: ColorTuple): ColorTuple {
+ let [r, g, b] = rgb;
+ r /= 255;
+ g /= 255;
+ b /= 255;
+ const max = Math.max(r, g, b);
+ const min = Math.min(r, g, b);
+ let h: number = (max + min) / 2;
+ let s: number = (max + min) / 2;
+ const l: number = (max + min) / 2;
+
+ if (max === min) {
+ h = s = 0; // achromatic
+ } else {
+ const d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ switch (max) {
+ case r:
+ h = (g - b) / d + (g < b ? 6 : 0);
+ break;
+ case g:
+ h = (b - r) / d + 2;
+ break;
+ case b:
+ h = (r - g) / d + 4;
+ break;
+ }
+ h /= 6;
+ }
+
+ return [h * 360, s * 100, l * 100];
+}
+
+// Return the perceived brightness of a color using a weighted average of the
+// r, g and b channels based on human perception.
+function perceivedBrightness(r: number, g: number, b: number): number {
+ // YIQ calculation from https://24ways.org/2010/calculating-color-contrast
+ return ((r * 299) + (g * 587) + (b * 114)) / 1000;
+}
+
+// Comparison function used for sorting colors.
+export function colorCompare(a: Color, b: Color): number {
+ return a.cssString.localeCompare(b.cssString);
+}
diff --git a/ui/src/common/color_unittest.ts b/ui/src/common/color_unittest.ts
new file mode 100644
index 0000000..4973308
--- /dev/null
+++ b/ui/src/common/color_unittest.ts
@@ -0,0 +1,113 @@
+// 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 {HSLColor, hslToRgb, HSLuvColor} from './color';
+
+describe('HSLColor', () => {
+ const col = new HSLColor({h: 123, s: 66, l: 45});
+
+ test('cssString', () => {
+ expect(col.cssString).toBe('rgb(39 190 47)');
+ expect(new HSLColor({h: 0, s: 0, l: 0}).cssString).toBe('rgb(0 0 0)');
+ expect(new HSLColor({h: 0, s: 100, l: 100}).cssString)
+ .toBe('rgb(255 255 255)');
+ expect(new HSLColor({h: 90, s: 25, l: 55}).cssString)
+ .toBe('rgb(140 169 112)');
+ expect(new HSLColor({h: 180, s: 80, l: 40}, 0.7).cssString)
+ .toBe('rgb(20 184 184 / 0.7)');
+ });
+
+ test('lighten', () => {
+ expect(col.lighten(20).hsl).toEqual([123, 66, 65]);
+ expect(col.lighten(100).hsl).toEqual([123, 66, 100]);
+ expect(col.lighten(-100).hsl).toEqual([123, 66, 0]);
+ });
+
+ test('saturate', () => {
+ expect(col.saturate(20).hsl).toEqual([123, 86, 45]);
+ expect(col.saturate(100).hsl).toEqual([123, 100, 45]);
+ expect(col.saturate(-100).hsl).toEqual([123, 0, 45]);
+ });
+
+ test('setAlpha', () => {
+ expect(col.setAlpha(.7).alpha).toEqual(.7);
+ expect(col.setAlpha(undefined).alpha).toEqual(undefined);
+ });
+
+ test('perceivedBrightness', () => {
+ // Test a few obviously light/dark colours.
+ expect(new HSLColor({h: 0, s: 0, l: 0}).perceivedBrightness)
+ .toBeLessThan(128);
+ expect(new HSLColor({h: 0, s: 0, l: 100}).perceivedBrightness)
+ .toBeGreaterThan(128);
+
+ expect(new HSLColor({h: 0, s: 0, l: 40}).perceivedBrightness)
+ .toBeLessThan(128);
+ expect(new HSLColor({h: 0, s: 0, l: 60}).perceivedBrightness)
+ .toBeGreaterThan(128);
+ });
+});
+
+describe('HSLuvColor', () => {
+ const col = new HSLuvColor({h: 123, s: 66, l: 45});
+
+ test('cssString', () => {
+ expect(col.cssString).toBe('rgb(69 117 58)');
+ expect(new HSLColor({h: 0, s: 0, l: 0}).cssString).toBe('rgb(0 0 0)');
+ expect(new HSLColor({h: 0, s: 100, l: 100}).cssString)
+ .toBe('rgb(255 255 255)');
+ expect(new HSLuvColor({h: 90, s: 25, l: 55}).cssString)
+ .toBe('rgb(131 133 112)');
+ expect(new HSLuvColor({h: 240, s: 100, l: 100}, 0.3).cssString)
+ .toBe('rgb(254 255 255 / 0.3)');
+ });
+
+ test('lighten', () => {
+ expect(col.lighten(20).hsl).toEqual([123, 66, 65]);
+ expect(col.lighten(100).hsl).toEqual([123, 66, 100]);
+ expect(col.lighten(-100).hsl).toEqual([123, 66, 0]);
+ });
+
+ test('saturate', () => {
+ expect(col.saturate(20).hsl).toEqual([123, 86, 45]);
+ expect(col.saturate(100).hsl).toEqual([123, 100, 45]);
+ expect(col.saturate(-100).hsl).toEqual([123, 0, 45]);
+ });
+
+ test('setAlpha', () => {
+ expect(col.setAlpha(.7).alpha).toEqual(.7);
+ expect(col.setAlpha(undefined).alpha).toEqual(undefined);
+ });
+
+ test('perceivedBrightness', () => {
+ // Test a few obviously light/dark colours.
+ expect(new HSLuvColor({h: 0, s: 0, l: 0}).perceivedBrightness)
+ .toBeLessThan(128);
+ expect(new HSLuvColor({h: 0, s: 0, l: 100}).perceivedBrightness)
+ .toBeGreaterThan(128);
+
+ expect(new HSLuvColor({h: 0, s: 0, l: 40}).perceivedBrightness)
+ .toBeLessThan(128);
+ expect(new HSLuvColor({h: 0, s: 0, l: 60}).perceivedBrightness)
+ .toBeGreaterThan(128);
+ });
+});
+
+test('hslToRGB', () => {
+ // Pick a few well-known conversions to check we're in the right ballpark.
+ expect(hslToRgb(0, 0, 0)).toEqual([0, 0, 0]);
+ expect(hslToRgb(0, 100, 50)).toEqual([255, 0, 0]);
+ expect(hslToRgb(120, 100, 50)).toEqual([0, 255, 0]);
+ expect(hslToRgb(240, 100, 50)).toEqual([0, 0, 255]);
+});
diff --git a/ui/src/common/colorizer.ts b/ui/src/common/colorizer.ts
index 326bb63..584deb5 100644
--- a/ui/src/common/colorizer.ts
+++ b/ui/src/common/colorizer.ts
@@ -13,91 +13,161 @@
// limitations under the License.
import {hsl} from 'color-convert';
-import {hsluvToRgb} from 'hsluv';
-import {clamp} from '../base/math_utils';
import {hash} from '../common/hash';
-import {cachedHsluvToHex} from '../frontend/hsluv_cache';
+import {featureFlags} from '../core/feature_flags';
-export interface Color {
- h: number;
- s: number;
- l: number;
- a?: number;
+import {Color, HSLColor, HSLuvColor} from './color';
+
+// 128 would provide equal weighting between dark and light text, but we want to
+// slightly prefer light text for stylistic reasons.
+// 140 means we must be brighter on average before switching to dark text.
+const PERCEIVED_BRIGHTNESS_LIMIT = 140;
+
+// This file defines some opinionated colors and provides functions to access
+// random but predictable colors based on a seed, as well as standardized ways
+// to access colors for core objects such as slices and thread states.
+
+// We have, over the years, accumulated a number of different color palettes
+// which are used for different parts of the UI.
+// It would be nice to combine these into a single palette in the future, but
+// changing colors is difficult especially for slice colors, as folks get used
+// to certain slices being certain colors and are resistant to change.
+// However we do it, we should make it possible for folks to switch back the a
+// previous palette, or define their own.
+
+const USE_CONSISTENT_COLORS = featureFlags.register({
+ id: 'useConsistentColors',
+ name: 'Use common color palette for timeline elements',
+ description: 'Use the same color palette for all timeline elements.',
+ defaultValue: false,
+});
+
+// |ColorScheme| defines a collection of colors which can be used for various UI
+// elements. In the future we would expand this interface to include light and
+// dark variants.
+export interface ColorScheme {
+ // The base color to be used for the bulk of the element.
+ readonly base: Color;
+
+ // A variant on the base color, commonly used for highlighting.
+ readonly variant: Color;
+
+ // Grayed out color to represent a disabled state.
+ readonly disabled: Color;
+
+ // Appropriate colors for text to be displayed on top of the above colors.
+ readonly textBase: Color;
+ readonly textVariant: Color;
+ readonly textDisabled: Color;
}
-const MD_PALETTE: Color[] = [
- {h: 4, s: 90, l: 58},
- {h: 340, s: 82, l: 52},
- {h: 291, s: 64, l: 42},
- {h: 262, s: 52, l: 47},
- {h: 231, s: 48, l: 48},
- {h: 207, s: 90, l: 54},
- {h: 199, s: 98, l: 48},
- {h: 187, s: 100, l: 42},
- {h: 174, s: 100, l: 29},
- {h: 122, s: 39, l: 49},
- {h: 88, s: 50, l: 53},
- {h: 66, s: 70, l: 54},
- {h: 45, s: 100, l: 51},
- {h: 36, s: 100, l: 50},
- {h: 14, s: 100, l: 57},
- {h: 16, s: 25, l: 38},
- {h: 200, s: 18, l: 46},
- {h: 54, s: 100, l: 62},
+const MD_PALETTE_RAW: Color[] = [
+ new HSLColor({h: 4, s: 90, l: 58}),
+ new HSLColor({h: 340, s: 82, l: 52}),
+ new HSLColor({h: 291, s: 64, l: 42}),
+ new HSLColor({h: 262, s: 52, l: 47}),
+ new HSLColor({h: 231, s: 48, l: 48}),
+ new HSLColor({h: 207, s: 90, l: 54}),
+ new HSLColor({h: 199, s: 98, l: 48}),
+ new HSLColor({h: 187, s: 100, l: 42}),
+ new HSLColor({h: 174, s: 100, l: 29}),
+ new HSLColor({h: 122, s: 39, l: 49}),
+ new HSLColor({h: 88, s: 50, l: 53}),
+ new HSLColor({h: 66, s: 70, l: 54}),
+ new HSLColor({h: 45, s: 100, l: 51}),
+ new HSLColor({h: 36, s: 100, l: 50}),
+ new HSLColor({h: 14, s: 100, l: 57}),
+ new HSLColor({h: 16, s: 25, l: 38}),
+ new HSLColor({h: 200, s: 18, l: 46}),
+ new HSLColor({h: 54, s: 100, l: 62}),
];
-export const GRAY_COLOR: Color = {
- h: 0,
- s: 0,
- l: 62,
-};
+const WHITE_COLOR = new HSLColor([0, 0, 100]);
+const BLACK_COLOR = new HSLColor([0, 0, 0]);
+const GRAY_COLOR = new HSLColor([0, 0, 90]);
+
+const MD_PALETTE: ColorScheme[] = MD_PALETTE_RAW.map((color): ColorScheme => {
+ const base = color.lighten(10, 60).desaturate(20);
+ const variant = base.lighten(30, 80).desaturate(20);
+
+ return {
+ base,
+ variant,
+ disabled: GRAY_COLOR,
+ textBase: WHITE_COLOR, // White text suits MD colors quite well
+ textVariant: WHITE_COLOR,
+ textDisabled: WHITE_COLOR, // Low contrast is on purpose
+ };
+});
+
+// Create a color scheme based on a single color, which defines the variant
+// color as a slightly darker and more saturated version of the base color.
+export function makeColorScheme(base: Color, variant?: Color): ColorScheme {
+ variant = variant ?? base.darken(15).saturate(15);
+
+ return {
+ base,
+ variant,
+ disabled: GRAY_COLOR,
+ textBase: base.perceivedBrightness >= PERCEIVED_BRIGHTNESS_LIMIT ?
+ BLACK_COLOR :
+ WHITE_COLOR,
+ textVariant: variant.perceivedBrightness >= PERCEIVED_BRIGHTNESS_LIMIT ?
+ BLACK_COLOR :
+ WHITE_COLOR,
+ textDisabled: WHITE_COLOR, // Low contrast is on purpose
+ };
+}
+
+const GRAY = makeColorScheme(new HSLColor([0, 0, 62]));
+const DESAT_RED = makeColorScheme(new HSLColor([3, 30, 49]));
+const DARK_GREEN = makeColorScheme(new HSLColor([120, 44, 34]));
+const LIME_GREEN = makeColorScheme(new HSLColor([75, 55, 47]));
+const TRANSPARENT_WHITE = makeColorScheme(new HSLColor([0, 1, 97], 0.55));
+const ORANGE = makeColorScheme(new HSLColor([36, 100, 50]));
+const INDIGO = makeColorScheme(new HSLColor([231, 48, 48]));
// A piece of wisdom from a long forgotten blog post: "Don't make
// colors you want to change something normal like grey."
-export const UNEXPECTED_PINK_COLOR: Color = {
- h: 330,
- s: 1.0,
- l: 0.706,
-};
+export const UNEXPECTED_PINK = makeColorScheme(new HSLColor([330, 100, 70]));
-export function hueForCpu(cpu: number): number {
- return (128 + (32 * cpu)) % 256;
+// Selects a predictable color scheme from a palette of material design colors,
+// based on a string seed.
+function materialColorScheme(seed: string): ColorScheme {
+ const colorIdx = hash(seed, MD_PALETTE.length);
+ return MD_PALETTE[colorIdx];
}
-const DESAT_RED: Color = {
- h: 3,
- s: 30,
- l: 49,
-};
-const DARK_GREEN: Color = {
- h: 120,
- s: 44,
- l: 34,
-};
-const LIME_GREEN: Color = {
- h: 75,
- s: 55,
- l: 47,
-};
-const TRANSPARENT_WHITE: Color = {
- h: 0,
- s: 1,
- l: 97,
- a: 0.55,
-};
-const ORANGE: Color = {
- h: 36,
- s: 100,
- l: 50,
-};
-const INDIGO: Color = {
- h: 231,
- s: 48,
- l: 48,
-};
+const proceduralColorCache = new Map<string, ColorScheme>();
-export function colorForState(state: string): Readonly<Color> {
+// Procedurally generates a predictable color scheme based on a string seed.
+function proceduralColorScheme(seed: string): ColorScheme {
+ const colorScheme = proceduralColorCache.get(seed);
+ if (colorScheme) {
+ return colorScheme;
+ } else {
+ const hue = hash(seed, 360);
+ // Saturation 100 would give the most differentiation between colors, but
+ // it's garish.
+ const saturation = 80;
+
+ // Prefer using HSLuv, not the browser's built-in vanilla HSL handling. This
+ // is because this function chooses hue/lightness uniform at random, but HSL
+ // is not perceptually uniform.
+ // See https://www.boronine.com/2012/03/26/Color-Spaces-for-Human-Beings/.
+ const base =
+ new HSLuvColor({h: hue, s: saturation, l: hash(seed + 'x', 40) + 40});
+ const variant = new HSLuvColor({h: hue, s: saturation, l: 30});
+ const colorScheme = makeColorScheme(base, variant);
+
+ proceduralColorCache.set(seed, colorScheme);
+
+ return colorScheme;
+ }
+}
+
+export function colorForState(state: string): ColorScheme {
if (state === 'Running') {
return DARK_GREEN;
} else if (state.startsWith('Runnable')) {
@@ -113,163 +183,59 @@
return INDIGO;
}
-export function textColorForState(stateCode: string): string {
- const background = colorForState(stateCode);
- return background.l > 80 ? '#404040' : '#fff';
+export function colorForTid(tid: number): ColorScheme {
+ return materialColorScheme(tid.toString());
}
-export function colorForString(identifier: string): Color {
- const colorIdx = hash(identifier, MD_PALETTE.length);
- return Object.assign({}, MD_PALETTE[colorIdx]);
-}
-
-export function colorForTid(tid: number): Color {
- return colorForString(tid.toString());
-}
-
-export function colorForThread(thread?: {pid?: number, tid: number}): Color {
+export function colorForThread(thread?: {pid?: number, tid: number}):
+ ColorScheme {
if (thread === undefined) {
- return Object.assign({}, GRAY_COLOR);
+ return GRAY;
}
const tid = thread.pid ? thread.pid : thread.tid;
return colorForTid(tid);
}
-// 40 different random hues 9 degrees apart.
+export function colorForCpu(cpu: number): Color {
+ if (USE_CONSISTENT_COLORS.get()) {
+ return materialColorScheme(cpu.toString()).base;
+ } else {
+ const hue = (128 + (32 * cpu)) % 256;
+ return new HSLColor({h: hue, s: 50, l: 50});
+ }
+}
+
export function randomColor(): string {
- const hue = Math.floor(Math.random() * 40) * 9;
- return '#' + hsl.hex([hue, 90, 30]);
+ const rand = Math.random();
+ if (USE_CONSISTENT_COLORS.get()) {
+ return materialColorScheme(rand.toString()).base.cssString;
+ } else {
+ // 40 different random hues 9 degrees apart.
+ const hue = Math.floor(rand * 40) * 9;
+ return '#' + hsl.hex([hue, 90, 30]);
+ }
}
-// Chooses a color uniform at random based on hash(sliceName). Returns [hue,
-// saturation, lightness].
-//
-// Prefer converting this to an RGB color using hsluv, not the browser's
-// built-in vanilla HSL handling. This is because this function chooses
-// hue/lightness uniform at random, but HSL is not perceptually uniform. See
-// https://www.boronine.com/2012/03/26/Color-Spaces-for-Human-Beings/.
-//
-// If isSelected, the color will be particularly dark, making it stand out.
-export function hslForSlice(
- sliceName: string, isSelected: boolean|null): [number, number, number] {
- const hue = hash(sliceName, 360);
- // Saturation 100 would give the most differentiation between colors, but it's
- // garish.
- const saturation = 80;
- const lightness = isSelected ? 30 : hash(sliceName + 'x', 40) + 40;
- return [hue, saturation, lightness];
-}
-
-// Lightens the color for thread slices to represent wall time.
-export function colorForThreadIdleSlice(
- hue: number,
- saturation: number,
- lightness: number,
- isSelected: boolean|null): string {
- // Increase lightness by 80% when selected and 40% otherwise,
- // without exceeding 88.
- let newLightness = isSelected ? lightness * 1.8 : lightness * 1.4;
- newLightness = Math.min(newLightness, 88);
- return cachedHsluvToHex(hue, saturation, newLightness);
-}
-
-export function colorCompare(x: Color, y: Color): number {
- return (x.h - y.h) || (x.s - y.s) || (x.l - y.l);
-}
-
-// Return true if two colors have the same value.
-export function colorsEqual(a: Color, b: Color): boolean {
- return a.h === b.h && a.s === b.s && a.l === b.l && a.a === b.a;
-}
-
-export function getColorForSlice(
- sliceName: string, hasFocus: boolean|null): Color {
+export function getColorForSlice(sliceName: string): ColorScheme {
const name = sliceName.replace(/( )?\d+/g, '');
- const [hue, saturation, lightness] = hslForSlice(name, hasFocus);
-
- return {
- h: hue,
- s: saturation,
- l: lightness,
- };
+ if (USE_CONSISTENT_COLORS.get()) {
+ return materialColorScheme(name);
+ } else {
+ return proceduralColorScheme(name);
+ }
}
-const LIGHTNESS_MAX = 100;
-const LIGHTNESS_MIN = 0;
-
-// Lighten color by a percentage.
-export function colorLighten(color: Color, amount: number): Color {
- return {
- ...color,
- l: clamp(color.l + amount, LIGHTNESS_MIN, LIGHTNESS_MAX),
- };
+export function colorForFtrace(name: string): ColorScheme {
+ return materialColorScheme(name);
}
-// Darken color by a percentage.
-export function colorDarken(color: Color, amount: number): Color {
- return colorLighten(color, -amount);
-}
-
-const SATURATION_MAX = 100;
-const SATURATION_MIN = 0;
-
-// Saturate color by a percentage.
-export function colorSaturate(color: Color, amount: number): Color {
- return {
- ...color,
- s: clamp(color.s + amount, SATURATION_MIN, SATURATION_MAX),
- };
-}
-
-// Desaturate color by a percentage.
-export function colorDesaturate(color: Color, amount: number): Color {
- return colorSaturate(color, -amount);
-}
-
-// Convert color to RGB values in the range 0-255
-export function colorToRGB(color: Color): [number, number, number] {
- const h = color.h;
- const s = color.s / SATURATION_MAX;
- const l = color.l / LIGHTNESS_MAX;
-
- const c = (1 - Math.abs(2 * l - 1)) * s;
- const x = c * (1 - Math.abs((h / 60) % 2 - 1));
- const m = l - c / 2;
-
- let [r, g, b] = [0, 0, 0];
-
- if (0 <= h && h < 60) {
- [r, g, b] = [c, x, 0];
- } else if (60 <= h && h < 120) {
- [r, g, b] = [x, c, 0];
- } else if (120 <= h && h < 180) {
- [r, g, b] = [0, c, x];
- } else if (180 <= h && h < 240) {
- [r, g, b] = [0, x, c];
- } else if (240 <= h && h < 300) {
- [r, g, b] = [x, 0, c];
- } else if (300 <= h && h < 360) {
- [r, g, b] = [c, 0, x];
+export function colorForSample(callsiteId: number, isHovered: boolean): string {
+ let colorScheme;
+ if (USE_CONSISTENT_COLORS.get()) {
+ colorScheme = materialColorScheme(String(callsiteId));
+ } else {
+ colorScheme = proceduralColorScheme(String(callsiteId));
}
- // Convert to 0-255 range
- r = Math.round((r + m) * 255);
- g = Math.round((g + m) * 255);
- b = Math.round((b + m) * 255);
-
- return [r, g, b];
-}
-
-// Get whether a color should be considered "light" based on its perceived
-// brightness.
-export function colorIsLight(color: Color): boolean {
- // YIQ calculation from https://24ways.org/2010/calculating-color-contrast
- // hsluvToRgb returns rgb in range 0..1
- const [r, g, b] = hsluvToRgb([color.h, color.s, color.l]);
- const yiq = ((r * 255 * 299) + (g * 255 * 587) + (b * 255 * 114)) / 1000;
- return yiq >= 128;
-}
-
-export function colorIsDark(color: Color): boolean {
- return !colorIsLight(color);
+ return isHovered ? colorScheme.variant.cssString : colorScheme.base.cssString;
}
diff --git a/ui/src/common/colorizer_unittest.ts b/ui/src/common/colorizer_unittest.ts
index a163568..ffc931e 100644
--- a/ui/src/common/colorizer_unittest.ts
+++ b/ui/src/common/colorizer_unittest.ts
@@ -13,15 +13,8 @@
// limitations under the License.
import {
- Color,
- colorCompare,
+ colorForCpu,
colorForThread,
- colorIsLight,
- colorLighten,
- colorSaturate,
- colorsEqual,
- colorToRGB,
- hueForCpu,
} from './colorizer';
const PROCESS_A_THREAD_A = {
@@ -61,66 +54,12 @@
expect(colorUnkA).toEqual(colorUnkB);
});
-test('it copies colors', () => {
+test('it doesn\'t copy colors', () => {
const a = colorForThread(PROCESS_A_THREAD_A);
const b = colorForThread(PROCESS_A_THREAD_A);
- expect(a === b).toEqual(false);
+ expect(a).toBe(b);
});
test('it gives different cpus different hues', () => {
- expect(hueForCpu(0)).not.toEqual(hueForCpu(1));
-});
-
-test('colorCompare', () => {
- const col: Color = {h: 123, s: 66, l: 45};
-
- expect(colorCompare({...col}, col)).toBe(0);
-
- expect(colorCompare({...col, h: 34}, col)).toBeLessThan(0);
- expect(colorCompare({...col, h: 156}, col)).toBeGreaterThan(0);
-
- expect(colorCompare({...col, s: 22}, col)).toBeLessThan(0);
- expect(colorCompare({...col, s: 100}, col)).toBeGreaterThan(0);
-
- expect(colorCompare({...col, l: 22}, col)).toBeLessThan(0);
- expect(colorCompare({...col, l: 76}, col)).toBeGreaterThan(0);
-});
-
-test('colorsEqual', () => {
- const col: Color = {h: 123, s: 66, l: 45};
- expect(colorsEqual(col, {h: 123, s: 66, l: 45})).toBeTruthy();
- expect(colorsEqual(col, {h: 86, s: 66, l: 45})).toBeFalsy();
- expect(colorsEqual(col, {h: 123, s: 43, l: 45})).toBeFalsy();
- expect(colorsEqual(col, {h: 123, s: 43, l: 78})).toBeFalsy();
-});
-
-test('colorLighten', () => {
- const col: Color = {h: 123, s: 66, l: 45};
- expect(colorLighten(col, 20)).toEqual({...col, l: 65});
- expect(colorLighten(col, 100)).toEqual({...col, l: 100});
- expect(colorLighten(col, -100)).toEqual({...col, l: 0});
-});
-
-test('colorSaturate', () => {
- const col: Color = {h: 123, s: 66, l: 45};
- expect(colorSaturate(col, 20)).toEqual({...col, s: 86});
- expect(colorSaturate(col, 100)).toEqual({...col, s: 100});
- expect(colorSaturate(col, -100)).toEqual({...col, s: 0});
-});
-
-test('colorToRGB', () => {
- // Pick a few well-known conversions to check we're in the right ballpark.
- expect(colorToRGB({h: 0, s: 0, l: 0})).toEqual([0, 0, 0]);
- expect(colorToRGB({h: 0, s: 100, l: 50})).toEqual([255, 0, 0]);
- expect(colorToRGB({h: 120, s: 100, l: 50})).toEqual([0, 255, 0]);
- expect(colorToRGB({h: 240, s: 100, l: 50})).toEqual([0, 0, 255]);
-});
-
-test('lightness calculations', () => {
- // Pick a few obvious light/dark colours to check we're in the right ballpark.
- expect(colorIsLight({h: 0, s: 0, l: 0})).toBeFalsy();
- expect(colorIsLight({h: 0, s: 0, l: 100})).toBeTruthy();
-
- expect(colorIsLight({h: 0, s: 0, l: 40})).toBeFalsy();
- expect(colorIsLight({h: 0, s: 0, l: 60})).toBeTruthy();
+ expect(colorForCpu(0)).not.toEqual(colorForCpu(1));
});
diff --git a/ui/src/common/track_adapter.ts b/ui/src/common/track_adapter.ts
index 8dbb089..7514c5f 100644
--- a/ui/src/common/track_adapter.ts
+++ b/ui/src/common/track_adapter.ts
@@ -18,7 +18,8 @@
import {assertExists} from '../base/logging';
import {duration, Span, time} from '../base/time';
import {PxSpan, TimeScale} from '../frontend/time_scale';
-import {NewTrackArgs, SliceRect} from '../frontend/track';
+import {NewTrackArgs} from '../frontend/track';
+import {SliceRect} from '../public';
import {EngineProxy} from '../trace_processor/engine';
import {BasicAsyncTrack} from './basic_async_track';
diff --git a/ui/src/controller/aggregation/slice_aggregation_controller.ts b/ui/src/controller/aggregation/slice_aggregation_controller.ts
index 4de7003..7543110 100644
--- a/ui/src/controller/aggregation/slice_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/slice_aggregation_controller.ts
@@ -17,7 +17,9 @@
import {Area, Sorting} from '../../common/state';
import {globals} from '../../frontend/globals';
import {Engine} from '../../trace_processor/engine';
-import {ASYNC_SLICE_TRACK_KIND} from '../../tracks/async_slices';
+import {
+ ASYNC_SLICE_TRACK_KIND,
+} from '../../tracks/async_slices/async_slice_track';
import {SLICE_TRACK_KIND} from '../../tracks/chrome_slices';
import {AggregationController} from './aggregation_controller';
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index c123e78..8c6433b 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -39,7 +39,7 @@
STR_NULL,
} from '../trace_processor/query_result';
import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../tracks/actual_frames';
-import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices';
+import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices/async_slice_track';
import {
ENABLE_SCROLL_JANK_PLUGIN_V2,
getScrollJankTracks,
@@ -288,14 +288,25 @@
}
}
- const track: AddTrackArgs = {
- uri: `perfetto.AsyncSlices#${rawName}`,
- trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
- trackGroup,
- name,
- };
+ if (showV1()) {
+ const track: AddTrackArgs = {
+ uri: `perfetto.AsyncSlices#${rawName}`,
+ trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+ trackGroup,
+ name,
+ };
+ this.tracksToAdd.push(track);
+ }
- this.tracksToAdd.push(track);
+ if (showV2()) {
+ const track: AddTrackArgs = {
+ uri: `perfetto.AsyncSlices#${rawName}.v2`,
+ trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+ trackGroup,
+ name,
+ };
+ this.tracksToAdd.push(track);
+ }
}
}
@@ -1022,12 +1033,24 @@
processName,
kind: ASYNC_SLICE_TRACK_KIND,
});
- this.tracksToAdd.push({
- uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}`,
- name,
- trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
- trackGroup: uuid,
- });
+
+ if (showV1()) {
+ this.tracksToAdd.push({
+ uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}`,
+ name,
+ trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+ trackGroup: uuid,
+ });
+ }
+
+ if (showV2()) {
+ this.tracksToAdd.push({
+ uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}.v2`,
+ name,
+ trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+ trackGroup: uuid,
+ });
+ }
}
}
@@ -1078,12 +1101,24 @@
const kind = ACTUAL_FRAMES_SLICE_TRACK_KIND;
const name =
getTrackName({name: trackName, upid, pid, processName, kind});
- this.tracksToAdd.push({
- uri: `perfetto.ActualFrames#${upid}`,
- name,
- trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK,
- trackGroup: uuid,
- });
+
+ if (showV1()) {
+ this.tracksToAdd.push({
+ uri: `perfetto.ActualFrames#${upid}`,
+ name,
+ trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK,
+ trackGroup: uuid,
+ });
+ }
+
+ if (showV2()) {
+ this.tracksToAdd.push({
+ uri: `perfetto.ActualFrames#${upid}.v2`,
+ name,
+ trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK,
+ trackGroup: uuid,
+ });
+ }
}
}
@@ -1135,12 +1170,24 @@
const kind = EXPECTED_FRAMES_SLICE_TRACK_KIND;
const name =
getTrackName({name: trackName, upid, pid, processName, kind});
- this.tracksToAdd.push({
- uri: `perfetto.ExpectedFrames#${upid}`,
- name,
- trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK,
- trackGroup: uuid,
- });
+
+ if (showV1()) {
+ this.tracksToAdd.push({
+ uri: `perfetto.ExpectedFrames#${upid}`,
+ name,
+ trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK,
+ trackGroup: uuid,
+ });
+ }
+
+ if (showV2()) {
+ this.tracksToAdd.push({
+ uri: `perfetto.ExpectedFrames#${upid}.v2`,
+ name,
+ trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK,
+ trackGroup: uuid,
+ });
+ }
}
}
diff --git a/ui/src/frontend/aggregation_panel.ts b/ui/src/frontend/aggregation_panel.ts
index ea90a3f..fed608c 100644
--- a/ui/src/frontend/aggregation_panel.ts
+++ b/ui/src/frontend/aggregation_panel.ts
@@ -20,7 +20,7 @@
Column,
ThreadStateExtra,
} from '../common/aggregation_data';
-import {colorForState, textColorForState} from '../common/colorizer';
+import {colorForState} from '../common/colorizer';
import {translateState} from '../common/thread_state';
import {globals} from './globals';
@@ -122,15 +122,14 @@
if (data === undefined) return undefined;
const states = [];
for (let i = 0; i < data.states.length; i++) {
- const color = colorForState(data.states[i]);
- const textColor = textColorForState(data.states[i]);
+ const colorScheme = colorForState(data.states[i]);
const width = data.values[i] / data.totalMs * 100;
states.push(
m('.state',
{
style: {
- background: `hsl(${color.h},${color.s}%,${color.l}%)`,
- color: `${textColor}`,
+ background: colorScheme.base.cssString,
+ color: colorScheme.textBase.cssString,
width: `${width}%`,
},
},
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index e8a0cbf..c0c637e 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -27,27 +27,19 @@
drawIncompleteSlice,
drawTrackHoverTooltip,
} from '../common/canvas_utils';
-import {
- Color,
- colorCompare,
- colorDesaturate,
- colorIsLight,
- colorLighten,
- colorsEqual,
- UNEXPECTED_PINK_COLOR,
-} from '../common/colorizer';
+import {colorCompare} from '../common/color';
+import {UNEXPECTED_PINK} from '../common/colorizer';
import {Selection, SelectionKind} from '../common/state';
import {raf} from '../core/raf_scheduler';
-import {Slice} from '../public';
+import {Slice, SliceRect} from '../public';
import {LONG, NUM} from '../trace_processor/query_result';
import {checkerboardExcept} from './checkerboard';
import {globals} from './globals';
-import {cachedHsluvToHex} from './hsluv_cache';
import {DEFAULT_SLICE_LAYOUT, SliceLayout} from './slice_layout';
import {constraintsToQuerySuffix} from './sql_utils';
import {PxSpan, TimeScale} from './time_scale';
-import {NewTrackArgs, SliceRect, TrackBase} from './track';
+import {NewTrackArgs, TrackBase} from './track';
import {BUCKETS_PER_PIXEL, CacheKey, TrackCache} from './track_cache';
// The common class that underpins all tracks drawing slices.
@@ -59,7 +51,7 @@
const SLICE_MIN_WIDTH_FOR_TEXT_PX = 5;
const SLICE_MIN_WIDTH_PX = 1 / BUCKETS_PER_PIXEL;
const CHEVRON_WIDTH_PX = 10;
-const DEFAULT_SLICE_COLOR = UNEXPECTED_PINK_COLOR;
+const DEFAULT_SLICE_COLOR = UNEXPECTED_PINK;
// Exposed and standalone to allow for testing without making this
// visible to subclasses.
@@ -423,13 +415,15 @@
// Second pass: fill slices by color.
const vizSlicesByColor = vizSlices.slice();
- vizSlicesByColor.sort((a, b) => colorCompare(a.color, b.color));
+ vizSlicesByColor.sort(
+ (a, b) => colorCompare(a.colorScheme.base, b.colorScheme.base));
let lastColor = undefined;
for (const slice of vizSlicesByColor) {
- if (slice.color !== lastColor) {
- lastColor = slice.color;
- const {h, s, l} = slice.color;
- ctx.fillStyle = cachedHsluvToHex(h, s, l);
+ const color = slice.isHighlighted ? slice.colorScheme.variant.cssString :
+ slice.colorScheme.base.cssString;
+ if (color !== lastColor) {
+ lastColor = color;
+ ctx.fillStyle = color;
}
const y = padding + slice.depth * (sliceHeight + rowSpacing);
if (slice.flags & SLICE_FLAGS_INSTANT) {
@@ -444,7 +438,7 @@
}
// Pass 2.5: Draw fillRatio light section.
- let prevColor: Color|undefined;
+ ctx.fillStyle = `#FFFFFF50`;
for (const slice of vizSlicesByColor) {
// Can't draw fill ratio on incomplete or instant slices.
if (slice.flags & (SLICE_FLAGS_INCOMPLETE | SLICE_FLAGS_INSTANT)) {
@@ -468,17 +462,6 @@
continue;
}
- // Lighten and desaturate the slice color
- const color = getFillRatioLightColor(slice.color);
-
- // Set color if not set previously
- // Slices are sorted by color and light tint is a pure function of slice
- // color so we should be able to re-use colors quite frequently
- if (prevColor === undefined || !colorsEqual(color, prevColor)) {
- ctx.fillStyle = cachedHsluvToHex(color.h, color.s, color.l);
- prevColor = color;
- }
-
const y = padding + slice.depth * (sliceHeight + rowSpacing);
const x = slice.x + (sliceDrawWidth - lightSectionDrawWidth);
ctx.fillRect(x, y, lightSectionDrawWidth, sliceHeight);
@@ -495,7 +478,9 @@
}
// Change the title color dynamically depending on contrast.
- ctx.fillStyle = colorIsLight(slice.color) ? 'black' : 'white';
+ const textColor = slice.isHighlighted ? slice.colorScheme.textVariant :
+ slice.colorScheme.textBase;
+ ctx.fillStyle = textColor.cssString;
const title = cropText(slice.title, charWidth, slice.w);
const rectXCenter = slice.x + slice.w / 2;
const y = padding + slice.depth * (sliceHeight + rowSpacing);
@@ -527,9 +512,9 @@
// Draw a thicker border around the selected slice (or chevron).
const slice = discoveredSelection;
- const color = slice.color;
+ const color = slice.colorScheme;
const y = padding + slice.depth * (sliceHeight + rowSpacing);
- ctx.strokeStyle = cachedHsluvToHex(color.h, 100, 10);
+ ctx.strokeStyle = color.base.setHSL({s: 100, l: 10}).cssString;
ctx.beginPath();
const THICKNESS = 3;
ctx.lineWidth = THICKNESS;
@@ -786,8 +771,8 @@
// The derived class doesn't need to initialize these. They are
// rewritten on every renderCanvas() call. We just need to initialize
// them to something.
- baseColor: DEFAULT_SLICE_COLOR,
- color: DEFAULT_SLICE_COLOR,
+ colorScheme: DEFAULT_SLICE_COLOR,
+ isHighlighted: false,
};
}
@@ -946,15 +931,7 @@
for (const slice of slices) {
const isHovering = globals.state.highlightedSliceId === slice.id ||
(this.hoveredSlice && this.hoveredSlice.title === slice.title);
- if (isHovering) {
- slice.color = {
- h: slice.baseColor.h,
- s: slice.baseColor.s,
- l: 30,
- };
- } else {
- slice.color = slice.baseColor;
- }
+ slice.isHighlighted = !!isHovering;
}
}
@@ -1007,7 +984,3 @@
// Input args (BaseSliceTrack -> Impl):
slice: S; // The slice which is clicked.
}
-
-function getFillRatioLightColor(color: Color): Color {
- return colorLighten(colorDesaturate(color, 15), 15);
-}
diff --git a/ui/src/frontend/base_slice_track_unittest.ts b/ui/src/frontend/base_slice_track_unittest.ts
index 207105f..73fca19 100644
--- a/ui/src/frontend/base_slice_track_unittest.ts
+++ b/ui/src/frontend/base_slice_track_unittest.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Time} from '../base/time';
-import {GRAY_COLOR} from '../common/colorizer';
+import {UNEXPECTED_PINK} from '../common/colorizer';
import {Slice} from '../public';
import {
@@ -35,9 +35,9 @@
flags: 0,
title: '',
subTitle: '',
- baseColor: GRAY_COLOR,
- color: GRAY_COLOR,
+ colorScheme: UNEXPECTED_PINK,
fillRatio: 1,
+ isHighlighted: false,
};
}
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index ed47282..876c73d 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -15,12 +15,12 @@
import {time} from '../base/time';
import {pluginManager} from '../common/plugins';
import {TrackState} from '../common/state';
+import {SliceRect} from '../public';
import {TRACK_SHELL_WIDTH} from './css_constants';
import {ALL_CATEGORIES, getFlowCategories} from './flow_events_panel';
import {Flow, FlowPoint, globals} from './globals';
import {PanelVNode} from './panel';
-import {SliceRect} from './track';
import {TrackGroupPanel} from './track_group_panel';
import {TrackPanel} from './track_panel';
diff --git a/ui/src/frontend/ftrace_panel.ts b/ui/src/frontend/ftrace_panel.ts
index c313726..a77d31b 100644
--- a/ui/src/frontend/ftrace_panel.ts
+++ b/ui/src/frontend/ftrace_panel.ts
@@ -16,7 +16,7 @@
import {time, Time} from '../base/time';
import {Actions} from '../common/actions';
-import {colorForString} from '../common/colorizer';
+import {colorForFtrace} from '../common/colorizer';
import {StringListPatch} from '../common/state';
import {DetailsShell} from '../widgets/details_shell';
import {
@@ -177,12 +177,7 @@
const rank = i + offset;
- const color = colorForString(name);
- const hsl = `hsl(
- ${color.h},
- ${color.s - 20}%,
- ${Math.min(color.l + 10, 60)}%
- )`;
+ const color = colorForFtrace(name).base.cssString;
rows.push(m(
`.row`,
@@ -192,7 +187,7 @@
onmouseout: this.onRowOut.bind(this),
},
m('.cell', timestamp),
- m('.cell', m('span.colour', {style: {background: hsl}}), name),
+ m('.cell', m('span.colour', {style: {background: color}}), name),
m('.cell', cpu),
m('.cell', process),
m('.cell', args),
diff --git a/ui/src/frontend/hsluv_cache.ts b/ui/src/frontend/hsluv_cache.ts
deleted file mode 100644
index 44cafa6..0000000
--- a/ui/src/frontend/hsluv_cache.ts
+++ /dev/null
@@ -1,39 +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 {hsluvToHex} from 'hsluv';
-
-class HsluvCache {
- storage = new Map<number, string>();
-
- get(hue: number, saturation: number, lightness: number): string {
- const key = hue * 1e6 + saturation * 1e3 + lightness;
- const value = this.storage.get(key);
-
- if (value === undefined) {
- const computed = hsluvToHex([hue, saturation, lightness]);
- this.storage.set(key, computed);
- return computed;
- }
-
- return value;
- }
-}
-
-const cache = new HsluvCache();
-
-export function cachedHsluvToHex(
- hue: number, saturation: number, lightness: number): string {
- return cache.get(hue, saturation, lightness);
-}
diff --git a/ui/src/frontend/named_slice_track.ts b/ui/src/frontend/named_slice_track.ts
index b5ab169..d8c3514 100644
--- a/ui/src/frontend/named_slice_track.ts
+++ b/ui/src/frontend/named_slice_track.ts
@@ -58,8 +58,8 @@
const baseSlice = super.rowToSlice(row);
// Ignore PIDs or numeric arguments when hashing.
const name = row.name || '';
- const baseColor = getColorForSlice(name, false);
- return {...baseSlice, title: name, baseColor};
+ const colorScheme = getColorForSlice(name);
+ return {...baseSlice, title: name, colorScheme};
}
onSliceOver(args: OnSliceOverArgs<T['slice']>) {
diff --git a/ui/src/frontend/overview_timeline_panel.ts b/ui/src/frontend/overview_timeline_panel.ts
index 7ad81ff..edda1d3 100644
--- a/ui/src/frontend/overview_timeline_panel.ts
+++ b/ui/src/frontend/overview_timeline_panel.ts
@@ -20,7 +20,7 @@
Time,
time,
} from '../base/time';
-import {hueForCpu} from '../common/colorizer';
+import {colorForCpu} from '../common/colorizer';
import {timestampFormat, TimestampFormat} from '../common/timestamp_format';
import {
@@ -135,7 +135,8 @@
const xEnd = Math.ceil(this.timeScale.timeToPx(loads[i].end));
const yOff = Math.floor(headerHeight + y * trackHeight);
const lightness = Math.ceil((1 - loads[i].load * 0.7) * 100);
- ctx.fillStyle = `hsl(${hueForCpu(y)}, 50%, ${lightness}%)`;
+ const color = colorForCpu(y).setHSL({s: 50, l: lightness});
+ ctx.fillStyle = color.cssString;
ctx.fillRect(xStart, yOff, xEnd - xStart, Math.ceil(trackHeight));
}
y++;
diff --git a/ui/src/frontend/slice.ts b/ui/src/frontend/slice.ts
deleted file mode 100644
index 7e82268..0000000
--- a/ui/src/frontend/slice.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {duration, time} from '../base/time';
-import {Color} from '../common/colorizer';
-
-export interface Slice {
- // These properties are updated only once per query result when the Slice
- // object is created and don't change afterwards.
- readonly id: number;
- readonly startNsQ: time;
- readonly endNsQ: time;
- readonly durNsQ: duration;
- readonly ts: time;
- readonly dur: duration;
- readonly depth: number;
- readonly flags: number;
-
- // Each slice can represent some extra numerical information by rendering a
- // portion of the slice with a lighter tint.
- // |fillRatio\ describes the ratio of the normal area to the tinted area
- // width of the slice, normalized between 0.0 -> 1.0.
- // 0.0 means the whole slice is tinted.
- // 1.0 means none of the slice is tinted.
- // E.g. If |fillRatio| = 0.65 the slice will be rendered like this:
- // [############|*******]
- // ^------------^-------^
- // Normal Light
- readonly fillRatio: number;
-
- // These can be changed by the Impl.
- title: string;
- subTitle: string;
- baseColor: Color;
- color: Color;
-}
diff --git a/ui/src/frontend/slice_track_base.ts b/ui/src/frontend/slice_track_base.ts
index 37c2ec8..bf66782 100644
--- a/ui/src/frontend/slice_track_base.ts
+++ b/ui/src/frontend/slice_track_base.ts
@@ -16,18 +16,14 @@
import {Actions} from '../common/actions';
import {BasicAsyncTrack} from '../common/basic_async_track';
import {cropText, drawIncompleteSlice} from '../common/canvas_utils';
-import {
- colorForThreadIdleSlice,
- getColorForSlice,
-} from '../common/colorizer';
+import {getColorForSlice} from '../common/colorizer';
import {HighPrecisionTime} from '../common/high_precision_time';
import {TrackData} from '../common/track_data';
+import {SliceRect} from '../public';
import {checkerboardExcept} from './checkerboard';
import {globals} from './globals';
-import {cachedHsluvToHex} from './hsluv_cache';
import {PxSpan, TimeScale} from './time_scale';
-import {SliceRect} from './track';
export const SLICE_TRACK_KIND = 'ChromeSliceTrack';
const SLICE_HEIGHT = 18;
@@ -141,11 +137,14 @@
globals.state.highlightedSliceId === sliceId;
const hasFocus = highlighted || isSelected;
- const colorObj = getColorForSlice(title, hasFocus);
+ const colorScheme = getColorForSlice(title);
+ const colorObj = hasFocus ? colorScheme.variant : colorScheme.base;
+ const textColor =
+ hasFocus ? colorScheme.textVariant : colorScheme.textBase;
let color: string;
if (colorOverride === undefined) {
- color = cachedHsluvToHex(colorObj.h, colorObj.s, colorObj.l);
+ color = colorObj.cssString;
} else {
color = colorOverride;
}
@@ -165,7 +164,7 @@
ctx.translate(rect.left, rect.top);
// Draw a rectangle around the selected slice
- ctx.strokeStyle = cachedHsluvToHex(colorObj.h, 100, 10);
+ ctx.strokeStyle = colorObj.setHSL({s: 100, l: 10}).cssString;
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeRect(
@@ -196,9 +195,8 @@
const cpuTimeRatio = data.cpuTimeRatio![i];
const firstPartWidth = rect.width * cpuTimeRatio;
const secondPartWidth = rect.width * (1 - cpuTimeRatio);
- ctx.fillRect(rect.left, rect.top, firstPartWidth, SLICE_HEIGHT);
- ctx.fillStyle = colorForThreadIdleSlice(
- colorObj.h, colorObj.s, colorObj.l, hasFocus);
+ ctx.fillRect(rect.left, rect.top, rect.width, SLICE_HEIGHT);
+ ctx.fillStyle = '#FFFFFF50';
ctx.fillRect(
rect.left + firstPartWidth,
rect.top,
@@ -211,7 +209,7 @@
// Selected case
if (isSelected) {
drawRectOnSelected = () => {
- ctx.strokeStyle = cachedHsluvToHex(colorObj.h, 100, 10);
+ ctx.strokeStyle = colorObj.setHSL({s: 100, l: 10}).cssString;
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeRect(
@@ -222,7 +220,7 @@
// Don't render text when we have less than 5px to play with.
if (rect.width >= 5) {
- ctx.fillStyle = colorObj.l > 65 ? '#404040' : 'white';
+ ctx.fillStyle = textColor.cssString;
const displayText = cropText(title, charWidth, rect.width);
const rectXCenter = rect.left + rect.width / 2;
ctx.textBaseline = 'middle';
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index f83c03f..88565f2 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -15,7 +15,7 @@
import m from 'mithril';
import {duration, Span, time} from '../base/time';
-import {Track, TrackContext} from '../public';
+import {SliceRect, Track, TrackContext} from '../public';
import {EngineProxy} from '../trace_processor/engine';
import {PxSpan, TimeScale} from './time_scale';
@@ -26,27 +26,6 @@
engine: EngineProxy;
}
-// This interface forces track implementations to have some static properties.
-// Typescript does not have abstract static members, which is why this needs to
-// be in a separate interface.
-export interface TrackCreator {
- // Store the kind explicitly as a string as opposed to using class.kind in
- // case we ever minify our code.
- readonly kind: string;
-
- // We need the |create| method because the stored value in the registry can be
- // an abstract class, and we cannot call 'new' on an abstract class.
- create(args: NewTrackArgs): TrackBase;
-}
-
-export interface SliceRect {
- left: number;
- width: number;
- top: number;
- height: number;
- visible: boolean;
-}
-
// The abstract class that needs to be implemented by all tracks.
export abstract class TrackBase implements Track {
protected readonly trackKey: string;
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 90a593a..c877ad1 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -22,7 +22,7 @@
import {pluginManager} from '../common/plugins';
import {TrackState} from '../common/state';
import {raf} from '../core/raf_scheduler';
-import {Migrate, Track, TrackContext} from '../public';
+import {Migrate, SliceRect, Track, TrackContext} from '../public';
import {SELECTION_FILL_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
import {globals} from './globals';
@@ -30,7 +30,6 @@
import {Panel, PanelSize} from './panel';
import {verticalScrollToTrack} from './scroll_helper';
import {PxSpan, TimeScale} from './time_scale';
-import {SliceRect} from './track';
import {
drawVerticalLineAtTime,
} from './vertical_line_helper';
diff --git a/ui/src/frontend/track_registry.ts b/ui/src/frontend/track_registry.ts
deleted file mode 100644
index 92007d7..0000000
--- a/ui/src/frontend/track_registry.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) 2018 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 {Registry} from '../common/registry';
-import {TrackCreator} from './track';
-
-/**
- * Global registry that maps types to TrackCreator.
- */
-export const trackRegistry = Registry.kindRegistry<TrackCreator>();
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index 63750ee..480676c 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -16,10 +16,9 @@
import {Hotkey} from '../base/hotkeys';
import {duration, Span, time} from '../base/time';
-import {Color} from '../common/colorizer';
+import {ColorScheme} from '../common/colorizer';
import {Store} from '../frontend/store';
import {PxSpan, TimeScale} from '../frontend/time_scale';
-import {SliceRect} from '../frontend/track';
import {EngineProxy} from '../trace_processor/engine';
export {createStore, Store} from '../frontend/store';
@@ -60,8 +59,8 @@
// These can be changed by the Impl.
title: string;
subTitle: string;
- baseColor: Color;
- color: Color;
+ colorScheme: ColorScheme;
+ isHighlighted: boolean;
}
export interface Command {
@@ -164,6 +163,14 @@
mountStore<State>(migrate: Migrate<State>): Store<State>;
}
+export interface SliceRect {
+ left: number;
+ width: number;
+ top: number;
+ height: number;
+ visible: boolean;
+}
+
export interface Track {
onCreate(ctx: TrackContext): void;
render(ctx: CanvasRenderingContext2D): void;
diff --git a/ui/src/tracks/actual_frames/actual_frames_track.ts b/ui/src/tracks/actual_frames/actual_frames_track.ts
new file mode 100644
index 0000000..4b6c198b
--- /dev/null
+++ b/ui/src/tracks/actual_frames/actual_frames_track.ts
@@ -0,0 +1,146 @@
+// 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 {BigintMath as BIMath} from '../../base/bigint_math';
+import {duration, time} from '../../base/time';
+import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
+import {
+ EngineProxy,
+} from '../../public';
+import {
+ LONG,
+ LONG_NULL,
+ NUM,
+ STR,
+} from '../../trace_processor/query_result';
+
+export const ACTUAL_FRAMES_SLICE_TRACK_KIND = 'ActualFramesSliceTrack';
+
+const BLUE_COLOR = '#03A9F4'; // Blue 500
+const GREEN_COLOR = '#4CAF50'; // Green 500
+const YELLOW_COLOR = '#FFEB3B'; // Yellow 500
+const RED_COLOR = '#FF5722'; // Red 500
+const LIGHT_GREEN_COLOR = '#C0D588'; // Light Green 500
+const PINK_COLOR = '#F515E0'; // Pink 500
+
+export class ActualFramesTrack extends SliceTrackBase {
+ private maxDur = 0n;
+
+ constructor(
+ private engine: EngineProxy, maxDepth: number, trackKey: string,
+ private trackIds: number[], namespace?: string) {
+ super(maxDepth, trackKey, 'actual_frame_timeline_slice', namespace);
+ }
+
+ async onBoundsChange(start: time, end: time, resolution: duration):
+ Promise<SliceData> {
+ if (this.maxDur === 0n) {
+ const maxDurResult = await this.engine.query(`
+ select
+ max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
+ as maxDur
+ from experimental_slice_layout
+ where filter_track_ids = '${this.trackIds.join(',')}'
+ `);
+ this.maxDur = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
+ }
+
+ const rawResult = await this.engine.query(`
+ SELECT
+ (s.ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
+ s.ts as ts,
+ max(iif(s.dur = -1, (SELECT end_ts FROM trace_bounds) - s.ts, s.dur))
+ as dur,
+ s.layout_depth as layoutDepth,
+ s.name as name,
+ s.id as id,
+ s.dur = 0 as isInstant,
+ s.dur = -1 as isIncomplete,
+ CASE afs.jank_tag
+ WHEN 'Self Jank' THEN '${RED_COLOR}'
+ WHEN 'Other Jank' THEN '${YELLOW_COLOR}'
+ WHEN 'Dropped Frame' THEN '${BLUE_COLOR}'
+ WHEN 'Buffer Stuffing' THEN '${LIGHT_GREEN_COLOR}'
+ WHEN 'SurfaceFlinger Stuffing' THEN '${LIGHT_GREEN_COLOR}'
+ WHEN 'No Jank' THEN '${GREEN_COLOR}'
+ ELSE '${PINK_COLOR}'
+ END as color
+ from experimental_slice_layout s
+ join actual_frame_timeline_slice afs using(id)
+ where
+ filter_track_ids = '${this.trackIds.join(',')}' and
+ s.ts >= ${start - this.maxDur} and
+ s.ts <= ${end}
+ group by tsq, s.layout_depth
+ order by tsq, s.layout_depth
+`);
+
+ const numRows = rawResult.numRows();
+ const slices: SliceData = {
+ start,
+ end,
+ resolution,
+ length: numRows,
+ strings: [],
+ sliceIds: new Float64Array(numRows),
+ starts: new BigInt64Array(numRows),
+ ends: new BigInt64Array(numRows),
+ depths: new Uint16Array(numRows),
+ titles: new Uint16Array(numRows),
+ colors: new Uint16Array(numRows),
+ isInstant: new Uint16Array(numRows),
+ isIncomplete: new Uint16Array(numRows),
+ };
+
+ const stringIndexes = new Map<string, number>();
+ function internString(str: string) {
+ let idx = stringIndexes.get(str);
+ if (idx !== undefined) return idx;
+ idx = slices.strings.length;
+ slices.strings.push(str);
+ stringIndexes.set(str, idx);
+ return idx;
+ }
+
+ const it = rawResult.iter({
+ 'tsq': LONG,
+ 'ts': LONG,
+ 'dur': LONG,
+ 'layoutDepth': NUM,
+ 'id': NUM,
+ 'name': STR,
+ 'isInstant': NUM,
+ 'isIncomplete': NUM,
+ 'color': STR,
+ });
+ for (let i = 0; it.valid(); i++, it.next()) {
+ const startQ = it.tsq;
+ const start = it.ts;
+ const dur = it.dur;
+ const end = start + dur;
+ const minEnd = startQ + resolution;
+ const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
+
+ slices.starts[i] = startQ;
+ slices.ends[i] = endQ;
+ slices.depths[i] = it.layoutDepth;
+ slices.titles[i] = internString(it.name);
+ slices.colors![i] = internString(it.color);
+ slices.sliceIds[i] = it.id;
+ slices.isInstant[i] = it.isInstant;
+ slices.isIncomplete[i] = it.isIncomplete;
+ }
+ return slices;
+ }
+}
diff --git a/ui/src/tracks/actual_frames/actual_frames_track_v2.ts b/ui/src/tracks/actual_frames/actual_frames_track_v2.ts
new file mode 100644
index 0000000..c30e673
--- /dev/null
+++ b/ui/src/tracks/actual_frames/actual_frames_track_v2.ts
@@ -0,0 +1,96 @@
+// 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 {HSLColor} from '../../common/color';
+import {ColorScheme, makeColorScheme} from '../../common/colorizer';
+import {
+ NAMED_ROW,
+ NamedSliceTrack,
+ NamedSliceTrackTypes,
+} from '../../frontend/named_slice_track';
+import {EngineProxy, Slice, STR_NULL} from '../../public';
+
+const BLUE = makeColorScheme(new HSLColor('#03A9F4')); // Blue 500
+const GREEN = makeColorScheme(new HSLColor('#4CAF50')); // Green 500
+const YELLOW = makeColorScheme(new HSLColor('#FFEB3B')); // Yellow 500
+const RED = makeColorScheme(new HSLColor('#FF5722')); // Red 500
+const LIGHT_GREEN =
+ makeColorScheme(new HSLColor('#C0D588')); // Light Green 500
+const PINK = makeColorScheme(new HSLColor('#F515E0')); // Pink 500
+
+export const ACTUAL_FRAME_ROW = {
+ // Base columns (tsq, ts, dur, id, depth).
+ ...NAMED_ROW,
+
+ // Chrome-specific columns.
+ jankTag: STR_NULL,
+};
+export type ActualFrameRow = typeof ACTUAL_FRAME_ROW;
+
+export interface ActualFrameTrackTypes extends NamedSliceTrackTypes {
+ row: ActualFrameRow;
+}
+
+export class ActualFramesTrack extends NamedSliceTrack<ActualFrameTrackTypes> {
+ constructor(
+ engine: EngineProxy, maxDepth: number, trackKey: string,
+ private trackIds: number[]) {
+ super({engine, trackKey});
+ this.sliceLayout.maxDepth = maxDepth + 1;
+ }
+
+ // This is used by the base class to call iter().
+ getRowSpec() {
+ return ACTUAL_FRAME_ROW;
+ }
+
+ getSqlSource(): string {
+ return `
+ SELECT
+ s.ts as ts,
+ s.dur as dur,
+ s.layout_depth as depth,
+ s.name as name,
+ s.id as id,
+ afs.jank_tag as jankTag
+ from experimental_slice_layout s
+ join actual_frame_timeline_slice afs using(id)
+ where
+ filter_track_ids = '${this.trackIds.join(',')}'
+ `;
+ }
+
+ rowToSlice(row: ActualFrameRow): Slice {
+ const baseSlice = super.rowToSlice(row);
+ return {...baseSlice, colorScheme: getColorSchemeForJank(row.jankTag)};
+ }
+}
+
+function getColorSchemeForJank(jankTag: string|null): ColorScheme {
+ switch (jankTag) {
+ case 'Self Jank':
+ return RED;
+ case 'Other Jank':
+ return YELLOW;
+ case 'Dropped Frame':
+ return BLUE;
+ case 'Buffer Stuffing':
+ case 'SurfaceFlinger Stuffing':
+ return LIGHT_GREEN;
+ case 'No Jank':
+ return GREEN;
+ default:
+ return PINK;
+ }
+}
diff --git a/ui/src/tracks/actual_frames/index.ts b/ui/src/tracks/actual_frames/index.ts
index fbc3cf7..8fcd597 100644
--- a/ui/src/tracks/actual_frames/index.ts
+++ b/ui/src/tracks/actual_frames/index.ts
@@ -12,11 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {BigintMath as BIMath} from '../../base/bigint_math';
-import {duration, time} from '../../base/time';
-import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
import {
- EngineProxy,
Plugin,
PluginContext,
PluginContextTrace,
@@ -24,134 +20,19 @@
} from '../../public';
import {getTrackName} from '../../public/utils';
import {
- LONG,
- LONG_NULL,
NUM,
NUM_NULL,
STR,
STR_NULL,
} from '../../trace_processor/query_result';
+import {ActualFramesTrack} from './actual_frames_track';
+import {
+ ActualFramesTrack as ActualFramesTrackV2,
+} from './actual_frames_track_v2';
+
export const ACTUAL_FRAMES_SLICE_TRACK_KIND = 'ActualFramesSliceTrack';
-const BLUE_COLOR = '#03A9F4'; // Blue 500
-const GREEN_COLOR = '#4CAF50'; // Green 500
-const YELLOW_COLOR = '#FFEB3B'; // Yellow 500
-const RED_COLOR = '#FF5722'; // Red 500
-const LIGHT_GREEN_COLOR = '#C0D588'; // Light Green 500
-const PINK_COLOR = '#F515E0'; // Pink 500
-
-class SliceTrack extends SliceTrackBase {
- private maxDur = 0n;
-
- constructor(
- private engine: EngineProxy, maxDepth: number, trackKey: string,
- private trackIds: number[], namespace?: string) {
- super(maxDepth, trackKey, 'actual_frame_timeline_slice', namespace);
- }
-
- async onBoundsChange(start: time, end: time, resolution: duration):
- Promise<SliceData> {
- if (this.maxDur === 0n) {
- const maxDurResult = await this.engine.query(`
- select
- max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
- as maxDur
- from experimental_slice_layout
- where filter_track_ids = '${this.trackIds.join(',')}'
- `);
- this.maxDur = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
- }
-
- const rawResult = await this.engine.query(`
- SELECT
- (s.ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
- s.ts as ts,
- max(iif(s.dur = -1, (SELECT end_ts FROM trace_bounds) - s.ts, s.dur))
- as dur,
- s.layout_depth as layoutDepth,
- s.name as name,
- s.id as id,
- s.dur = 0 as isInstant,
- s.dur = -1 as isIncomplete,
- CASE afs.jank_tag
- WHEN 'Self Jank' THEN '${RED_COLOR}'
- WHEN 'Other Jank' THEN '${YELLOW_COLOR}'
- WHEN 'Dropped Frame' THEN '${BLUE_COLOR}'
- WHEN 'Buffer Stuffing' THEN '${LIGHT_GREEN_COLOR}'
- WHEN 'SurfaceFlinger Stuffing' THEN '${LIGHT_GREEN_COLOR}'
- WHEN 'No Jank' THEN '${GREEN_COLOR}'
- ELSE '${PINK_COLOR}'
- END as color
- from experimental_slice_layout s
- join actual_frame_timeline_slice afs using(id)
- where
- filter_track_ids = '${this.trackIds.join(',')}' and
- s.ts >= ${start - this.maxDur} and
- s.ts <= ${end}
- group by tsq, s.layout_depth
- order by tsq, s.layout_depth
-`);
-
- const numRows = rawResult.numRows();
- const slices: SliceData = {
- start,
- end,
- resolution,
- length: numRows,
- strings: [],
- sliceIds: new Float64Array(numRows),
- starts: new BigInt64Array(numRows),
- ends: new BigInt64Array(numRows),
- depths: new Uint16Array(numRows),
- titles: new Uint16Array(numRows),
- colors: new Uint16Array(numRows),
- isInstant: new Uint16Array(numRows),
- isIncomplete: new Uint16Array(numRows),
- };
-
- const stringIndexes = new Map<string, number>();
- function internString(str: string) {
- let idx = stringIndexes.get(str);
- if (idx !== undefined) return idx;
- idx = slices.strings.length;
- slices.strings.push(str);
- stringIndexes.set(str, idx);
- return idx;
- }
-
- const it = rawResult.iter({
- 'tsq': LONG,
- 'ts': LONG,
- 'dur': LONG,
- 'layoutDepth': NUM,
- 'id': NUM,
- 'name': STR,
- 'isInstant': NUM,
- 'isIncomplete': NUM,
- 'color': STR,
- });
- for (let i = 0; it.valid(); i++, it.next()) {
- const startQ = it.tsq;
- const start = it.ts;
- const dur = it.dur;
- const end = start + dur;
- const minEnd = startQ + resolution;
- const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
-
- slices.starts[i] = startQ;
- slices.ends[i] = endQ;
- slices.depths[i] = it.layoutDepth;
- slices.titles[i] = internString(it.name);
- slices.colors![i] = internString(it.color);
- slices.sliceIds[i] = it.id;
- slices.isInstant[i] = it.isInstant;
- slices.isIncomplete[i] = it.isIncomplete;
- }
- return slices;
- }
-}
-
class ActualFrames implements Plugin {
onActivate(_ctx: PluginContext): void {}
@@ -211,7 +92,22 @@
trackIds,
kind: ACTUAL_FRAMES_SLICE_TRACK_KIND,
track: ({trackKey}) => {
- return new SliceTrack(
+ return new ActualFramesTrack(
+ engine,
+ maxDepth,
+ trackKey,
+ trackIds,
+ );
+ },
+ });
+
+ ctx.registerStaticTrack({
+ uri: `perfetto.ActualFrames#${upid}.v2`,
+ displayName,
+ trackIds,
+ kind: ACTUAL_FRAMES_SLICE_TRACK_KIND,
+ track: ({trackKey}) => {
+ return new ActualFramesTrackV2(
engine,
maxDepth,
trackKey,
diff --git a/ui/src/tracks/async_slices/async_slice_track.ts b/ui/src/tracks/async_slices/async_slice_track.ts
new file mode 100644
index 0000000..5d6e25a
--- /dev/null
+++ b/ui/src/tracks/async_slices/async_slice_track.ts
@@ -0,0 +1,119 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {BigintMath as BIMath} from '../../base/bigint_math';
+import {duration, time} from '../../base/time';
+import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
+import {EngineProxy} from '../../public';
+import {
+ LONG,
+ LONG_NULL,
+ NUM,
+ STR,
+} from '../../trace_processor/query_result';
+
+export const ASYNC_SLICE_TRACK_KIND = 'AsyncSliceTrack';
+
+export class AsyncSliceTrack extends SliceTrackBase {
+ private maxDurNs: duration = 0n;
+
+ constructor(
+ private engine: EngineProxy, maxDepth: number, trackKey: string,
+ private trackIds: number[], namespace?: string) {
+ // TODO is 'slice' right here?
+ super(maxDepth, trackKey, 'slice', namespace);
+ }
+
+ async onBoundsChange(start: time, end: time, resolution: duration):
+ Promise<SliceData> {
+ if (this.maxDurNs === 0n) {
+ const maxDurResult = await this.engine.query(`
+ select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts,
+ dur)) as maxDur from experimental_slice_layout where filter_track_ids
+ = '${this.trackIds.join(',')}'
+ `);
+ this.maxDurNs = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
+ }
+
+ const queryRes = await this.engine.query(`
+ SELECT
+ (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
+ ts,
+ max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as
+ dur, layout_depth as depth, ifnull(name, '[null]') as name, id, dur =
+ 0 as isInstant, dur = -1 as isIncomplete
+ from experimental_slice_layout
+ where
+ filter_track_ids = '${this.trackIds.join(',')}' and
+ ts >= ${start - this.maxDurNs} and
+ ts <= ${end}
+ group by tsq, layout_depth
+ order by tsq, layout_depth
+ `);
+
+ const numRows = queryRes.numRows();
+ const slices: SliceData = {
+ start,
+ end,
+ resolution,
+ length: numRows,
+ strings: [],
+ sliceIds: new Float64Array(numRows),
+ starts: new BigInt64Array(numRows),
+ ends: new BigInt64Array(numRows),
+ depths: new Uint16Array(numRows),
+ titles: new Uint16Array(numRows),
+ isInstant: new Uint16Array(numRows),
+ isIncomplete: new Uint16Array(numRows),
+ };
+
+ const stringIndexes = new Map<string, number>();
+ function internString(str: string) {
+ let idx = stringIndexes.get(str);
+ if (idx !== undefined) return idx;
+ idx = slices.strings.length;
+ slices.strings.push(str);
+ stringIndexes.set(str, idx);
+ return idx;
+ }
+
+ const it = queryRes.iter({
+ tsq: LONG,
+ ts: LONG,
+ dur: LONG,
+ depth: NUM,
+ name: STR,
+ id: NUM,
+ isInstant: NUM,
+ isIncomplete: NUM,
+ });
+ for (let row = 0; it.valid(); it.next(), row++) {
+ const startQ = it.tsq;
+ const start = it.ts;
+ const dur = it.dur;
+ const end = start + dur;
+ const minEnd = startQ + resolution;
+ const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
+
+ slices.starts[row] = startQ;
+ slices.ends[row] = endQ;
+ slices.depths[row] = it.depth;
+ slices.titles[row] = internString(it.name);
+ slices.sliceIds[row] = it.id;
+ slices.isInstant[row] = it.isInstant;
+ slices.isIncomplete[row] = it.isIncomplete;
+ }
+ return slices;
+ }
+}
diff --git a/ui/src/tracks/async_slices/async_slice_track_v2.ts b/ui/src/tracks/async_slices/async_slice_track_v2.ts
new file mode 100644
index 0000000..4e87c9b
--- /dev/null
+++ b/ui/src/tracks/async_slices/async_slice_track_v2.ts
@@ -0,0 +1,45 @@
+// 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 {NamedSliceTrack} from '../../frontend/named_slice_track';
+import {NewTrackArgs} from '../../frontend/track';
+import {Slice} from '../../public';
+
+export class AsyncSliceTrackV2 extends NamedSliceTrack {
+ constructor(
+ args: NewTrackArgs, maxDepth: number, private trackIds: number[]) {
+ super(args);
+ this.sliceLayout.maxDepth = maxDepth + 1;
+ }
+
+ getSqlSource(): string {
+ return `
+ select
+ ts,
+ dur,
+ layout_depth as depth,
+ ifnull(name, '[null]') as name,
+ id,
+ thread_dur as threadDur
+ from experimental_slice_layout
+ where filter_track_ids = '${this.trackIds.join(',')}'
+ `;
+ }
+
+ onUpdatedSlices(slices: Slice[]) {
+ for (const slice of slices) {
+ slice.isHighlighted = (slice === this.hoveredSlice);
+ }
+ }
+}
diff --git a/ui/src/tracks/async_slices/index.ts b/ui/src/tracks/async_slices/index.ts
index 97d43fa..eb3ad88 100644
--- a/ui/src/tracks/async_slices/index.ts
+++ b/ui/src/tracks/async_slices/index.ts
@@ -12,11 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {BigintMath as BIMath} from '../../base/bigint_math';
-import {duration, time} from '../../base/time';
-import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
import {
- EngineProxy,
Plugin,
PluginContext,
PluginContextTrace,
@@ -24,109 +20,17 @@
} from '../../public';
import {getTrackName} from '../../public/utils';
import {
- LONG,
- LONG_NULL,
NUM,
NUM_NULL,
STR,
STR_NULL,
} from '../../trace_processor/query_result';
+import {AsyncSliceTrack} from './async_slice_track';
+import {AsyncSliceTrackV2} from './async_slice_track_v2';
+
export const ASYNC_SLICE_TRACK_KIND = 'AsyncSliceTrack';
-class AsyncSliceTrack extends SliceTrackBase {
- private maxDurNs: duration = 0n;
-
- constructor(
- private engine: EngineProxy, maxDepth: number, trackKey: string,
- private trackIds: number[], namespace?: string) {
- // TODO is 'slice' right here?
- super(maxDepth, trackKey, 'slice', namespace);
- }
-
- async onBoundsChange(start: time, end: time, resolution: duration):
- Promise<SliceData> {
- if (this.maxDurNs === 0n) {
- const maxDurResult = await this.engine.query(`
- select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts,
- dur)) as maxDur from experimental_slice_layout where filter_track_ids
- = '${this.trackIds.join(',')}'
- `);
- this.maxDurNs = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
- }
-
- const queryRes = await this.engine.query(`
- SELECT
- (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
- ts,
- max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as
- dur, layout_depth as depth, ifnull(name, '[null]') as name, id, dur =
- 0 as isInstant, dur = -1 as isIncomplete
- from experimental_slice_layout
- where
- filter_track_ids = '${this.trackIds.join(',')}' and
- ts >= ${start - this.maxDurNs} and
- ts <= ${end}
- group by tsq, layout_depth
- order by tsq, layout_depth
- `);
-
- const numRows = queryRes.numRows();
- const slices: SliceData = {
- start,
- end,
- resolution,
- length: numRows,
- strings: [],
- sliceIds: new Float64Array(numRows),
- starts: new BigInt64Array(numRows),
- ends: new BigInt64Array(numRows),
- depths: new Uint16Array(numRows),
- titles: new Uint16Array(numRows),
- isInstant: new Uint16Array(numRows),
- isIncomplete: new Uint16Array(numRows),
- };
-
- const stringIndexes = new Map<string, number>();
- function internString(str: string) {
- let idx = stringIndexes.get(str);
- if (idx !== undefined) return idx;
- idx = slices.strings.length;
- slices.strings.push(str);
- stringIndexes.set(str, idx);
- return idx;
- }
-
- const it = queryRes.iter({
- tsq: LONG,
- ts: LONG,
- dur: LONG,
- depth: NUM,
- name: STR,
- id: NUM,
- isInstant: NUM,
- isIncomplete: NUM,
- });
- for (let row = 0; it.valid(); it.next(), row++) {
- const startQ = it.tsq;
- const start = it.ts;
- const dur = it.dur;
- const end = start + dur;
- const minEnd = startQ + resolution;
- const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
-
- slices.starts[row] = startQ;
- slices.ends[row] = endQ;
- slices.depths[row] = it.depth;
- slices.titles[row] = internString(it.name);
- slices.sliceIds[row] = it.id;
- slices.isInstant[row] = it.isInstant;
- slices.isIncomplete[row] = it.isIncomplete;
- }
- return slices;
- }
-}
-
class AsyncSlicePlugin implements Plugin {
onActivate(_ctx: PluginContext) {}
@@ -220,6 +124,20 @@
);
},
});
+
+ ctx.registerStaticTrack({
+ uri: `perfetto.AsyncSlices#${rawName}.v2`,
+ displayName,
+ trackIds,
+ kind: ASYNC_SLICE_TRACK_KIND,
+ track: ({trackKey}) => {
+ return new AsyncSliceTrackV2(
+ {engine, trackKey},
+ maxDepth,
+ trackIds,
+ );
+ },
+ });
}
}
@@ -288,6 +206,20 @@
);
},
});
+
+ ctx.registerStaticTrack({
+ uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}.v2`,
+ displayName,
+ trackIds,
+ kind: ASYNC_SLICE_TRACK_KIND,
+ track: ({trackKey}) => {
+ return new AsyncSliceTrackV2(
+ {engine: ctx.engine, trackKey},
+ maxDepth,
+ trackIds,
+ );
+ },
+ });
}
}
}
diff --git a/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts b/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
index 85650ed..b85116f 100644
--- a/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
@@ -12,15 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {
- getColorForSlice,
-} from '../../common/colorizer';
import {globals} from '../../frontend/globals';
-import {
- NamedSliceTrackTypes,
-} from '../../frontend/named_slice_track';
+import {NamedRow, NamedSliceTrackTypes} from '../../frontend/named_slice_track';
import {NewTrackArgs} from '../../frontend/track';
-import {PrimaryTrackSortKey} from '../../public';
+import {PrimaryTrackSortKey, Slice} from '../../public';
import {
CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
@@ -33,7 +28,7 @@
ScrollJankPluginState,
ScrollJankTracks as DecideTracksResult,
} from './index';
-import {DEEP_RED_COLOR, RED_COLOR} from './jank_colors';
+import {JANK_COLOR} from './jank_colors';
export const JANKY_LATENCY_NAME = 'Janky EventLatency';
@@ -80,17 +75,14 @@
};
}
- // TODO(stevegolton): The janky event color should be assigned in rowToSlice.
- // For example:
-
- // rowToSlice(row: NamedSliceRow): Slice {
- // const baseSlice = super.rowToSlice(row);
- // if (baseSlice.title === JANKY_LATENCY_NAME) {
- // return {...baseSlice, baseColor: RED_COLOR};
- // } else {
- // return baseSlice;
- // }
- // }
+ rowToSlice(row: NamedRow): Slice {
+ const baseSlice = super.rowToSlice(row);
+ if (baseSlice.title === JANKY_LATENCY_NAME) {
+ return {...baseSlice, colorScheme: JANK_COLOR};
+ } else {
+ return baseSlice;
+ }
+ }
onUpdatedSlices(slices: EventLatencyTrackTypes['slice'][]) {
for (const slice of slices) {
@@ -101,16 +93,7 @@
const highlighted = globals.state.highlightedSliceId === slice.id;
const hasFocus = highlighted || isSelected;
-
- if (slice.title === JANKY_LATENCY_NAME) {
- if (hasFocus) {
- slice.baseColor = DEEP_RED_COLOR;
- } else {
- slice.baseColor = RED_COLOR;
- }
- } else {
- slice.baseColor = getColorForSlice(slice.title, hasFocus);
- }
+ slice.isHighlighted = !!hasFocus;
}
super.onUpdatedSlices(slices);
}
diff --git a/ui/src/tracks/chrome_scroll_jank/jank_colors.ts b/ui/src/tracks/chrome_scroll_jank/jank_colors.ts
index f556f7d..22d508e 100644
--- a/ui/src/tracks/chrome_scroll_jank/jank_colors.ts
+++ b/ui/src/tracks/chrome_scroll_jank/jank_colors.ts
@@ -12,16 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Color} from '../../common/colorizer';
+import {HSLColor} from '../../common/color';
+import {makeColorScheme} from '../../common/colorizer';
-export const RED_COLOR: Color = {
- h: 7,
- s: 100,
- l: 46,
-};
-
-export const DEEP_RED_COLOR: Color = {
- h: 11,
- s: 100,
- l: 32,
-};
+export const JANK_COLOR = makeColorScheme(new HSLColor([343, 100, 43]));
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
index 70f6f0a..a5bf0aa 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
@@ -12,13 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {
- getColorForSlice,
-} from '../../common/colorizer';
import {globals} from '../../frontend/globals';
-import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
+import {NamedRow, NamedSliceTrackTypes} from '../../frontend/named_slice_track';
import {NewTrackArgs, TrackBase} from '../../frontend/track';
-import {PrimaryTrackSortKey} from '../../public';
+import {PrimaryTrackSortKey, Slice} from '../../public';
import {
CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
@@ -31,7 +28,7 @@
ScrollJankPluginState,
ScrollJankTracks as DecideTracksResult,
} from './index';
-import {DEEP_RED_COLOR, RED_COLOR} from './jank_colors';
+import {JANK_COLOR} from './jank_colors';
import {ScrollJankV3DetailsPanel} from './scroll_jank_v3_details_panel';
const UNKNOWN_SLICE_NAME = 'Unknown';
@@ -87,6 +84,24 @@
ScrollJankPluginState.getInstance().unregisterTrack(ScrollJankV3Track.kind);
}
+ rowToSlice(row: NamedRow): Slice {
+ const slice = super.rowToSlice(row);
+
+ let stage = slice.title.substring(0, slice.title.indexOf(JANK_SLICE_NAME));
+ // Stage may include substage, in which case we use the substage for
+ // color selection.
+ const separator = '::';
+ if (stage.indexOf(separator) != -1) {
+ stage = stage.substring(stage.indexOf(separator) + separator.length);
+ }
+
+ if (stage == UNKNOWN_SLICE_NAME) {
+ return {...slice, colorScheme: JANK_COLOR};
+ } else {
+ return slice;
+ }
+ }
+
onUpdatedSlices(slices: EventLatencyTrackTypes['slice'][]) {
for (const slice of slices) {
const currentSelection = globals.state.currentSelection;
@@ -96,25 +111,7 @@
const highlighted = globals.state.highlightedSliceId === slice.id;
const hasFocus = highlighted || isSelected;
-
- let stage =
- slice.title.substring(0, slice.title.indexOf(JANK_SLICE_NAME));
- // Stage may include substage, in which case we use the substage for
- // color selection.
- const separator = '::';
- if (stage.indexOf(separator) != -1) {
- stage = stage.substring(stage.indexOf(separator) + separator.length);
- }
-
- if (stage == UNKNOWN_SLICE_NAME) {
- if (hasFocus) {
- slice.baseColor = DEEP_RED_COLOR;
- } else {
- slice.baseColor = RED_COLOR;
- }
- } else {
- slice.baseColor = getColorForSlice(stage, hasFocus);
- }
+ slice.isHighlighted = !!hasFocus;
}
super.onUpdatedSlices(slices);
}
diff --git a/ui/src/tracks/chrome_slices/index.ts b/ui/src/tracks/chrome_slices/index.ts
index 615771f..ccf318b 100644
--- a/ui/src/tracks/chrome_slices/index.ts
+++ b/ui/src/tracks/chrome_slices/index.ts
@@ -201,14 +201,7 @@
onUpdatedSlices(slices: ChromeSliceTrackTypes['slice'][]) {
for (const slice of slices) {
- if (slice === this.hoveredSlice) {
- slice.color = {
- ...slice.baseColor,
- l: 30,
- };
- } else {
- slice.color = slice.baseColor;
- }
+ slice.isHighlighted = (slice === this.hoveredSlice);
}
}
}
diff --git a/ui/src/tracks/cpu_freq/index.ts b/ui/src/tracks/cpu_freq/index.ts
index 83bc30b..c42b480 100644
--- a/ui/src/tracks/cpu_freq/index.ts
+++ b/ui/src/tracks/cpu_freq/index.ts
@@ -18,7 +18,7 @@
import {duration, time, Time} from '../../base/time';
import {calcCachedBucketSize} from '../../common/cache_utils';
import {drawTrackHoverTooltip} from '../../common/canvas_utils';
-import {hueForCpu} from '../../common/colorizer';
+import {colorForCpu} from '../../common/colorizer';
import {
TrackAdapter,
TrackControllerAdapter,
@@ -326,13 +326,14 @@
const yLabel = `${num} ${kUnits[unitGroup + 1]}Hz`;
// Draw the CPU frequency graph.
- const hue = hueForCpu(this.config.cpu);
+ const color = colorForCpu(this.config.cpu);
let saturation = 45;
if (globals.state.hoveredUtid !== -1) {
saturation = 0;
}
- ctx.fillStyle = `hsl(${hue}, ${saturation}%, 70%)`;
- ctx.strokeStyle = `hsl(${hue}, ${saturation}%, 55%)`;
+
+ ctx.fillStyle = color.setHSL({s: saturation, l: 70}).cssString;
+ ctx.strokeStyle = color.setHSL({s: saturation, l: 55}).cssString;
const calculateX = (timestamp: time) => {
return Math.floor(visibleTimeScale.timeToPx(timestamp));
@@ -412,8 +413,8 @@
if (this.hoveredValue !== undefined && this.hoveredTs !== undefined) {
let text = `${this.hoveredValue.toLocaleString()}kHz`;
- ctx.fillStyle = `hsl(${hue}, 45%, 75%)`;
- ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`;
+ ctx.fillStyle = color.setHSL({s: 45, l: 75}).cssString;
+ ctx.strokeStyle = color.setHSL({s: 45, l: 45}).cssString;
const xStart = Math.floor(visibleTimeScale.timeToPx(this.hoveredTs));
const xEnd = this.hoveredTsEnd === undefined ?
diff --git a/ui/src/tracks/cpu_profile/index.ts b/ui/src/tracks/cpu_profile/index.ts
index d03a842..143e057 100644
--- a/ui/src/tracks/cpu_profile/index.ts
+++ b/ui/src/tracks/cpu_profile/index.ts
@@ -16,7 +16,7 @@
import {searchSegment} from '../../base/binary_search';
import {duration, Time, time} from '../../base/time';
import {Actions} from '../../common/actions';
-import {hslForSlice} from '../../common/colorizer';
+import {colorForSample} from '../../common/colorizer';
import {
TrackAdapter,
TrackControllerAdapter,
@@ -24,7 +24,6 @@
} from '../../common/track_adapter';
import {TrackData} from '../../common/track_data';
import {globals} from '../../frontend/globals';
-import {cachedHsluvToHex} from '../../frontend/hsluv_cache';
import {TimeScale} from '../../frontend/time_scale';
import {NewTrackArgs} from '../../frontend/track';
import {
@@ -90,12 +89,6 @@
}
}
-function colorForSample(callsiteId: number, isHovered: boolean): string {
- const [hue, saturation, lightness] =
- hslForSlice(String(callsiteId), isHovered);
- return cachedHsluvToHex(hue, saturation, lightness);
-}
-
class CpuProfileTrack extends TrackAdapter<Config, Data> {
static create(args: NewTrackArgs): CpuProfileTrack {
return new CpuProfileTrack(args);
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index 45babfd..28497a2 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -24,6 +24,7 @@
drawIncompleteSlice,
drawTrackHoverTooltip,
} from '../../common/canvas_utils';
+import {Color} from '../../common/color';
import {colorForThread} from '../../common/colorizer';
import {
TrackAdapter,
@@ -285,20 +286,22 @@
const isHovering = globals.state.hoveredUtid !== -1;
const isThreadHovered = globals.state.hoveredUtid === utid;
const isProcessHovered = globals.state.hoveredPid === pid;
- const color = colorForThread(threadInfo);
+ const colorScheme = colorForThread(threadInfo);
+ let color: Color;
+ let textColor: Color;
if (isHovering && !isThreadHovered) {
if (!isProcessHovered) {
- color.l = 90;
- color.s = 0;
+ color = colorScheme.disabled;
+ textColor = colorScheme.textDisabled;
} else {
- color.l = Math.min(color.l + 30, 80);
- color.s -= 20;
+ color = colorScheme.variant;
+ textColor = colorScheme.textVariant;
}
} else {
- color.l = Math.min(color.l + 10, 60);
- color.s -= 20;
+ color = colorScheme.base;
+ textColor = colorScheme.textBase;
}
- ctx.fillStyle = `hsl(${color.h}, ${color.s}%, ${color.l}%)`;
+ ctx.fillStyle = color.cssString;
if (data.isIncomplete[i]) {
drawIncompleteSlice(ctx, rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
} else {
@@ -330,10 +333,10 @@
title = cropText(title, charWidth, visibleWidth);
subTitle = cropText(subTitle, charWidth, visibleWidth);
const rectXCenter = left + visibleWidth / 2;
- ctx.fillStyle = '#fff';
+ ctx.fillStyle = textColor.cssString;
ctx.font = '12px Roboto Condensed';
ctx.fillText(title, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 - 1);
- ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
+ ctx.fillStyle = textColor.setAlpha(0.6).cssString;
ctx.font = '10px Roboto Condensed';
ctx.fillText(subTitle, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 + 9);
}
@@ -352,7 +355,7 @@
const rectWidth = Math.max(1, rectEnd - rectStart);
// Draw a rectangle around the slice that is currently selected.
- ctx.strokeStyle = `hsl(${color.h}, ${color.s}%, 30%)`;
+ ctx.strokeStyle = color.base.setHSL({l: 30}).cssString;
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeRect(rectStart, MARGIN_TOP - 1.5, rectWidth, RECT_HEIGHT + 3);
diff --git a/ui/src/tracks/expected_frames/expected_frames_track.ts b/ui/src/tracks/expected_frames/expected_frames_track.ts
new file mode 100644
index 0000000..665cfc5
--- /dev/null
+++ b/ui/src/tracks/expected_frames/expected_frames_track.ts
@@ -0,0 +1,123 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {BigintMath as BIMath} from '../../base/bigint_math';
+import {Duration, duration, time} from '../../base/time';
+import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
+import {EngineProxy} from '../../public';
+import {
+ LONG,
+ LONG_NULL,
+ NUM,
+ STR,
+} from '../../trace_processor/query_result';
+
+export class ExpectedFramesTrack extends SliceTrackBase {
+ private maxDur = Duration.ZERO;
+
+ constructor(
+ private engine: EngineProxy, maxDepth: number, trackKey: string,
+ private trackIds: number[], namespace?: string) {
+ super(maxDepth, trackKey, '', namespace);
+ }
+
+ async onBoundsChange(start: time, end: time, resolution: duration):
+ Promise<SliceData> {
+ if (this.maxDur === Duration.ZERO) {
+ const maxDurResult = await this.engine.query(`
+ select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
+ as maxDur
+ from experimental_slice_layout
+ where filter_track_ids = '${this.trackIds.join(',')}'
+ `);
+ this.maxDur = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
+ }
+
+ const queryRes = await this.engine.query(`
+ SELECT
+ (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
+ ts,
+ max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur,
+ layout_depth as layoutDepth,
+ name,
+ id,
+ dur = 0 as isInstant,
+ dur = -1 as isIncomplete
+ from experimental_slice_layout
+ where
+ filter_track_ids = '${this.trackIds.join(',')}' and
+ ts >= ${start - this.maxDur} and
+ ts <= ${end}
+ group by tsq, layout_depth
+ order by tsq, layout_depth
+ `);
+
+ const numRows = queryRes.numRows();
+ const slices: SliceData = {
+ start,
+ end,
+ resolution,
+ length: numRows,
+ strings: [],
+ sliceIds: new Float64Array(numRows),
+ starts: new BigInt64Array(numRows),
+ ends: new BigInt64Array(numRows),
+ depths: new Uint16Array(numRows),
+ titles: new Uint16Array(numRows),
+ colors: new Uint16Array(numRows),
+ isInstant: new Uint16Array(numRows),
+ isIncomplete: new Uint16Array(numRows),
+ };
+
+ const stringIndexes = new Map<string, number>();
+ function internString(str: string) {
+ let idx = stringIndexes.get(str);
+ if (idx !== undefined) return idx;
+ idx = slices.strings.length;
+ slices.strings.push(str);
+ stringIndexes.set(str, idx);
+ return idx;
+ }
+ const greenIndex = internString('#4CAF50');
+
+ const it = queryRes.iter({
+ tsq: LONG,
+ ts: LONG,
+ dur: LONG,
+ layoutDepth: NUM,
+ id: NUM,
+ name: STR,
+ isInstant: NUM,
+ isIncomplete: NUM,
+ });
+ for (let row = 0; it.valid(); it.next(), ++row) {
+ const startQ = it.tsq;
+ const start = it.ts;
+ const dur = it.dur;
+ const end = start + dur;
+ const minEnd = startQ + resolution;
+ const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
+
+ slices.starts[row] = startQ;
+ slices.ends[row] = endQ;
+ slices.depths[row] = it.layoutDepth;
+ slices.titles[row] = internString(it.name);
+ slices.sliceIds[row] = it.id;
+ slices.isInstant[row] = it.isInstant;
+ slices.isIncomplete[row] = it.isIncomplete;
+ slices.colors![row] = greenIndex;
+ }
+ return slices;
+ }
+}
diff --git a/ui/src/tracks/expected_frames/expected_frames_track_v2.ts b/ui/src/tracks/expected_frames/expected_frames_track_v2.ts
new file mode 100644
index 0000000..268b4b6
--- /dev/null
+++ b/ui/src/tracks/expected_frames/expected_frames_track_v2.ts
@@ -0,0 +1,48 @@
+// 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 {HSLColor} from '../../common/color';
+import {makeColorScheme} from '../../common/colorizer';
+import {NamedRow, NamedSliceTrack} from '../../frontend/named_slice_track';
+import {EngineProxy, Slice} from '../../public';
+
+const GREEN = makeColorScheme(new HSLColor('#4CAF50')); // Green 500
+
+export class ExpectedFramesTrack extends NamedSliceTrack {
+ constructor(
+ engine: EngineProxy, maxDepth: number, trackKey: string,
+ private trackIds: number[]) {
+ super({engine, trackKey});
+ this.sliceLayout.maxDepth = maxDepth + 1;
+ }
+
+ getSqlSource(): string {
+ return `
+ SELECT
+ ts,
+ dur,
+ layout_depth as depth,
+ name,
+ id
+ from experimental_slice_layout
+ where
+ filter_track_ids = '${this.trackIds.join(',')}'
+ `;
+ }
+
+ rowToSlice(row: NamedRow): Slice {
+ const baseSlice = super.rowToSlice(row);
+ return {...baseSlice, colorScheme: GREEN};
+ }
+}
diff --git a/ui/src/tracks/expected_frames/index.ts b/ui/src/tracks/expected_frames/index.ts
index 8cc1833..10b3f44 100644
--- a/ui/src/tracks/expected_frames/index.ts
+++ b/ui/src/tracks/expected_frames/index.ts
@@ -12,14 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {BigintMath as BIMath} from '../../base/bigint_math';
-import {Duration, duration, time} from '../../base/time';
+// import { NamedSliceTrack } from 'src/frontend/named_slice_track';
import {
- SliceData,
- SliceTrackBase,
-} from '../../frontend/slice_track_base';
-import {
- EngineProxy,
Plugin,
PluginContext,
PluginContextTrace,
@@ -27,115 +21,19 @@
} from '../../public';
import {getTrackName} from '../../public/utils';
import {
- LONG,
- LONG_NULL,
NUM,
NUM_NULL,
STR,
STR_NULL,
} from '../../trace_processor/query_result';
+import {ExpectedFramesTrack} from './expected_frames_track';
+import {
+ ExpectedFramesTrack as ExpectedFramesTrackV2,
+} from './expected_frames_track_v2';
+
export const EXPECTED_FRAMES_SLICE_TRACK_KIND = 'ExpectedFramesSliceTrack';
-class SliceTrack extends SliceTrackBase {
- private maxDur = Duration.ZERO;
-
- constructor(
- private engine: EngineProxy, maxDepth: number, trackKey: string,
- private trackIds: number[], namespace?: string) {
- super(maxDepth, trackKey, '', namespace);
- }
-
- async onBoundsChange(start: time, end: time, resolution: duration):
- Promise<SliceData> {
- if (this.maxDur === Duration.ZERO) {
- const maxDurResult = await this.engine.query(`
- select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
- as maxDur
- from experimental_slice_layout
- where filter_track_ids = '${this.trackIds.join(',')}'
- `);
- this.maxDur = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
- }
-
- const queryRes = await this.engine.query(`
- SELECT
- (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
- ts,
- max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur,
- layout_depth as layoutDepth,
- name,
- id,
- dur = 0 as isInstant,
- dur = -1 as isIncomplete
- from experimental_slice_layout
- where
- filter_track_ids = '${this.trackIds.join(',')}' and
- ts >= ${start - this.maxDur} and
- ts <= ${end}
- group by tsq, layout_depth
- order by tsq, layout_depth
- `);
-
- const numRows = queryRes.numRows();
- const slices: SliceData = {
- start,
- end,
- resolution,
- length: numRows,
- strings: [],
- sliceIds: new Float64Array(numRows),
- starts: new BigInt64Array(numRows),
- ends: new BigInt64Array(numRows),
- depths: new Uint16Array(numRows),
- titles: new Uint16Array(numRows),
- colors: new Uint16Array(numRows),
- isInstant: new Uint16Array(numRows),
- isIncomplete: new Uint16Array(numRows),
- };
-
- const stringIndexes = new Map<string, number>();
- function internString(str: string) {
- let idx = stringIndexes.get(str);
- if (idx !== undefined) return idx;
- idx = slices.strings.length;
- slices.strings.push(str);
- stringIndexes.set(str, idx);
- return idx;
- }
- const greenIndex = internString('#4CAF50');
-
- const it = queryRes.iter({
- tsq: LONG,
- ts: LONG,
- dur: LONG,
- layoutDepth: NUM,
- id: NUM,
- name: STR,
- isInstant: NUM,
- isIncomplete: NUM,
- });
- for (let row = 0; it.valid(); it.next(), ++row) {
- const startQ = it.tsq;
- const start = it.ts;
- const dur = it.dur;
- const end = start + dur;
- const minEnd = startQ + resolution;
- const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
-
- slices.starts[row] = startQ;
- slices.ends[row] = endQ;
- slices.depths[row] = it.layoutDepth;
- slices.titles[row] = internString(it.name);
- slices.sliceIds[row] = it.id;
- slices.isInstant[row] = it.isInstant;
- slices.isIncomplete[row] = it.isIncomplete;
- slices.colors![row] = greenIndex;
- }
- return slices;
- }
-}
-
class ExpectedFramesPlugin implements Plugin {
onActivate(_ctx: PluginContext): void {}
@@ -195,7 +93,22 @@
trackIds,
kind: EXPECTED_FRAMES_SLICE_TRACK_KIND,
track: ({trackKey}) => {
- return new SliceTrack(
+ return new ExpectedFramesTrack(
+ engine,
+ maxDepth,
+ trackKey,
+ trackIds,
+ );
+ },
+ });
+
+ ctx.registerStaticTrack({
+ uri: `perfetto.ExpectedFrames#${upid}.v2`,
+ displayName,
+ trackIds,
+ kind: EXPECTED_FRAMES_SLICE_TRACK_KIND,
+ track: ({trackKey}) => {
+ return new ExpectedFramesTrackV2(
engine,
maxDepth,
trackKey,
diff --git a/ui/src/tracks/ftrace/index.ts b/ui/src/tracks/ftrace/index.ts
index 5dd646c..6a2a966 100644
--- a/ui/src/tracks/ftrace/index.ts
+++ b/ui/src/tracks/ftrace/index.ts
@@ -14,7 +14,7 @@
import {duration, Time, time} from '../../base/time';
import {BasicAsyncTrack} from '../../common/basic_async_track';
-import {colorForString} from '../../common/colorizer';
+import {colorForFtrace} from '../../common/colorizer';
import {LIMIT, TrackData} from '../../common/track_data';
import {checkerboardExcept} from '../../frontend/checkerboard';
import {globals} from '../../frontend/globals';
@@ -116,13 +116,7 @@
for (let i = 0; i < data.timestamps.length; i++) {
const name = data.names[i];
- const color = colorForString(name);
- const hsl = `hsl(
- ${color.h},
- ${color.s - 20}%,
- ${Math.min(color.l + 10, 60)}%
- )`;
- ctx.fillStyle = hsl;
+ ctx.fillStyle = colorForFtrace(name).base.cssString;
const timestamp = Time.fromRaw(data.timestamps[i]);
const xPos = Math.floor(visibleTimeScale.timeToPx(timestamp));
diff --git a/ui/src/tracks/process_summary/process_scheduling_track.ts b/ui/src/tracks/process_summary/process_scheduling_track.ts
index 0d65539..f7ad4c1 100644
--- a/ui/src/tracks/process_summary/process_scheduling_track.ts
+++ b/ui/src/tracks/process_summary/process_scheduling_track.ts
@@ -19,6 +19,7 @@
import {Actions} from '../../common/actions';
import {calcCachedBucketSize} from '../../common/cache_utils';
import {drawTrackHoverTooltip} from '../../common/canvas_utils';
+import {Color} from '../../common/color';
import {colorForThread} from '../../common/colorizer';
import {
TrackAdapter,
@@ -250,20 +251,18 @@
const isHovering = globals.state.hoveredUtid !== -1;
const isThreadHovered = globals.state.hoveredUtid === utid;
const isProcessHovered = globals.state.hoveredPid === pid;
- const color = colorForThread(threadInfo);
+ const colorScheme = colorForThread(threadInfo);
+ let color: Color;
if (isHovering && !isThreadHovered) {
if (!isProcessHovered) {
- color.l = 90;
- color.s = 0;
+ color = colorScheme.disabled;
} else {
- color.l = Math.min(color.l + 30, 80);
- color.s -= 20;
+ color = colorScheme.variant;
}
} else {
- color.l = Math.min(color.l + 10, 60);
- color.s -= 20;
+ color = colorScheme.base;
}
- ctx.fillStyle = `hsl(${color.h}, ${color.s}%, ${color.l}%)`;
+ ctx.fillStyle = color.cssString;
const y = MARGIN_TOP + cpuTrackHeight * cpu + cpu;
ctx.fillRect(rectStart, y, rectEnd - rectStart, cpuTrackHeight);
}
diff --git a/ui/src/tracks/process_summary/process_summary_track.ts b/ui/src/tracks/process_summary/process_summary_track.ts
index 0b29eea..a1b32c7 100644
--- a/ui/src/tracks/process_summary/process_summary_track.ts
+++ b/ui/src/tracks/process_summary/process_summary_track.ts
@@ -184,10 +184,7 @@
// TODO(hjd): Dedupe this math.
const color = colorForTid(this.config.pidForColor);
- color.l = Math.min(color.l + 10, 60);
- color.s -= 20;
-
- ctx.fillStyle = `hsl(${color.h}, ${color.s}%, ${color.l}%)`;
+ ctx.fillStyle = color.base.cssString;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
for (let i = 0; i < data.utilizations.length; i++) {
diff --git a/ui/src/tracks/thread_state/index.ts b/ui/src/tracks/thread_state/index.ts
index 2293f98..457ef28 100644
--- a/ui/src/tracks/thread_state/index.ts
+++ b/ui/src/tracks/thread_state/index.ts
@@ -246,21 +246,15 @@
currentSelection.kind === 'THREAD_STATE' &&
currentSelection.id === data.ids[i];
- const color = colorForState(state);
-
- let colorStr = `hsl(${color.h},${color.s}%,${color.l}%)`;
- if (color.a) {
- colorStr = `hsla(${color.h},${color.s}%,${color.l}%, ${color.a})`;
- }
- ctx.fillStyle = colorStr;
-
+ const colorScheme = colorForState(state);
+ ctx.fillStyle = colorScheme.base.cssString;
ctx.fillRect(rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
// Don't render text when we have less than 10px to play with.
if (rectWidth < 10 || state === 'Sleeping') continue;
const title = cropText(state, charWidth, rectWidth);
const rectXCenter = rectStart + rectWidth / 2;
- ctx.fillStyle = color.l > 80 ? '#404040' : '#fff';
+ ctx.fillStyle = colorScheme.textBase.cssString;
ctx.fillText(title, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 + 3);
if (isSelected) {
@@ -269,8 +263,7 @@
Math.max(0 - EXCESS_WIDTH, timeScale.timeToPx(tStart));
const rectEnd =
Math.min(windowSpan.end + EXCESS_WIDTH, timeScale.timeToPx(tEnd));
- const color = colorForState(state);
- ctx.strokeStyle = `hsl(${color.h},${color.s}%,${color.l * 0.7}%)`;
+ ctx.strokeStyle = colorScheme.base.cssString;
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeRect(
diff --git a/ui/src/tracks/thread_state/thread_state_track_v2.ts b/ui/src/tracks/thread_state/thread_state_track_v2.ts
deleted file mode 100644
index f6216a6..0000000
--- a/ui/src/tracks/thread_state/thread_state_track_v2.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {Actions} from '../../common/actions';
-import {Color, colorForState} from '../../common/colorizer';
-import {Selection} from '../../common/state';
-import {translateState} from '../../common/thread_state';
-import {
- BASE_ROW,
- BaseSliceTrack,
- BaseSliceTrackTypes,
- OnSliceClickArgs,
-} from '../../frontend/base_slice_track';
-import {globals} from '../../frontend/globals';
-import {
- SLICE_LAYOUT_FLAT_DEFAULTS,
- SliceLayout,
-} from '../../frontend/slice_layout';
-import {NewTrackArgs} from '../../frontend/track';
-import {NUM_NULL, STR} from '../../trace_processor/query_result';
-
-export const THREAD_STATE_ROW = {
- ...BASE_ROW,
- state: STR,
- ioWait: NUM_NULL,
-};
-
-export type ThreadStateRow = typeof THREAD_STATE_ROW;
-
-export interface ThreadStateTrackTypes extends BaseSliceTrackTypes {
- row: ThreadStateRow;
-}
-
-export class ThreadStateTrack extends BaseSliceTrack<ThreadStateTrackTypes> {
- protected sliceLayout: SliceLayout = {...SLICE_LAYOUT_FLAT_DEFAULTS};
-
- constructor(args: NewTrackArgs, private utid: number) {
- super(args);
- }
-
- // This is used by the base class to call iter().
- getRowSpec(): ThreadStateTrackTypes['row'] {
- return THREAD_STATE_ROW;
- }
-
- getSqlSource(): string {
- // Do not display states 'x' and 'S' (dead & sleeping).
- const sql = `
- select
- id,
- ts,
- dur,
- cpu,
- state,
- io_wait as ioWait,
- 0 as depth
- from thread_state
- where
- utid = ${this.utid} and
- state != 'x' and
- state != 'S'
- `;
- return sql;
- }
-
- rowToSlice(row: ThreadStateTrackTypes['row']):
- ThreadStateTrackTypes['slice'] {
- const baseSlice = super.rowToSlice(row);
- const ioWait = row.ioWait === null ? undefined : !!row.ioWait;
- const title = translateState(row.state, ioWait);
- const baseColor: Color = colorForState(title);
- return {...baseSlice, title, baseColor};
- }
-
- onUpdatedSlices(slices: ThreadStateTrackTypes['slice'][]) {
- for (const slice of slices) {
- if (slice === this.hoveredSlice) {
- slice.color = {
- h: slice.baseColor.h,
- s: slice.baseColor.s,
- l: 30,
- };
- } else {
- slice.color = slice.baseColor;
- }
- }
- }
-
- onSliceClick(args: OnSliceClickArgs<ThreadStateTrackTypes['slice']>) {
- globals.makeSelection(Actions.selectThreadState({
- id: args.slice.id,
- trackKey: this.trackKey,
- }));
- }
-
- protected isSelectionHandled(selection: Selection): boolean {
- return selection.kind === 'THREAD_STATE';
- }
-}
diff --git a/ui/src/tracks/thread_state/thread_state_v2.ts b/ui/src/tracks/thread_state/thread_state_v2.ts
index 2481dc9..c35ed5c 100644
--- a/ui/src/tracks/thread_state/thread_state_v2.ts
+++ b/ui/src/tracks/thread_state/thread_state_v2.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Actions} from '../../common/actions';
-import {Color, colorForState} from '../../common/colorizer';
+import {colorForState} from '../../common/colorizer';
import {Selection} from '../../common/state';
import {translateState} from '../../common/thread_state';
import {
@@ -35,19 +35,13 @@
state: STR,
ioWait: NUM_NULL,
};
+
export type ThreadStateRow = typeof THREAD_STATE_ROW;
-
-export interface ThreadStateTrackConfig {
- utid: number;
-}
-
export interface ThreadStateTrackTypes extends BaseSliceTrackTypes {
row: ThreadStateRow;
}
-export const THREAD_STATE_TRACK_V2_KIND = 'ThreadStateTrackV2';
-
export class ThreadStateTrack extends BaseSliceTrack<ThreadStateTrackTypes> {
protected sliceLayout: SliceLayout = {...SLICE_LAYOUT_FLAT_DEFAULTS};
@@ -62,11 +56,15 @@
getSqlSource(): string {
// Do not display states 'x' and 'S' (dead & sleeping).
+ // Note: Thread state tracks V1 basically ignores incomplete slices, faking
+ // their duration as 1 instead. Let's just do this here as well for now to
+ // achieve feature parity with tracks V1 and tackle the issue of overlapping
+ // incomplete slices later.
return `
select
id,
ts,
- dur,
+ max(dur, 1) as dur,
cpu,
state,
io_wait as ioWait,
@@ -84,21 +82,13 @@
const baseSlice = super.rowToSlice(row);
const ioWait = row.ioWait === null ? undefined : !!row.ioWait;
const title = translateState(row.state, ioWait);
- const baseColor: Color = colorForState(title);
- return {...baseSlice, title, baseColor};
+ const color = colorForState(title);
+ return {...baseSlice, title, colorScheme: color};
}
onUpdatedSlices(slices: ThreadStateTrackTypes['slice'][]) {
for (const slice of slices) {
- if (slice === this.hoveredSlice) {
- slice.color = {
- h: slice.baseColor.h,
- s: slice.baseColor.s,
- l: 30,
- };
- } else {
- slice.color = slice.baseColor;
- }
+ slice.isHighlighted = (slice === this.hoveredSlice);
}
}