Merge "Add --stdlib-sources argument to check_sql_modules"
diff --git a/include/perfetto/public/protos/BUILD.gn b/include/perfetto/public/protos/BUILD.gn
index 5432dd5..5901a27 100644
--- a/include/perfetto/public/protos/BUILD.gn
+++ b/include/perfetto/public/protos/BUILD.gn
@@ -17,8 +17,13 @@
"common/builtin_clock.pzc.h",
"config/data_source_config.pzc.h",
"config/trace_config.pzc.h",
+ "trace/interned_data/interned_data.pzc.h",
"trace/test_event.pzc.h",
"trace/trace.pzc.h",
"trace/trace_packet.pzc.h",
+ "trace/track_event/counter_descriptor.pzc.h",
+ "trace/track_event/debug_annotation.pzc.h",
+ "trace/track_event/track_descriptor.pzc.h",
+ "trace/track_event/track_event.pzc.h",
]
}
diff --git a/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h b/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h
new file mode 100644
index 0000000..b5147a5
--- /dev/null
+++ b/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h
@@ -0,0 +1,148 @@
+/*
+ * 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 INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_INTERNED_DATA_INTERNED_DATA_PZC_H_
+#define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_INTERNED_DATA_INTERNED_DATA_PZC_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "perfetto/public/pb_macros.h"
+
+PERFETTO_PB_MSG_DECL(perfetto_protos_Callstack);
+PERFETTO_PB_MSG_DECL(perfetto_protos_DebugAnnotationName);
+PERFETTO_PB_MSG_DECL(perfetto_protos_DebugAnnotationValueTypeName);
+PERFETTO_PB_MSG_DECL(perfetto_protos_EventCategory);
+PERFETTO_PB_MSG_DECL(perfetto_protos_EventName);
+PERFETTO_PB_MSG_DECL(perfetto_protos_Frame);
+PERFETTO_PB_MSG_DECL(perfetto_protos_HistogramName);
+PERFETTO_PB_MSG_DECL(perfetto_protos_InternedGpuRenderStageSpecification);
+PERFETTO_PB_MSG_DECL(perfetto_protos_InternedGraphicsContext);
+PERFETTO_PB_MSG_DECL(perfetto_protos_InternedString);
+PERFETTO_PB_MSG_DECL(perfetto_protos_LogMessageBody);
+PERFETTO_PB_MSG_DECL(perfetto_protos_Mapping);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ProfiledFrameSymbols);
+PERFETTO_PB_MSG_DECL(perfetto_protos_SourceLocation);
+PERFETTO_PB_MSG_DECL(perfetto_protos_UnsymbolizedSourceLocation);
+
+PERFETTO_PB_MSG(perfetto_protos_InternedData);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_EventCategory,
+ event_categories,
+ 1);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_EventName,
+ event_names,
+ 2);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_DebugAnnotationName,
+ debug_annotation_names,
+ 3);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_DebugAnnotationValueTypeName,
+ debug_annotation_value_type_names,
+ 27);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_SourceLocation,
+ source_locations,
+ 4);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_UnsymbolizedSourceLocation,
+ unsymbolized_source_locations,
+ 28);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_LogMessageBody,
+ log_message_body,
+ 20);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_HistogramName,
+ histogram_names,
+ 25);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedString,
+ build_ids,
+ 16);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedString,
+ mapping_paths,
+ 17);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedString,
+ source_paths,
+ 18);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedString,
+ function_names,
+ 5);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_ProfiledFrameSymbols,
+ profiled_frame_symbols,
+ 21);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_Mapping,
+ mappings,
+ 19);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_Frame,
+ frames,
+ 6);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_Callstack,
+ callstacks,
+ 7);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedString,
+ vulkan_memory_keys,
+ 22);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedGraphicsContext,
+ graphics_contexts,
+ 23);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedGpuRenderStageSpecification,
+ gpu_specifications,
+ 24);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedString,
+ kernel_symbols,
+ 26);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedString,
+ debug_annotation_string_values,
+ 29);
+
+#endif // INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_INTERNED_DATA_INTERNED_DATA_PZC_H_
diff --git a/include/perfetto/public/protos/trace/track_event/counter_descriptor.pzc.h b/include/perfetto/public/protos/trace/track_event/counter_descriptor.pzc.h
new file mode 100644
index 0000000..a5d075a
--- /dev/null
+++ b/include/perfetto/public/protos/trace/track_event/counter_descriptor.pzc.h
@@ -0,0 +1,77 @@
+/*
+ * 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 INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_COUNTER_DESCRIPTOR_PZC_H_
+#define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_COUNTER_DESCRIPTOR_PZC_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "perfetto/public/pb_macros.h"
+
+PERFETTO_PB_ENUM_IN_MSG(perfetto_protos_CounterDescriptor, BuiltinCounterType){
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_CounterDescriptor,
+ COUNTER_UNSPECIFIED) = 0,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_CounterDescriptor,
+ COUNTER_THREAD_TIME_NS) = 1,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_CounterDescriptor,
+ COUNTER_THREAD_INSTRUCTION_COUNT) = 2,
+};
+
+PERFETTO_PB_ENUM_IN_MSG(perfetto_protos_CounterDescriptor, Unit){
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_CounterDescriptor,
+ UNIT_UNSPECIFIED) = 0,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_CounterDescriptor,
+ UNIT_TIME_NS) = 1,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_CounterDescriptor,
+ UNIT_COUNT) = 2,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_CounterDescriptor,
+ UNIT_SIZE_BYTES) = 3,
+};
+
+PERFETTO_PB_MSG(perfetto_protos_CounterDescriptor);
+PERFETTO_PB_FIELD(perfetto_protos_CounterDescriptor,
+ VARINT,
+ enum perfetto_protos_CounterDescriptor_BuiltinCounterType,
+ type,
+ 1);
+PERFETTO_PB_FIELD(perfetto_protos_CounterDescriptor,
+ STRING,
+ const char*,
+ categories,
+ 2);
+PERFETTO_PB_FIELD(perfetto_protos_CounterDescriptor,
+ VARINT,
+ enum perfetto_protos_CounterDescriptor_Unit,
+ unit,
+ 3);
+PERFETTO_PB_FIELD(perfetto_protos_CounterDescriptor,
+ STRING,
+ const char*,
+ unit_name,
+ 6);
+PERFETTO_PB_FIELD(perfetto_protos_CounterDescriptor,
+ VARINT,
+ int64_t,
+ unit_multiplier,
+ 4);
+PERFETTO_PB_FIELD(perfetto_protos_CounterDescriptor,
+ VARINT,
+ bool,
+ is_incremental,
+ 5);
+
+#endif // INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_COUNTER_DESCRIPTOR_PZC_H_
diff --git a/include/perfetto/public/protos/trace/track_event/debug_annotation.pzc.h b/include/perfetto/public/protos/trace/track_event/debug_annotation.pzc.h
new file mode 100644
index 0000000..d4152ae
--- /dev/null
+++ b/include/perfetto/public/protos/trace/track_event/debug_annotation.pzc.h
@@ -0,0 +1,182 @@
+/*
+ * 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 INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_DEBUG_ANNOTATION_PZC_H_
+#define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_DEBUG_ANNOTATION_PZC_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "perfetto/public/pb_macros.h"
+
+PERFETTO_PB_MSG_DECL(perfetto_protos_DebugAnnotation);
+PERFETTO_PB_MSG_DECL(perfetto_protos_DebugAnnotation_NestedValue);
+
+PERFETTO_PB_ENUM_IN_MSG(perfetto_protos_DebugAnnotation_NestedValue,
+ NestedType){
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_DebugAnnotation_NestedValue,
+ UNSPECIFIED) = 0,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_DebugAnnotation_NestedValue,
+ DICT) = 1,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_DebugAnnotation_NestedValue,
+ ARRAY) = 2,
+};
+
+PERFETTO_PB_MSG(perfetto_protos_DebugAnnotationValueTypeName);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotationValueTypeName,
+ VARINT,
+ uint64_t,
+ iid,
+ 1);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotationValueTypeName,
+ STRING,
+ const char*,
+ name,
+ 2);
+
+PERFETTO_PB_MSG(perfetto_protos_DebugAnnotationName);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotationName,
+ VARINT,
+ uint64_t,
+ iid,
+ 1);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotationName,
+ STRING,
+ const char*,
+ name,
+ 2);
+
+PERFETTO_PB_MSG(perfetto_protos_DebugAnnotation);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ VARINT,
+ uint64_t,
+ name_iid,
+ 1);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ STRING,
+ const char*,
+ name,
+ 10);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation, VARINT, bool, bool_value, 2);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ VARINT,
+ uint64_t,
+ uint_value,
+ 3);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ VARINT,
+ int64_t,
+ int_value,
+ 4);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ FIXED64,
+ double,
+ double_value,
+ 5);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ VARINT,
+ uint64_t,
+ pointer_value,
+ 7);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ MSG,
+ perfetto_protos_DebugAnnotation_NestedValue,
+ nested_value,
+ 8);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ STRING,
+ const char*,
+ legacy_json_value,
+ 9);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ STRING,
+ const char*,
+ string_value,
+ 6);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ VARINT,
+ uint64_t,
+ string_value_iid,
+ 17);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ STRING,
+ const char*,
+ proto_type_name,
+ 16);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ VARINT,
+ uint64_t,
+ proto_type_name_iid,
+ 13);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ STRING,
+ const char*,
+ proto_value,
+ 14);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ MSG,
+ perfetto_protos_DebugAnnotation,
+ dict_entries,
+ 11);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation,
+ MSG,
+ perfetto_protos_DebugAnnotation,
+ array_values,
+ 12);
+
+PERFETTO_PB_MSG(perfetto_protos_DebugAnnotation_NestedValue);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation_NestedValue,
+ VARINT,
+ enum perfetto_protos_DebugAnnotation_NestedValue_NestedType,
+ nested_type,
+ 1);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation_NestedValue,
+ STRING,
+ const char*,
+ dict_keys,
+ 2);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation_NestedValue,
+ MSG,
+ perfetto_protos_DebugAnnotation_NestedValue,
+ dict_values,
+ 3);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation_NestedValue,
+ MSG,
+ perfetto_protos_DebugAnnotation_NestedValue,
+ array_values,
+ 4);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation_NestedValue,
+ VARINT,
+ int64_t,
+ int_value,
+ 5);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation_NestedValue,
+ FIXED64,
+ double,
+ double_value,
+ 6);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation_NestedValue,
+ VARINT,
+ bool,
+ bool_value,
+ 7);
+PERFETTO_PB_FIELD(perfetto_protos_DebugAnnotation_NestedValue,
+ STRING,
+ const char*,
+ string_value,
+ 8);
+
+#endif // INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_DEBUG_ANNOTATION_PZC_H_
diff --git a/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h b/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h
new file mode 100644
index 0000000..0069082
--- /dev/null
+++ b/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h
@@ -0,0 +1,69 @@
+/*
+ * 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 INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_TRACK_DESCRIPTOR_PZC_H_
+#define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_TRACK_DESCRIPTOR_PZC_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "perfetto/public/pb_macros.h"
+
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeProcessDescriptor);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeThreadDescriptor);
+PERFETTO_PB_MSG_DECL(perfetto_protos_CounterDescriptor);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ProcessDescriptor);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ThreadDescriptor);
+
+PERFETTO_PB_MSG(perfetto_protos_TrackDescriptor);
+PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor, VARINT, uint64_t, uuid, 1);
+PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor,
+ VARINT,
+ uint64_t,
+ parent_uuid,
+ 5);
+PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor,
+ STRING,
+ const char*,
+ name,
+ 2);
+PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor,
+ MSG,
+ perfetto_protos_ProcessDescriptor,
+ process,
+ 3);
+PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor,
+ MSG,
+ perfetto_protos_ChromeProcessDescriptor,
+ chrome_process,
+ 6);
+PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor,
+ MSG,
+ perfetto_protos_ThreadDescriptor,
+ thread,
+ 4);
+PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor,
+ MSG,
+ perfetto_protos_ChromeThreadDescriptor,
+ chrome_thread,
+ 7);
+PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor,
+ MSG,
+ perfetto_protos_CounterDescriptor,
+ counter,
+ 8);
+
+#endif // INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_TRACK_DESCRIPTOR_PZC_H_
diff --git a/include/perfetto/public/protos/trace/track_event/track_event.pzc.h b/include/perfetto/public/protos/trace/track_event/track_event.pzc.h
new file mode 100644
index 0000000..5811154
--- /dev/null
+++ b/include/perfetto/public/protos/trace/track_event/track_event.pzc.h
@@ -0,0 +1,382 @@
+/*
+ * 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 INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_TRACK_EVENT_PZC_H_
+#define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_TRACK_EVENT_PZC_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "perfetto/public/pb_macros.h"
+
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeActiveProcesses);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeApplicationStateInfo);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeCompositorSchedulerState);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeContentSettingsEventInfo);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeFrameReporter);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeHistogramSample);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeKeyedService);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeLatencyInfo);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeLegacyIpc);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeMessagePump);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeMojoEventInfo);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeRendererSchedulerState);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeUserEvent);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeWindowHandleEventInfo);
+PERFETTO_PB_MSG_DECL(perfetto_protos_DebugAnnotation);
+PERFETTO_PB_MSG_DECL(perfetto_protos_LogMessage);
+PERFETTO_PB_MSG_DECL(perfetto_protos_SourceLocation);
+PERFETTO_PB_MSG_DECL(perfetto_protos_TaskExecution);
+PERFETTO_PB_MSG_DECL(perfetto_protos_TrackEvent_LegacyEvent);
+
+PERFETTO_PB_ENUM_IN_MSG(perfetto_protos_TrackEvent, Type){
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TrackEvent,
+ TYPE_UNSPECIFIED) = 0,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TrackEvent,
+ TYPE_SLICE_BEGIN) = 1,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TrackEvent,
+ TYPE_SLICE_END) = 2,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TrackEvent, TYPE_INSTANT) = 3,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TrackEvent, TYPE_COUNTER) = 4,
+};
+
+PERFETTO_PB_ENUM_IN_MSG(perfetto_protos_TrackEvent_LegacyEvent, FlowDirection){
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TrackEvent_LegacyEvent,
+ FLOW_UNSPECIFIED) = 0,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TrackEvent_LegacyEvent,
+ FLOW_IN) = 1,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TrackEvent_LegacyEvent,
+ FLOW_OUT) = 2,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TrackEvent_LegacyEvent,
+ FLOW_INOUT) = 3,
+};
+
+PERFETTO_PB_ENUM_IN_MSG(perfetto_protos_TrackEvent_LegacyEvent,
+ InstantEventScope){
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TrackEvent_LegacyEvent,
+ SCOPE_UNSPECIFIED) = 0,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TrackEvent_LegacyEvent,
+ SCOPE_GLOBAL) = 1,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TrackEvent_LegacyEvent,
+ SCOPE_PROCESS) = 2,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TrackEvent_LegacyEvent,
+ SCOPE_THREAD) = 3,
+};
+
+PERFETTO_PB_MSG(perfetto_protos_EventName);
+PERFETTO_PB_FIELD(perfetto_protos_EventName, VARINT, uint64_t, iid, 1);
+PERFETTO_PB_FIELD(perfetto_protos_EventName, STRING, const char*, name, 2);
+
+PERFETTO_PB_MSG(perfetto_protos_EventCategory);
+PERFETTO_PB_FIELD(perfetto_protos_EventCategory, VARINT, uint64_t, iid, 1);
+PERFETTO_PB_FIELD(perfetto_protos_EventCategory, STRING, const char*, name, 2);
+
+PERFETTO_PB_MSG(perfetto_protos_TrackEventDefaults);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEventDefaults,
+ VARINT,
+ uint64_t,
+ track_uuid,
+ 11);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEventDefaults,
+ VARINT,
+ uint64_t,
+ extra_counter_track_uuids,
+ 31);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEventDefaults,
+ VARINT,
+ uint64_t,
+ extra_double_counter_track_uuids,
+ 45);
+
+PERFETTO_PB_MSG(perfetto_protos_TrackEvent);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ uint64_t,
+ category_iids,
+ 3);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ STRING,
+ const char*,
+ categories,
+ 22);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent, VARINT, uint64_t, name_iid, 10);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent, STRING, const char*, name, 23);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ enum perfetto_protos_TrackEvent_Type,
+ type,
+ 9);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent, VARINT, uint64_t, track_uuid, 11);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ int64_t,
+ counter_value,
+ 30);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ FIXED64,
+ double,
+ double_counter_value,
+ 44);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ uint64_t,
+ extra_counter_track_uuids,
+ 31);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ int64_t,
+ extra_counter_values,
+ 12);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ uint64_t,
+ extra_double_counter_track_uuids,
+ 45);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ FIXED64,
+ double,
+ extra_double_counter_values,
+ 46);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ uint64_t,
+ flow_ids_old,
+ 36);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent, FIXED64, uint64_t, flow_ids, 47);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ uint64_t,
+ terminating_flow_ids_old,
+ 42);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ FIXED64,
+ uint64_t,
+ terminating_flow_ids,
+ 48);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_DebugAnnotation,
+ debug_annotations,
+ 4);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_TaskExecution,
+ task_execution,
+ 5);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_LogMessage,
+ log_message,
+ 21);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeCompositorSchedulerState,
+ cc_scheduler_state,
+ 24);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeUserEvent,
+ chrome_user_event,
+ 25);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeKeyedService,
+ chrome_keyed_service,
+ 26);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeLegacyIpc,
+ chrome_legacy_ipc,
+ 27);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeHistogramSample,
+ chrome_histogram_sample,
+ 28);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeLatencyInfo,
+ chrome_latency_info,
+ 29);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeFrameReporter,
+ chrome_frame_reporter,
+ 32);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeApplicationStateInfo,
+ chrome_application_state_info,
+ 39);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeRendererSchedulerState,
+ chrome_renderer_scheduler_state,
+ 40);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeWindowHandleEventInfo,
+ chrome_window_handle_event_info,
+ 41);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeContentSettingsEventInfo,
+ chrome_content_settings_event_info,
+ 43);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeActiveProcesses,
+ chrome_active_processes,
+ 49);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_SourceLocation,
+ source_location,
+ 33);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ uint64_t,
+ source_location_iid,
+ 34);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeMessagePump,
+ chrome_message_pump,
+ 35);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_ChromeMojoEventInfo,
+ chrome_mojo_event_info,
+ 38);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ int64_t,
+ timestamp_delta_us,
+ 1);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ int64_t,
+ timestamp_absolute_us,
+ 16);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ int64_t,
+ thread_time_delta_us,
+ 2);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ int64_t,
+ thread_time_absolute_us,
+ 17);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ int64_t,
+ thread_instruction_count_delta,
+ 8);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ VARINT,
+ int64_t,
+ thread_instruction_count_absolute,
+ 20);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
+ perfetto_protos_TrackEvent_LegacyEvent,
+ legacy_event,
+ 6);
+
+PERFETTO_PB_MSG(perfetto_protos_TrackEvent_LegacyEvent);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ uint64_t,
+ name_iid,
+ 1);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ int32_t,
+ phase,
+ 2);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ int64_t,
+ duration_us,
+ 3);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ int64_t,
+ thread_duration_us,
+ 4);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ int64_t,
+ thread_instruction_delta,
+ 15);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ uint64_t,
+ unscoped_id,
+ 6);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ uint64_t,
+ local_id,
+ 10);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ uint64_t,
+ global_id,
+ 11);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ STRING,
+ const char*,
+ id_scope,
+ 7);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ bool,
+ use_async_tts,
+ 9);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ uint64_t,
+ bind_id,
+ 8);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ bool,
+ bind_to_enclosing,
+ 12);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ enum perfetto_protos_TrackEvent_LegacyEvent_FlowDirection,
+ flow_direction,
+ 13);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ enum perfetto_protos_TrackEvent_LegacyEvent_InstantEventScope,
+ instant_event_scope,
+ 14);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ int32_t,
+ pid_override,
+ 18);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent_LegacyEvent,
+ VARINT,
+ int32_t,
+ tid_override,
+ 19);
+
+#endif // INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACK_EVENT_TRACK_EVENT_PZC_H_
diff --git a/protos/perfetto/trace/ftrace/scm.proto b/protos/perfetto/trace/ftrace/scm.proto
index 0613ce0..cc4e122 100644
--- a/protos/perfetto/trace/ftrace/scm.proto
+++ b/protos/perfetto/trace/ftrace/scm.proto
@@ -10,5 +10,4 @@
optional uint64 x0 = 2;
optional uint64 x5 = 3;
}
-message ScmCallEndFtraceEvent {
-}
+message ScmCallEndFtraceEvent {}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 8422621..b93244d 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -7269,8 +7269,7 @@
optional uint64 x0 = 2;
optional uint64 x5 = 3;
}
-message ScmCallEndFtraceEvent {
-}
+message ScmCallEndFtraceEvent {}
// End of protos/perfetto/trace/ftrace/scm.proto
diff --git a/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc b/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
index 712e3d1..cf9654c 100644
--- a/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
+++ b/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
@@ -223,9 +223,10 @@
s += std::string("// ") + __FILE__ + "\n";
s += "// Do not edit.\n";
s += R"(
-#include "perfetto/protozero/proto_utils.h"
#include "src/traced/probes/ftrace/event_info.h"
+#include "perfetto/protozero/proto_utils.h"
+
namespace perfetto {
using protozero::proto_utils::ProtoSchemaType;
diff --git a/src/tools/ftrace_proto_gen/proto_gen_utils.cc b/src/tools/ftrace_proto_gen/proto_gen_utils.cc
index 99c65e8..d734e88 100644
--- a/src/tools/ftrace_proto_gen/proto_gen_utils.cc
+++ b/src/tools/ftrace_proto_gen/proto_gen_utils.cc
@@ -32,12 +32,7 @@
namespace {
std::string RunClangFmt(const std::string& input) {
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_MAC)
- const std::string platform = "mac";
-#else
- const std::string platform = "linux64";
-#endif
- base::Subprocess clang_fmt({"buildtools/" + platform + "/clang-format"});
+ base::Subprocess clang_fmt({"third_party/clang-format/clang-format"});
clang_fmt.args.stdout_mode = base::Subprocess::OutputMode::kBuffer;
clang_fmt.args.stderr_mode = base::Subprocess::OutputMode::kInherit;
clang_fmt.args.input = input;
diff --git a/src/trace_processor/containers/row_map.h b/src/trace_processor/containers/row_map.h
index 45eedf5..a2a6a41 100644
--- a/src/trace_processor/containers/row_map.h
+++ b/src/trace_processor/containers/row_map.h
@@ -451,6 +451,12 @@
NoVariantMatched();
}
+ // Returns the data in RowMap BitVector, nullptr if RowMap is in a different
+ // mode.
+ const BitVector* GetIfBitVector() const {
+ return std::get_if<BitVector>(&data_);
+ }
+
// Returns the iterator over the rows in this RowMap.
Iterator IterateRows() const { return Iterator(this); }
diff --git a/src/trace_processor/db/overlays/null_overlay.cc b/src/trace_processor/db/overlays/null_overlay.cc
index ee27295..204f113 100644
--- a/src/trace_processor/db/overlays/null_overlay.cc
+++ b/src/trace_processor/db/overlays/null_overlay.cc
@@ -28,7 +28,7 @@
uint32_t start = non_null_->CountSetBits(t_range.range.start);
uint32_t end = non_null_->CountSetBits(t_range.range.end);
- return StorageRange({Range(start, end)});
+ return StorageRange(start, end);
}
TableBitVector NullOverlay::MapToTableBitVector(StorageBitVector s_bv,
diff --git a/src/trace_processor/db/overlays/null_overlay_unittest.cc b/src/trace_processor/db/overlays/null_overlay_unittest.cc
index 0c02ad7..5782c3e 100644
--- a/src/trace_processor/db/overlays/null_overlay_unittest.cc
+++ b/src/trace_processor/db/overlays/null_overlay_unittest.cc
@@ -25,7 +25,7 @@
TEST(NullOverlay, MapToStorageRangeOutsideBoundary) {
BitVector bv{0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0};
NullOverlay overlay(&bv);
- StorageRange r = overlay.MapToStorageRange({RowMap::Range(1, 6)});
+ StorageRange r = overlay.MapToStorageRange(TableRange(1, 6));
ASSERT_EQ(r.range.start, 0u);
ASSERT_EQ(r.range.end, 2u);
@@ -34,7 +34,7 @@
TEST(NullOverlay, MapToStorageRangeOnBoundary) {
BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
NullOverlay overlay(&bv);
- StorageRange r = overlay.MapToStorageRange({RowMap::Range(3, 8)});
+ StorageRange r = overlay.MapToStorageRange(TableRange(3, 8));
ASSERT_EQ(r.range.start, 1u);
ASSERT_EQ(r.range.end, 4u);
diff --git a/src/trace_processor/db/overlays/selector_overlay.h b/src/trace_processor/db/overlays/selector_overlay.h
index 1cad92c..73499b2 100644
--- a/src/trace_processor/db/overlays/selector_overlay.h
+++ b/src/trace_processor/db/overlays/selector_overlay.h
@@ -27,7 +27,7 @@
// Overlay responsible for selecting specific rows from Storage.
class SelectorOverlay : public StorageOverlay {
public:
- explicit SelectorOverlay(BitVector* selected) : selected_(selected) {}
+ explicit SelectorOverlay(const BitVector* selected) : selected_(selected) {}
StorageRange MapToStorageRange(TableRange) const override;
@@ -44,7 +44,7 @@
CostEstimatePerRow EstimateCostPerRow(OverlayOp) const override;
private:
- BitVector* selected_;
+ const BitVector* selected_;
};
} // namespace overlays
diff --git a/src/trace_processor/db/overlays/selector_overlay_unittest.cc b/src/trace_processor/db/overlays/selector_overlay_unittest.cc
index 8c743e8..ccc6980 100644
--- a/src/trace_processor/db/overlays/selector_overlay_unittest.cc
+++ b/src/trace_processor/db/overlays/selector_overlay_unittest.cc
@@ -25,7 +25,7 @@
TEST(SelectorOverlay, MapToStorageRangeFirst) {
BitVector selector{0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1};
SelectorOverlay overlay(&selector);
- StorageRange r = overlay.MapToStorageRange({RowMap::Range(1, 4)});
+ StorageRange r = overlay.MapToStorageRange(TableRange(1, 4));
ASSERT_EQ(r.range.start, 4u);
ASSERT_EQ(r.range.end, 8u);
@@ -34,7 +34,7 @@
TEST(SelectorOverlay, MapToStorageRangeSecond) {
BitVector selector{0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0};
SelectorOverlay overlay(&selector);
- StorageRange r = overlay.MapToStorageRange({RowMap::Range(1, 3)});
+ StorageRange r = overlay.MapToStorageRange(TableRange(1, 3));
ASSERT_EQ(r.range.start, 4u);
ASSERT_EQ(r.range.end, 7u);
diff --git a/src/trace_processor/db/overlays/types.h b/src/trace_processor/db/overlays/types.h
index 7978ada..0ea50c5 100644
--- a/src/trace_processor/db/overlays/types.h
+++ b/src/trace_processor/db/overlays/types.h
@@ -26,11 +26,17 @@
// A range of indices in the table space.
struct TableRange {
+ TableRange(uint32_t start, uint32_t end) : range(start, end) {}
+ explicit TableRange(RowMap::Range r) : range(r) {}
+
RowMap::Range range;
};
// A range of indices in the storage space.
struct StorageRange {
+ StorageRange(uint32_t start, uint32_t end) : range(start, end) {}
+ explicit StorageRange(RowMap::Range r) : range(r) {}
+
RowMap::Range range;
};
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
index 46cbd29..923e321 100644
--- a/src/trace_processor/db/query_executor.cc
+++ b/src/trace_processor/db/query_executor.cc
@@ -23,6 +23,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/status_or.h"
#include "src/trace_processor/db/overlays/null_overlay.h"
+#include "src/trace_processor/db/overlays/selector_overlay.h"
#include "src/trace_processor/db/overlays/storage_overlay.h"
#include "src/trace_processor/db/query_executor.h"
#include "src/trace_processor/db/storage/numeric_storage.h"
@@ -157,13 +158,13 @@
const SimpleColumn& col,
RowMap* rm) {
// TODO(b/283763282): We should align these to word boundaries.
- TableRange table_range{Range(rm->Get(0), rm->Get(rm->size() - 1) + 1)};
+ TableRange table_range(rm->Get(0), rm->Get(rm->size() - 1) + 1);
base::SmallVector<Range, kMaxOverlayCount> overlay_bounds;
for (const auto& overlay : col.overlays) {
StorageRange storage_range = overlay->MapToStorageRange(table_range);
overlay_bounds.emplace_back(storage_range.range);
- table_range = TableRange({storage_range.range});
+ table_range = TableRange(storage_range.range);
}
// Use linear search algorithm on storage.
@@ -252,10 +253,10 @@
use_legacy = use_legacy || (overlays::FilterOpToOverlayOp(c.op) ==
overlays::OverlayOp::kOther &&
col.type() != c.value.type);
- use_legacy = use_legacy ||
- col.overlay().row_map().size() != col.storage_base().size();
use_legacy = use_legacy || col.IsSorted() || col.IsDense() || col.IsSetId();
- use_legacy = use_legacy || col.overlay().row_map().IsIndexVector();
+ use_legacy =
+ use_legacy || (col.overlay().size() != col.storage_base().size() &&
+ !col.overlay().row_map().IsBitVector());
if (use_legacy) {
col.FilterInto(c.op, c.value, &rm);
continue;
@@ -265,9 +266,14 @@
uint32_t s_size = col.storage_base().non_null_size();
storage::NumericStorage storage(s_data, s_size, col.col_type());
- overlays::NullOverlay null_overlay(col.storage_base().bv());
-
SimpleColumn s_col{OverlaysVec(), &storage};
+
+ overlays::SelectorOverlay selector_overlay(
+ col.overlay().row_map().GetIfBitVector());
+ if (col.overlay().size() != col.storage_base().size())
+ s_col.overlays.emplace_back(&selector_overlay);
+
+ overlays::NullOverlay null_overlay(col.storage_base().bv());
if (col.IsNullable()) {
s_col.overlays.emplace_back(&null_overlay);
}
diff --git a/src/trace_processor/db/query_executor_benchmark.cc b/src/trace_processor/db/query_executor_benchmark.cc
index 55a7251..9024a50 100644
--- a/src/trace_processor/db/query_executor_benchmark.cc
+++ b/src/trace_processor/db/query_executor_benchmark.cc
@@ -20,6 +20,8 @@
#include "perfetto/ext/base/string_utils.h"
#include "src/base/test/utils.h"
#include "src/trace_processor/db/query_executor.h"
+#include "src/trace_processor/db/table.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
#include "src/trace_processor/tables/slice_tables_py.h"
namespace perfetto {
@@ -27,6 +29,24 @@
namespace {
using SliceTable = tables::SliceTable;
+using ThreadTrackTable = tables::ThreadTrackTable;
+using ExpectedFrameTimelineSliceTable = tables::ExpectedFrameTimelineSliceTable;
+using RawTable = tables::RawTable;
+using FtraceEventTable = tables::FtraceEventTable;
+
+// `SELECT * FROM SLICE` on android_monitor_contention_trace.at
+static char kSliceTable[] = "test/data/slice_table_for_benchmarks.csv";
+
+// `SELECT * FROM SLICE` on android_monitor_contention_trace.at
+static char kExpectedFrameTimelineTable[] =
+ "test/data/expected_frame_timeline_for_benchmarks.csv";
+
+// `SELECT id, cpu FROM raw` on chrome_android_systrace.pftrace.
+static char kRawTable[] = "test/data/raw_cpu_for_benchmarks.csv";
+
+// `SELECT id, cpu FROM ftrace_event` on chrome_android_systrace.pftrace.
+static char kFtraceEventTable[] =
+ "test/data/ftrace_event_cpu_for_benchmarks.csv";
enum DB { V1, V2 };
@@ -51,12 +71,10 @@
return output;
}
-std::vector<SliceTable::Row> LoadRowsFromCSVToSliceTable(
- benchmark::State& state) {
- std::vector<SliceTable::Row> rows;
+std::vector<std::string> ReadCSV(benchmark::State& state,
+ std::string file_name) {
std::string table_csv;
- static const char kTestTrace[] = "test/data/example_android_trace_30s.csv";
- perfetto::base::ReadFile(perfetto::base::GetTestDataPath(kTestTrace),
+ perfetto::base::ReadFile(perfetto::base::GetTestDataPath(file_name),
&table_csv);
if (table_csv.empty()) {
state.SkipWithError(
@@ -65,44 +83,112 @@
return {};
}
PERFETTO_CHECK(!table_csv.empty());
-
- std::vector<std::string> rows_strings = base::SplitString(table_csv, "\n");
- for (size_t i = 1; i < rows_strings.size(); ++i) {
- std::vector<std::string> row_vec = SplitCSVLine(rows_strings[i]);
- SliceTable::Row row;
- PERFETTO_CHECK(row_vec.size() >= 12);
- row.ts = *base::StringToInt64(row_vec[2]);
- row.dur = *base::StringToInt64(row_vec[3]);
- row.track_id =
- tables::ThreadTrackTable::Id(*base::StringToUInt32(row_vec[4]));
- row.depth = *base::StringToUInt32(row_vec[7]);
- row.stack_id = *base::StringToInt32(row_vec[8]);
- row.parent_stack_id = *base::StringToInt32(row_vec[9]);
- row.parent_id = base::StringToUInt32(row_vec[11]).has_value()
- ? std::make_optional<SliceTable::Id>(
- *base::StringToUInt32(row_vec[11]))
- : std::nullopt;
- row.arg_set_id = *base::StringToUInt32(row_vec[11]);
- row.thread_ts = base::StringToInt64(row_vec[12]);
- row.thread_dur = base::StringToInt64(row_vec[13]);
- rows.emplace_back(row);
- }
- return rows;
+ return base::SplitString(table_csv, "\n");
}
-struct BenchmarkSliceTable {
- explicit BenchmarkSliceTable(benchmark::State& state) : table_{&pool_} {
- auto rows = LoadRowsFromCSVToSliceTable(state);
- for (uint32_t i = 0; i < rows.size(); ++i) {
- table_.Insert(rows[i]);
+SliceTable::Row GetSliceTableRow(std::string string_row) {
+ std::vector<std::string> row_vec = SplitCSVLine(string_row);
+ SliceTable::Row row;
+ PERFETTO_CHECK(row_vec.size() >= 12);
+ row.ts = *base::StringToInt64(row_vec[2]);
+ row.dur = *base::StringToInt64(row_vec[3]);
+ row.track_id = ThreadTrackTable::Id(*base::StringToUInt32(row_vec[4]));
+ row.depth = *base::StringToUInt32(row_vec[7]);
+ row.stack_id = *base::StringToInt32(row_vec[8]);
+ row.parent_stack_id = *base::StringToInt32(row_vec[9]);
+ row.parent_id = base::StringToUInt32(row_vec[11]).has_value()
+ ? std::make_optional<SliceTable::Id>(
+ *base::StringToUInt32(row_vec[11]))
+ : std::nullopt;
+ row.arg_set_id = *base::StringToUInt32(row_vec[11]);
+ row.thread_ts = base::StringToInt64(row_vec[12]);
+ row.thread_dur = base::StringToInt64(row_vec[13]);
+ return row;
+}
+
+struct SliceTableForBenchmark {
+ explicit SliceTableForBenchmark(benchmark::State& state) : table_{&pool_} {
+ std::vector<std::string> rows_strings = ReadCSV(state, kSliceTable);
+
+ for (size_t i = 1; i < rows_strings.size(); ++i) {
+ table_.Insert(GetSliceTableRow(rows_strings[i]));
}
}
+
StringPool pool_;
SliceTable table_;
};
-void SliceTableBenchmark(benchmark::State& state,
- BenchmarkSliceTable& table,
+struct ExpectedFrameTimelineTableForBenchmark {
+ explicit ExpectedFrameTimelineTableForBenchmark(benchmark::State& state)
+ : table_{&pool_, &parent_} {
+ std::vector<std::string> table_rows_as_string =
+ ReadCSV(state, kExpectedFrameTimelineTable);
+ std::vector<std::string> parent_rows_as_string =
+ ReadCSV(state, kSliceTable);
+
+ uint32_t cur_idx = 0;
+ for (size_t i = 1; i < table_rows_as_string.size(); ++i, ++cur_idx) {
+ std::vector<std::string> row_vec = SplitCSVLine(table_rows_as_string[i]);
+
+ uint32_t idx = *base::StringToUInt32(row_vec[0]);
+ while (cur_idx < idx) {
+ parent_.Insert(GetSliceTableRow(parent_rows_as_string[cur_idx + 1]));
+ cur_idx++;
+ }
+
+ ExpectedFrameTimelineSliceTable::Row row;
+ row.ts = *base::StringToInt64(row_vec[2]);
+ row.dur = *base::StringToInt64(row_vec[3]);
+ row.track_id = ThreadTrackTable::Id(*base::StringToUInt32(row_vec[4]));
+ row.depth = *base::StringToUInt32(row_vec[7]);
+ row.stack_id = *base::StringToInt32(row_vec[8]);
+ row.parent_stack_id = *base::StringToInt32(row_vec[9]);
+ row.parent_id = base::StringToUInt32(row_vec[11]).has_value()
+ ? std::make_optional<SliceTable::Id>(
+ *base::StringToUInt32(row_vec[11]))
+ : std::nullopt;
+ row.arg_set_id = *base::StringToUInt32(row_vec[11]);
+ row.thread_ts = base::StringToInt64(row_vec[12]);
+ row.thread_dur = base::StringToInt64(row_vec[13]);
+ table_.Insert(row);
+ }
+ }
+ StringPool pool_;
+ SliceTable parent_{&pool_};
+ ExpectedFrameTimelineSliceTable table_;
+};
+
+struct FtraceEventTableForBenchmark {
+ explicit FtraceEventTableForBenchmark(benchmark::State& state) {
+ std::vector<std::string> raw_rows = ReadCSV(state, kRawTable);
+ std::vector<std::string> ftrace_event_rows =
+ ReadCSV(state, kFtraceEventTable);
+
+ uint32_t cur_idx = 0;
+ for (size_t i = 1; i < ftrace_event_rows.size(); ++i, cur_idx++) {
+ std::vector<std::string> row_vec = SplitCSVLine(ftrace_event_rows[i]);
+ uint32_t idx = *base::StringToUInt32(row_vec[0]);
+ while (cur_idx < idx) {
+ std::vector<std::string> raw_row = SplitCSVLine(raw_rows[cur_idx + 1]);
+ RawTable::Row r;
+ r.cpu = *base::StringToUInt32(raw_row[1]);
+ raw_.Insert(r);
+ cur_idx++;
+ }
+ FtraceEventTable::Row row;
+ row.cpu = *base::StringToUInt32(row_vec[1]);
+ table_.Insert(row);
+ }
+ }
+
+ StringPool pool_;
+ RawTable raw_{&pool_};
+ tables::FtraceEventTable table_{&pool_, &raw_};
+};
+
+void BenchmarkSliceTable(benchmark::State& state,
+ SliceTableForBenchmark& table,
Constraint c) {
Table::kUseFilterV2 = state.range(0) == 1;
for (auto _ : state) {
@@ -114,26 +200,66 @@
benchmark::Counter::kInvert);
}
-static void BM_DBv2SliceTableTrackIdEquals(benchmark::State& state) {
- BenchmarkSliceTable table(state);
- SliceTableBenchmark(state, table, table.table_.track_id().eq(100));
+void BenchmarkExpectedFrameTable(benchmark::State& state,
+ ExpectedFrameTimelineTableForBenchmark& table,
+ Constraint c) {
+ Table::kUseFilterV2 = state.range(0) == 1;
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(table.table_.FilterToRowMap({c}));
+ }
+ state.counters["s/row"] =
+ benchmark::Counter(static_cast<double>(table.table_.row_count()),
+ benchmark::Counter::kIsIterationInvariantRate |
+ benchmark::Counter::kInvert);
}
-BENCHMARK(BM_DBv2SliceTableTrackIdEquals)->ArgsProduct({{DB::V1, DB::V2}});
-
-static void BM_DBv2SliceTableParentIdIsNotNull(benchmark::State& state) {
- BenchmarkSliceTable table(state);
- SliceTableBenchmark(state, table, table.table_.parent_id().is_not_null());
+void BenchmarkFtraceEventTable(benchmark::State& state,
+ FtraceEventTableForBenchmark& table,
+ Constraint c) {
+ Table::kUseFilterV2 = state.range(0) == 1;
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(table.table_.FilterToRowMap({c}));
+ }
+ state.counters["s/row"] =
+ benchmark::Counter(static_cast<double>(table.table_.row_count()),
+ benchmark::Counter::kIsIterationInvariantRate |
+ benchmark::Counter::kInvert);
}
-BENCHMARK(BM_DBv2SliceTableParentIdIsNotNull)->ArgsProduct({{DB::V1, DB::V2}});
-
-static void BM_DBv2SliceTableParentIdEq(benchmark::State& state) {
- BenchmarkSliceTable table(state);
- SliceTableBenchmark(state, table, table.table_.parent_id().eq(88));
+static void BM_QESliceTableTrackIdEq(benchmark::State& state) {
+ SliceTableForBenchmark table(state);
+ BenchmarkSliceTable(state, table, table.table_.track_id().eq(100));
}
-BENCHMARK(BM_DBv2SliceTableParentIdEq)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QESliceTableTrackIdEq)->ArgsProduct({{DB::V1, DB::V2}});
+
+static void BM_QESliceTableParentIdIsNotNull(benchmark::State& state) {
+ SliceTableForBenchmark table(state);
+ BenchmarkSliceTable(state, table, table.table_.parent_id().is_not_null());
+}
+
+BENCHMARK(BM_QESliceTableParentIdIsNotNull)->ArgsProduct({{DB::V1, DB::V2}});
+
+static void BM_QESliceTableParentIdEq(benchmark::State& state) {
+ SliceTableForBenchmark table(state);
+ BenchmarkSliceTable(state, table, table.table_.parent_id().eq(88));
+}
+
+BENCHMARK(BM_QESliceTableParentIdEq)->ArgsProduct({{DB::V1, DB::V2}});
+
+static void BM_QEFilterWithSparseSelector(benchmark::State& state) {
+ ExpectedFrameTimelineTableForBenchmark table(state);
+ BenchmarkExpectedFrameTable(state, table, table.table_.track_id().eq(88));
+}
+
+BENCHMARK(BM_QEFilterWithSparseSelector)->ArgsProduct({{DB::V1, DB::V2}});
+
+static void BM_QEFilterWithDenseSelector(benchmark::State& state) {
+ FtraceEventTableForBenchmark table(state);
+ BenchmarkFtraceEventTable(state, table, table.table_.cpu().eq(4));
+}
+
+BENCHMARK(BM_QEFilterWithDenseSelector)->ArgsProduct({{DB::V1, DB::V2}});
} // namespace
} // namespace trace_processor
diff --git a/src/trace_processor/db/query_executor_unittest.cc b/src/trace_processor/db/query_executor_unittest.cc
index 01fff7a..fa914c0 100644
--- a/src/trace_processor/db/query_executor_unittest.cc
+++ b/src/trace_processor/db/query_executor_unittest.cc
@@ -224,61 +224,63 @@
}
TEST(QueryExecutor, SingleConstraintWithNullAndSelector) {
- std::vector<int64_t> storage_data{0, 1, 2, 3, 4, 0, 1, 2, 3, 4};
+ std::vector<int64_t> storage_data{0, 1, 2, 3, 0, 1, 2, 3};
NumericStorage storage(storage_data.data(), 10, ColumnType::kInt64);
- // Select 6 elements from storage, resulting in a vector {0, 1, 3, 4, 1, 2}.
- BitVector selector_bv{1, 1, 0, 1, 1, 0, 1, 1, 0, 0};
- SelectorOverlay selector_overlay(&selector_bv);
-
- // Add nulls, final vector {0, 1, NULL, 3, 4, NULL, 1, 2, NULL}.
- BitVector null_bv{1, 1, 0, 1, 1, 0, 1, 1, 0};
+ // Current vector
+ // 0, 1, NULL, 2, 3, 0, NULL, NULL, 1, 2, 3, NULL
+ BitVector null_bv{1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0};
NullOverlay null_overlay(&null_bv);
+ // Final vector
+ // 0, NULL, 3, NULL, 1, 3
+ BitVector selector_bv{1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0};
+ SelectorOverlay selector_overlay(&selector_bv);
+
// Create the column.
OverlaysVec overlays_vec;
- overlays_vec.emplace_back(&null_overlay);
overlays_vec.emplace_back(&selector_overlay);
+ overlays_vec.emplace_back(&null_overlay);
SimpleColumn col{overlays_vec, &storage};
// Filter.
Constraint c{0, FilterOp::kGe, SqlValue::Long(2)};
- QueryExecutor exec({col}, 9);
+ QueryExecutor exec({col}, 6);
RowMap res = exec.Filter({c});
- ASSERT_EQ(res.size(), 3u);
- ASSERT_EQ(res.Get(0), 3u);
- ASSERT_EQ(res.Get(1), 4u);
- ASSERT_EQ(res.Get(2), 7u);
+ ASSERT_EQ(res.size(), 2u);
+ ASSERT_EQ(res.Get(0), 2u);
+ ASSERT_EQ(res.Get(1), 5u);
}
TEST(QueryExecutor, IsNull) {
- std::vector<int64_t> storage_data{0, 1, 2, 3, 4, 0, 1, 2, 3, 4};
+ std::vector<int64_t> storage_data{0, 1, 2, 3, 0, 1, 2, 3};
NumericStorage storage(storage_data.data(), 10, ColumnType::kInt64);
- // Select 6 elements from storage, resulting in a vector {0, 1, 3, 4, 1, 2}.
- BitVector selector_bv{1, 1, 0, 1, 1, 0, 1, 1, 0, 0};
- SelectorOverlay selector_overlay(&selector_bv);
-
- // Add nulls, final vector {0, 1, NULL, 3, 4, NULL, 1, 2, NULL}.
- BitVector null_bv{1, 1, 0, 1, 1, 0, 1, 1, 0};
+ // Current vector
+ // 0, 1, NULL, 2, 3, 0, NULL, NULL, 1, 2, 3, NULL
+ BitVector null_bv{1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0};
NullOverlay null_overlay(&null_bv);
+ // Final vector
+ // 0, NULL, 3, NULL, 1, 3
+ BitVector selector_bv{1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0};
+ SelectorOverlay selector_overlay(&selector_bv);
+
// Create the column.
OverlaysVec overlays_vec;
- overlays_vec.emplace_back(&null_overlay);
overlays_vec.emplace_back(&selector_overlay);
+ overlays_vec.emplace_back(&null_overlay);
SimpleColumn col{overlays_vec, &storage};
// Filter.
Constraint c{0, FilterOp::kIsNull, SqlValue::Long(0)};
- QueryExecutor exec({col}, 9);
+ QueryExecutor exec({col}, 6);
RowMap res = exec.Filter({c});
- ASSERT_EQ(res.size(), 3u);
- ASSERT_EQ(res.Get(0), 2u);
- ASSERT_EQ(res.Get(1), 5u);
- ASSERT_EQ(res.Get(2), 8u);
+ ASSERT_EQ(res.size(), 2u);
+ ASSERT_EQ(res.Get(0), 1u);
+ ASSERT_EQ(res.Get(1), 3u);
}
} // namespace
diff --git a/src/trace_processor/stdlib/chrome/chrome_scroll_janks.sql b/src/trace_processor/stdlib/chrome/chrome_scroll_janks.sql
index 9f0f29e..b12e6a7 100644
--- a/src/trace_processor/stdlib/chrome/chrome_scroll_janks.sql
+++ b/src/trace_processor/stdlib/chrome/chrome_scroll_janks.sql
@@ -13,11 +13,11 @@
-- limitations under the License.
-- TODO(b/286187288): Move this dependency to stdlib.
-SELECT RUN_METRIC('chrome/event_latency_scroll_jank_cause.sql');
+SELECT RUN_METRIC('chrome/scroll_jank_v3.sql');
SELECT IMPORT('common.slices');
-- Selects EventLatency slices that correspond with janks in a scroll. This is
--- based on the V2 version of scroll jank metrics.
+-- based on the V3 version of scroll jank metrics.
--
-- @column id INT The slice id.
-- @column ts INT The start timestamp of the slice.
@@ -28,36 +28,38 @@
-- the jank.
-- @column sub_cause_of_jank STRING The stage of cause_of_jank that caused the
-- jank.
-CREATE TABLE chrome_janky_event_latencies_v2 AS
- SELECT
+-- @column frame_jank_ts INT The start timestamp where frame
+-- frame presentation was delayed.
+-- @column frame_jank_dur INT The duration in ms of the delay in frame
+-- presentation.
+CREATE TABLE chrome_janky_event_latencies_v3 AS
+SELECT
s.id,
s.ts,
s.dur,
s.track_id,
s.name,
e.cause_of_jank,
- e.sub_cause_of_jank
+ e.sub_cause_of_jank,
+ CAST(s.ts + s.dur - ((e.delay_since_last_frame - e.vsync_interval) * 1e6) AS INT) AS frame_jank_ts,
+ CAST((e.delay_since_last_frame - e.vsync_interval) * 1e6 AS INT) AS frame_jank_dur
FROM slice s
-JOIN event_latency_scroll_jank_cause e
- ON s.id = e.slice_id
-WHERE
- HAS_DESCENDANT_SLICE_WITH_NAME(
- s.id,
- 'SubmitCompositorFrameToPresentationCompositorFrame');
+JOIN chrome_janky_frames e
+ ON s.id = e. event_latency_id;
-- Defines slices for all of janky scrolling intervals in a trace.
--
-- @column id The unique identifier of the janky interval.
-- @column ts The start timestamp of the janky interval.
-- @column dur The duration of the janky interval.
-CREATE TABLE chrome_scroll_jank_intervals_v2 AS
+CREATE TABLE chrome_scroll_jank_intervals_v3 AS
-- Sub-table to retrieve all janky slice timestamps. Ordering calculations are
-- based on timestamps rather than durations.
WITH janky_latencies AS (
SELECT
- s.ts AS start_ts,
- s.ts + s.dur AS end_ts
- FROM chrome_janky_event_latencies_v2 s),
+ s.frame_jank_ts AS start_ts,
+ s.frame_jank_ts + s.frame_jank_dur AS end_ts
+ FROM chrome_janky_event_latencies_v3 s),
-- Determine the local maximum timestamp for janks thus far; this will allow
-- us to coalesce all earlier events up to the maximum.
ordered_jank_end_ts AS (
diff --git a/src/trace_processor/stdlib/experimental/slices.sql b/src/trace_processor/stdlib/experimental/slices.sql
index 95f1510..c09a849 100644
--- a/src/trace_processor/stdlib/experimental/slices.sql
+++ b/src/trace_processor/stdlib/experimental/slices.sql
@@ -13,6 +13,8 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
+SELECT IMPORT('common.slices');
+
-- All slices with related process and thread info if available. Unlike
-- `thread_slice` and `process_slice`, this view contains all slices,
-- with thread- and process-related columns set to NULL if the slice
@@ -64,4 +66,92 @@
LEFT JOIN thread USING (utid)
LEFT JOIN process process1 ON thread.upid = process1.upid
LEFT JOIN process_track ON slice.track_id = process_track.id
-LEFT JOIN process process2 ON process_track.upid = process2.upid;
\ No newline at end of file
+LEFT JOIN process process2 ON process_track.upid = process2.upid;
+
+-- The concept of a "flat slice" is to take the data in the slice table and
+-- remove all notion of nesting; we do this by projecting every slice in a stack to
+-- their ancestor slice, i.e at any point in time, taking the most specific active
+-- slice (i.e. the slice at the bottom of the stack) and representing that as the
+-- *only* slice that was running during that period.
+--
+-- This concept becomes very useful when you try and linearise a trace and
+-- compare it with other traces spanning the same user action; "self time" (i.e.
+-- time spent in a slice but *not* any children) is easily computed and span
+-- joins with thread state become possible without limiting to only depth zero
+--- slices.
+--
+-- Note that, no slices will be generated for intervals without without any slices.
+--
+-- As an example, consider the following slice stack:
+-- A-------------B.
+-- ----C----D----.
+-- The flattened slice will be: A----C----D----B.
+--
+-- @column slice_id Id of most active slice.
+-- @column ts Timestamp when `slice.id` became the most active slice.
+-- @column dur Duration of `slice.id` as the most active slice until the next active slice.
+-- @column depth Depth of `slice.id` in the original stack.
+-- @column name Name of `slice.id`.
+-- @column root_name Name of the top most slice of the stack.
+-- @column root_id Id of of the top most slice of the stack.
+-- @column track_id Alias for `slice.track_id`.
+-- @column utid Alias for `thread.utid`.
+-- @column tid Alias for `thread.tid`
+-- @column thread_name Alias for `thread.name`.
+-- @column upid Alias for `process.upid`.
+-- @column pid Alias for `process.pid`.
+-- @column process_name Alias for `process.name`.
+CREATE TABLE experimental_slice_flattened
+AS
+-- The algorithm proceeds as follows:
+-- 1. Find the start and end timestamps of all slices.
+-- 2. Iterate the generated timestamps within a stack in chronoligical order.
+-- 3. Generate a slice for each timestamp pair (regardless of if it was a start or end) .
+-- 4. If the first timestamp in the pair was originally a start, the slice is the 'current' slice,
+-- otherwise, the slice is the parent slice.
+WITH
+ begins AS (
+ SELECT id AS slice_id, ts, name, track_id, depth
+ FROM slice
+ WHERE dur != -1
+ ),
+ ends AS (
+ SELECT
+ COALESCE(parent.id, current.id) AS slice_id,
+ current.ts + current.dur AS ts,
+ COALESCE(parent.name, current.name) AS name,
+ current.track_id,
+ COALESCE(parent.depth, 0) AS depth
+ FROM slice current
+ LEFT JOIN slice parent
+ ON current.parent_id = parent.id
+ WHERE current.dur != -1
+ ),
+ events AS (
+ SELECT * FROM begins
+ UNION ALL
+ SELECT * FROM ends
+ ),
+ data AS (
+ SELECT
+ events.slice_id,
+ events.ts,
+ LEAD(events.ts)
+ OVER (PARTITION BY COALESCE(anc.id, events.slice_id) ORDER BY events.ts) - events.ts AS dur,
+ events.depth,
+ events.name,
+ COALESCE(anc.name, events.name) AS root_name,
+ COALESCE(anc.id, events.slice_id) AS root_id,
+ events.track_id,
+ thread_slice.utid,
+ thread_slice.tid,
+ thread_slice.thread_name,
+ thread_slice.upid,
+ thread_slice.pid,
+ thread_slice.process_name
+ FROM events
+ LEFT JOIN ANCESTOR_SLICE(events.slice_id) anc
+ ON anc.depth = 0
+ JOIN thread_slice ON thread_slice.id = events.slice_id
+ )
+SELECT * FROM data WHERE ts IS NOT NULL AND dur IS NOT NULL AND name IS NOT NULL;
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 6a8cca3..a223464 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -19,6 +19,7 @@
// Do not edit.
#include "src/traced/probes/ftrace/event_info.h"
+
#include "perfetto/protozero/proto_utils.h"
namespace perfetto {
diff --git a/test/trace_processor/diff_tests/slices/tests.py b/test/trace_processor/diff_tests/slices/tests.py
index 9a73cce..4142127 100644
--- a/test/trace_processor/diff_tests/slices/tests.py
+++ b/test/trace_processor/diff_tests/slices/tests.py
@@ -138,3 +138,25 @@
"end_ts"
174797566610797
"""))
+
+ def test_slice_flattened(self):
+ return DiffTestBlueprint(
+ trace=DataPath('chrome_input_with_frame_view.pftrace'),
+ query="""
+ SELECT import('experimental.slices');
+
+ SELECT name, root_name, ts, dur, depth, thread_name, tid, process_name, pid
+ FROM experimental_slice_flattened WHERE tid = 30944;
+ """,
+ out=Csv("""
+ "name","root_name","ts","dur","depth","thread_name","tid","process_name","pid"
+ "ThreadControllerImpl::RunTask","ThreadControllerImpl::RunTask",174793737042797,3937000,0,"CrBrowserMain",30944,"Browser",30944
+ "ThreadControllerImpl::RunTask","ThreadControllerImpl::RunTask",174793741016797,5930000,0,"CrBrowserMain",30944,"Browser",30944
+ "ThreadControllerImpl::RunTask","ThreadControllerImpl::RunTask",174793747000797,47000,0,"CrBrowserMain",30944,"Browser",30944
+ "Receive mojo message","ThreadControllerImpl::RunTask",174793747047797,136000,1,"CrBrowserMain",30944,"Browser",30944
+ "ThreadControllerImpl::RunTask","ThreadControllerImpl::RunTask",174793747183797,17000,0,"CrBrowserMain",30944,"Browser",30944
+ "Looper.dispatch: android.os.Handler(Kx3@57873a8)","Looper.dispatch: android.os.Handler(Kx3@57873a8)",174793747546797,119000,0,"CrBrowserMain",30944,"Browser",30944
+ "ThreadControllerImpl::RunTask","ThreadControllerImpl::RunTask",174796099970797,186000,0,"CrBrowserMain",30944,"Browser",30944
+ "Looper.dispatch: jy3(null)","Looper.dispatch: jy3(null)",174800056530797,1368000,0,"CrBrowserMain",30944,"Browser",30944
+ "ThreadControllerImpl::RunTask","ThreadControllerImpl::RunTask",174800107962797,132000,0,"CrBrowserMain",30944,"Browser",30944
+ """))
diff --git a/tools/install-build-deps b/tools/install-build-deps
index c72eae9..50cb1ae 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -25,6 +25,7 @@
import tempfile
import time
import zipfile
+import bz2
from collections import namedtuple
from platform import system, machine
@@ -35,7 +36,7 @@
# |source_url| can be either a git repo or a http url.
# If a git repo, |checksum| is the SHA1 committish that will be checked out.
# If a http url, |checksum| is the SHA256 of the downloaded file.
-# If the url is a .zip or .tgz file it will be automatically deflated under
+# If the url is a .zip, .tgz, or .tbz2 file it will be automatically deflated under
# |target_folder|, taking care of stripping the root folder if it's a single
# root (to avoid ending up with buildtools/protobuf/protobuf-1.2.3/... and have
# instead just buildtools/protobuf).
@@ -100,30 +101,38 @@
'windows', 'x64'),
# clang-format
- # From https://chromium.googlesource.com/chromium/src/buildtools/+/refs/heads/master/mac/clang-format.sha1
+ # From
+ # https://chromium.googlesource.com/chromium/src/buildtools/+/refs/heads/master/mac/clang-format.arm64.sha1
Dependency(
'third_party/clang-format/clang-format',
- 'https://storage.googleapis.com/chromium-clang-format/62bde1baa7196ad9df969fc1f06b66360b1a927b',
- '6df686a937443cbe6efc013467a7ba5f98d3f187eb7765bb7abc6ce47626cf66',
- 'darwin', 'all'),
+ 'https://storage.googleapis.com/chromium-clang-format/5553d7a3d912b7d49381ad68c9a56740601a57a0',
+ 'e077990b2ea6807f6abc71b4cf1e487719d7e40574baddd2b51187fdcc8db803',
+ 'darwin', 'arm64'),
+ # From
+ # https://chromium.googlesource.com/chromium/src/buildtools/+/refs/heads/master/mac/clang-format.x64.sha1
+ Dependency(
+ 'third_party/clang-format/clang-format',
+ 'https://storage.googleapis.com/chromium-clang-format/87d69aeff220c916b85b5d6d162fa5668aa9d64f',
+ '71179a8788a009cfd589636d50f0eb9f95f58b0cfda4351430bed7c0a48c080b',
+ 'darwin', 'x64'),
# From https://chromium.googlesource.com/chromium/src/buildtools/+/refs/heads/master/linux64/clang-format.sha1
Dependency(
'third_party/clang-format/clang-format',
- 'https://storage.googleapis.com/chromium-clang-format/1baf0089e895c989a311b6a38ed94d0e8be4c0a7',
- 'd02a97a87e8c28898033aaf5986967b24dc47ebd5b376e1cd93e5009f22cd75e',
+ 'https://storage.googleapis.com/chromium-clang-format/1facab3101fc6da6b9467543f27f0622b966bc19',
+ '5e459118d8ac825452e9e1f2717e4de5a36399dc6cc6aec7ec483ad27a0c927e',
'linux', 'x64'),
# From https://chromium.googlesource.com/chromium/src/buildtools/+/refs/heads/master/win/clang-format.exe.sha1
Dependency(
'third_party/clang-format/clang-format.exe',
- 'https://storage.googleapis.com/chromium-clang-format/d4afd4eba27022f5f6d518133aebde57281677c9',
- '2ba1b4d3ade90ea80316890b598ab5fc16777572be26afec6ce23117da121b80',
+ 'https://storage.googleapis.com/chromium-clang-format/2e569921b9884daf157021d6ae9e21991cd6cf81',
+ '2227376ada89ea832995b832222b722a27c4d5d8d59e9c4d7842877f99a1e30d',
'windows', 'x64'),
# Keep the SHA1 in sync with |clang_format_rev| in chromium //buildtools/DEPS.
Dependency(
'buildtools/clang_format/script',
- 'https://chromium.googlesource.com/chromium/llvm-project/cfe/tools/clang-format.git',
- '96636aa0e9f047f17447f2d45a094d0b59ed7917', 'all', 'all'),
+ 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/clang/tools/clang-format.git',
+ 'f97059df7f8b205064625cdb5f97b56668a125ef', 'all', 'all'),
# Ninja
Dependency(
@@ -650,12 +659,12 @@
deps_updated |= CheckoutGitRepo(local_path, dep.source_url, dep.checksum,
args.check_only)
continue
- is_zip = local_path.endswith('.zip') or local_path.endswith('.tgz')
- zip_target_dir = local_path[:-4] if is_zip else None
- zip_dir_stamp = os.path.join(zip_target_dir, '.stamp') if is_zip else None
+ is_compressed = any([local_path.endswith(ext) for ext in ['.zip', '.tgz', '.tbz2']])
+ compressed_target_dir = os.path.splitext(local_path)[0] if is_compressed else None
+ compressed_dir_stamp = os.path.join(compressed_target_dir, '.stamp') if is_compressed else None
- if ((not is_zip and HashLocalFile(local_path) == dep.checksum) or
- (is_zip and ReadFile(zip_dir_stamp) == dep.checksum)):
+ if ((not is_compressed and HashLocalFile(local_path) == dep.checksum) or
+ (is_compressed and ReadFile(compressed_dir_stamp) == dep.checksum)):
continue
deps_updated = True
if args.check_only:
@@ -678,32 +687,39 @@
assert (HashLocalFile(local_path) == dep.checksum)
- if is_zip:
- logging.info('Extracting %s into %s' % (local_path, zip_target_dir))
- assert (os.path.commonprefix((ROOT_DIR, zip_target_dir)) == ROOT_DIR)
- RmtreeIfExists(zip_target_dir)
+ if is_compressed:
+ logging.info('Extracting %s into %s' % (local_path, compressed_target_dir))
+ assert (os.path.commonprefix((ROOT_DIR, compressed_target_dir)) == ROOT_DIR)
+ RmtreeIfExists(compressed_target_dir)
# Decompress the archive.
if local_path.endswith('.tgz'):
- MkdirRecursive(zip_target_dir)
- subprocess.check_call(['tar', '-oxf', local_path], cwd=zip_target_dir)
+ MkdirRecursive(compressed_target_dir)
+ subprocess.check_call(['tar', '-oxf', local_path], cwd=compressed_target_dir)
elif local_path.endswith('.zip'):
with zipfile.ZipFile(local_path, 'r') as zf:
for info in zf.infolist():
- ExtractZipfilePreservePermissions(zf, info, zip_target_dir)
+ ExtractZipfilePreservePermissions(zf, info, compressed_target_dir)
+ elif local_path.endswith('.tbz2'):
+ tar_path = '{}.tar.tmp'.format(local_path)
+ with open(tar_path, 'w') as f:
+ with bz2.open(local_path, 'r') as bf:
+ f.write(bf.read())
+ MkdirRecursive(compressed_target_dir)
+ subprocess.check_call(['tar', '-oxf', tar_path], cwd=compressed_target_dir)
# If the zip contains one root folder, rebase one level up moving all
# its sub files and folders inside |target_dir|.
- subdir = os.listdir(zip_target_dir)
+ subdir = os.listdir(compressed_target_dir)
if len(subdir) == 1:
- subdir = os.path.join(zip_target_dir, subdir[0])
+ subdir = os.path.join(compressed_target_dir, subdir[0])
if os.path.isdir(subdir):
for subf in os.listdir(subdir):
- shutil.move(os.path.join(subdir, subf), zip_target_dir)
+ shutil.move(os.path.join(subdir, subf), compressed_target_dir)
os.rmdir(subdir)
# Create stamp and remove the archive.
- with open(zip_dir_stamp, 'w') as stamp_file:
+ with open(compressed_dir_stamp, 'w') as stamp_file:
stamp_file.write(dep.checksum)
os.remove(local_path)
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index 08c2ac6..94a9c0e 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -219,7 +219,8 @@
}
td {
- padding: 2px 1px;
+ padding: 3px 5px;
+ white-space: nowrap;
i.material-icons {
// Shrink the icons inside the table cells to increase the information
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index 640b30e..95ed422 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -26,20 +26,21 @@
@import "flags_page";
@import "hiring_banner";
@import "widgets_page";
+@import "widgets/anchor";
@import "widgets/button";
@import "widgets/checkbox";
-@import "widgets/text_input";
-@import "widgets/empty_state";
-@import "widgets/anchor";
-@import "widgets/popup";
-@import "widgets/multiselect";
-@import "widgets/select";
-@import "widgets/menu";
-@import "widgets/spinner";
-@import "widgets/tree";
-@import "widgets/switch";
-@import "widgets/form";
@import "widgets/details_shell";
+@import "widgets/empty_state";
+@import "widgets/error";
+@import "widgets/form";
@import "widgets/grid_layout";
+@import "widgets/menu";
+@import "widgets/multiselect";
+@import "widgets/popup";
@import "widgets/section";
@import "widgets/timestamp";
+@import "widgets/select";
+@import "widgets/spinner";
+@import "widgets/switch";
+@import "widgets/text_input";
+@import "widgets/tree";
diff --git a/ui/src/assets/widgets/error.scss b/ui/src/assets/widgets/error.scss
new file mode 100644
index 0000000..dfee07c
--- /dev/null
+++ b/ui/src/assets/widgets/error.scss
@@ -0,0 +1,20 @@
+// 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.
+
+.pf-error {
+ padding: 20px 10px;
+ color: hsl(-10, 50%, 50%);
+ font-family: $pf-font;
+ font-weight: 300;
+}
diff --git a/ui/src/base/array_utils.ts b/ui/src/base/array_utils.ts
index a3a9980..7236a43 100644
--- a/ui/src/base/array_utils.ts
+++ b/ui/src/base/array_utils.ts
@@ -31,3 +31,12 @@
export function allUnique(x: string[]): boolean {
return x.length == new Set(x).size;
}
+
+export function arrayEquals(a: any[]|undefined, b: any[]|undefined): boolean {
+ if (a === undefined || b === undefined) return false;
+ if (a.length !== b.length) return false;
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) return false;
+ }
+ return true;
+}
diff --git a/ui/src/common/queries.ts b/ui/src/common/queries.ts
index 1d63819..bf2518a 100644
--- a/ui/src/common/queries.ts
+++ b/ui/src/common/queries.ts
@@ -28,8 +28,14 @@
statementWithOutputCount: number;
}
+export interface QueryRunParams {
+ // If true, replaces nulls with "NULL" string. Default is true.
+ convertNullsToString?: boolean;
+}
+
export async function runQuery(
- sqlQuery: string, engine: EngineProxy): Promise<QueryResponse> {
+ sqlQuery: string, engine: EngineProxy, params?: QueryRunParams):
+ Promise<QueryResponse> {
const startMs = performance.now();
const queryRes = engine.query(sqlQuery);
@@ -47,6 +53,8 @@
// errored, the frontend will show a graceful message instead.
}
+ const convertNullsToString = params?.convertNullsToString ?? true;
+
const durationMs = performance.now() - startMs;
const rows: Row[] = [];
const columns = queryRes.columns();
@@ -55,7 +63,7 @@
const row: Row = {};
for (const colName of columns) {
const value = iter.get(colName);
- row[colName] = value === null ? 'NULL' : value;
+ row[colName] = value === null && convertNullsToString ? 'NULL' : value;
}
rows.push(row);
if (++numRows >= MAX_DISPLAY_ROWS) break;
diff --git a/ui/src/common/query_result.ts b/ui/src/common/query_result.ts
index 90fb8f4..9a9883c 100644
--- a/ui/src/common/query_result.ts
+++ b/ui/src/common/query_result.ts
@@ -71,6 +71,7 @@
export const LONG_NULL: bigint|null = 1n;
export type ColumnType = string|number|bigint|null|Uint8Array;
+export type SqlValue = ColumnType;
const SHIFT_32BITS = 32n;
@@ -159,7 +160,7 @@
// One row extracted from an SQL result:
export interface Row {
- [key: string]: ColumnType|undefined;
+ [key: string]: ColumnType;
}
// The methods that any iterator has to implement.
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index 8e50c57..d9ad58b 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -67,6 +67,7 @@
INPUT_LATENCY_TRACK,
} from '../tracks/scroll_jank';
import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state';
+import {THREAD_STATE_TRACK_V2_KIND} from '../tracks/thread_state_v2';
const TRACKS_V2_FLAG = featureFlags.register({
id: 'tracksV2.1',
@@ -75,6 +76,22 @@
defaultValue: false,
});
+const TRACKS_V2_COMPARE_FLAG = featureFlags.register({
+ id: 'tracksV2Compare',
+ name: 'Tracks V2: Also show V1 tracks',
+ description:
+ 'Show V1 tracks side by side with V2 tracks. Does nothing if TracksV2 is not enabled.',
+ defaultValue: false,
+});
+
+function showV2(): boolean {
+ return TRACKS_V2_FLAG.get();
+}
+
+function showV1(): boolean {
+ return !showV2() || (showV2() && TRACKS_V2_COMPARE_FLAG.get());
+}
+
const MEM_DMA_COUNTER_NAME = 'mem.dma_heap';
const MEM_DMA = 'mem.dma_buffer';
const MEM_ION = 'mem.ion';
@@ -931,18 +948,38 @@
// the track creation as well.
continue;
}
- const kind = THREAD_STATE_TRACK_KIND;
- this.tracksToAdd.push({
- engineId: this.engineId,
- kind,
- name: TrackDecider.getTrackName({utid, tid, threadName, kind}),
- trackGroup: uuid,
- trackSortKey: {
- utid,
- priority: InThreadTrackSortKey.THREAD_SCHEDULING_STATE_TRACK,
- },
- config: {utid, tid},
- });
+
+ const priority = InThreadTrackSortKey.THREAD_SCHEDULING_STATE_TRACK;
+
+ if (showV1()) {
+ const kind = THREAD_STATE_TRACK_KIND;
+ this.tracksToAdd.push({
+ engineId: this.engineId,
+ kind: THREAD_STATE_TRACK_KIND,
+ name: TrackDecider.getTrackName({utid, tid, threadName, kind}),
+ trackGroup: uuid,
+ trackSortKey: {
+ utid,
+ priority,
+ },
+ config: {utid, tid},
+ });
+ }
+
+ if (showV2()) {
+ const kind = THREAD_STATE_TRACK_V2_KIND;
+ this.tracksToAdd.push({
+ engineId: this.engineId,
+ kind,
+ name: TrackDecider.getTrackName({utid, tid, threadName, kind}),
+ trackGroup: uuid,
+ trackSortKey: {
+ utid,
+ priority,
+ },
+ config: {utid, tid},
+ });
+ }
}
}
@@ -1272,25 +1309,27 @@
const kind = SLICE_TRACK_KIND;
const name = TrackDecider.getTrackName(
{name: trackName, utid, tid, threadName, kind});
- this.tracksToAdd.push({
- engineId: this.engineId,
- kind,
- name,
- trackGroup: uuid,
- trackSortKey: {
- utid,
- priority: isDefaultTrackForScope ?
- InThreadTrackSortKey.DEFAULT_TRACK :
- InThreadTrackSortKey.ORDINARY,
- },
- config: {
- trackId,
- maxDepth,
- tid,
- },
- });
+ if (showV1()) {
+ this.tracksToAdd.push({
+ engineId: this.engineId,
+ kind,
+ name,
+ trackGroup: uuid,
+ trackSortKey: {
+ utid,
+ priority: isDefaultTrackForScope ?
+ InThreadTrackSortKey.DEFAULT_TRACK :
+ InThreadTrackSortKey.ORDINARY,
+ },
+ config: {
+ trackId,
+ maxDepth,
+ tid,
+ },
+ });
+ }
- if (TRACKS_V2_FLAG.get()) {
+ if (showV2()) {
this.tracksToAdd.push({
engineId: this.engineId,
kind: 'GenericSliceTrack',
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index a941a31..2e610d6 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -193,6 +193,11 @@
// there are at most |depth| slices.
private incomplete = new Array<CastInternal<T['slice']>>();
+ // The currently selected slice.
+ // TODO(hjd): We should fetch this from the underlying data rather
+ // than just remembering it when we see it.
+ private selectedSlice?: CastInternal<T['slice']>;
+
protected readonly tableName: string;
private maxDurNs: TPDuration = 0n;
@@ -271,6 +276,9 @@
// Give a chance to the embedder to change colors and other stuff.
this.onUpdatedSlices(this.slices);
this.onUpdatedSlices(this.incomplete);
+ if (this.selectedSlice !== undefined) {
+ this.onUpdatedSlices([this.selectedSlice]);
+ }
}
protected isSelectionHandled(selection: Selection): boolean {
@@ -283,6 +291,16 @@
return supportedSelectionKinds.includes(selection.kind);
}
+ private getTitleFont(): string {
+ const size = this.sliceLayout.titleSizePx ?? 12;
+ return `${size}px Roboto Condensed`;
+ }
+
+ private getSubtitleFont(): string {
+ const size = this.sliceLayout.subtitleSizePx ?? 8;
+ return `${size}px Roboto Condensed`;
+ }
+
renderCanvas(ctx: CanvasRenderingContext2D): void {
// TODO(hjd): fonts and colors should come from the CSS and not hardcoded
// here.
@@ -307,7 +325,7 @@
let charWidth = this.charWidth;
if (charWidth < 0) {
// TODO(hjd): Centralize font measurement/invalidation.
- ctx.font = '12px Roboto Condensed';
+ ctx.font = this.getTitleFont();
charWidth = this.charWidth = ctx.measureText('dbpqaouk').width / 8;
}
@@ -318,22 +336,21 @@
vizTime.start.toTPTime('floor'), vizTime.end.toTPTime('ceil'));
let selection = globals.state.currentSelection;
-
if (!selection || !this.isSelectionHandled(selection)) {
selection = null;
}
+ const selectedId = selection ? (selection as {id: number}).id : undefined;
+ let discoveredSelection: CastInternal<T['slice']>|undefined;
// Believe it or not, doing 4xO(N) passes is ~2x faster than trying to draw
// everything in one go. The key is that state changes operations on the
// canvas (e.g., color, fonts) dominate any number crunching we do in JS.
- this.updateSliceAndTrackHeight();
const sliceHeight = this.computedSliceHeight;
const padding = this.sliceLayout.padding;
const rowSpacing = this.computedRowSpacing;
// First pass: compute geometry of slices.
- let selSlice: CastInternal<T['slice']>|undefined;
// pxEnd is the last visible pixel in the visible viewport. Drawing
// anything < 0 or > pxEnd doesn't produce any visible effect as it goes
@@ -370,8 +387,8 @@
slice.w = sliceVizLimit - slice.x;
}
- if (selection && (selection as {id: number}).id === slice.id) {
- selSlice = slice;
+ if (selectedId === slice.id) {
+ discoveredSelection = slice;
}
}
@@ -399,7 +416,7 @@
// Third pass, draw the titles (e.g., process name for sched slices).
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
- ctx.font = '12px Roboto Condensed';
+ ctx.font = this.getTitleFont();
ctx.textBaseline = 'middle';
for (const slice of vizSlices) {
if ((slice.flags & SLICE_FLAGS_INSTANT) || !slice.title ||
@@ -411,13 +428,13 @@
const rectXCenter = slice.x + slice.w / 2;
const y = padding + slice.depth * (sliceHeight + rowSpacing);
const yDiv = slice.subTitle ? 3 : 2;
- const yMidPoint = Math.floor(y + sliceHeight / yDiv) - 0.5;
+ const yMidPoint = Math.floor(y + sliceHeight / yDiv) + 0.5;
ctx.fillText(title, rectXCenter, yMidPoint);
}
// Fourth pass, draw the subtitles (e.g., thread name for sched slices).
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
- ctx.font = '10px Roboto Condensed';
+ ctx.font = this.getSubtitleFont();
for (const slice of vizSlices) {
if (slice.w < SLICE_MIN_WIDTH_FOR_TEXT_PX || !slice.subTitle ||
(slice.flags & SLICE_FLAGS_INSTANT)) {
@@ -430,16 +447,24 @@
ctx.fillText(subTitle, rectXCenter, yMidPoint);
}
+ // Update the selectedSlice if required.
+ if (discoveredSelection !== undefined) {
+ this.selectedSlice = discoveredSelection;
+ } else if (selectedId === undefined) {
+ this.selectedSlice = undefined;
+ }
+
// Draw a thicker border around the selected slice (or chevron).
- if (selSlice !== undefined) {
- const color = selSlice.color;
- const y = padding + selSlice.depth * (sliceHeight + rowSpacing);
+ if (this.selectedSlice !== undefined) {
+ const slice = this.selectedSlice;
+ const color = slice.color;
+ const y = padding + slice.depth * (sliceHeight + rowSpacing);
ctx.strokeStyle = `hsl(${color.h}, ${color.s}%, 30%)`;
ctx.beginPath();
const THICKNESS = 3;
ctx.lineWidth = THICKNESS;
ctx.strokeRect(
- selSlice.x, y - THICKNESS / 2, selSlice.w, sliceHeight + THICKNESS);
+ slice.x, y - THICKNESS / 2, slice.w, sliceHeight + THICKNESS);
ctx.closePath();
}
@@ -457,7 +482,7 @@
// The only thing this does is drawing the sched latency arrow. We should
// have some abstraction for that arrow (ideally the same we'd use for
// flows).
- this.drawSchedLatencyArrow(ctx, selSlice);
+ this.drawSchedLatencyArrow(ctx, this.selectedSlice);
// If a slice is hovered, draw the tooltip.
const tooltip = this.hoverTooltip;
@@ -762,9 +787,14 @@
private getVisibleSlicesInternal(start: TPTime, end: TPTime):
Array<CastInternal<T['slice']>> {
- const slices =
+ let slices =
filterVisibleSlices<CastInternal<T['slice']>>(this.slices, start, end);
- return slices.concat(this.incomplete);
+ slices = slices.concat(this.incomplete);
+ // The selected slice is always visible:
+ if (this.selectedSlice && !this.slices.includes(this.selectedSlice)) {
+ slices.push(this.selectedSlice);
+ }
+ return slices;
}
private updateSliceAndTrackHeight() {
diff --git a/ui/src/frontend/bottom_tab.ts b/ui/src/frontend/bottom_tab.ts
index f78bd0c..e192969 100644
--- a/ui/src/frontend/bottom_tab.ts
+++ b/ui/src/frontend/bottom_tab.ts
@@ -118,6 +118,10 @@
void;
abstract viewTab(): void|m.Children;
+ close(): void {
+ closeTab(this.uuid);
+ }
+
createPanelVnode(): m.Vnode<any, any> {
return m(
BottomTabAdapter,
diff --git a/ui/src/frontend/chrome_slice_details_tab.ts b/ui/src/frontend/chrome_slice_details_tab.ts
index 4dd2fdf..54bfb5d 100644
--- a/ui/src/frontend/chrome_slice_details_tab.ts
+++ b/ui/src/frontend/chrome_slice_details_tab.ts
@@ -21,19 +21,27 @@
import {runQuery} from '../common/queries';
import {LONG, LONG_NULL, NUM, STR_NULL} from '../common/query_result';
import {
+ formatDuration,
TPDuration,
TPTime,
} from '../common/time';
import {ArgNode, convertArgsToTree, Key} from '../controller/args_parser';
import {Anchor} from './anchor';
-import {BottomTab, bottomTabRegistry, NewBottomTabArgs} from './bottom_tab';
+import {
+ addTab,
+ BottomTab,
+ bottomTabRegistry,
+ NewBottomTabArgs,
+} from './bottom_tab';
import {FlowPoint, globals} from './globals';
import {PanelSize} from './panel';
import {runQueryInNewTab} from './query_result_tab';
import {Icons} from './semantic_icons';
import {Arg} from './sql/args';
import {getSlice, SliceDetails, SliceRef} from './sql/slice';
+import {SqlTableTab} from './sql_table/tab';
+import {SqlTables} from './sql_table/well_known_tables';
import {asSliceSqlId, asTPTimestamp} from './sql_types';
import {getProcessName, getThreadName} from './thread_and_process_info';
import {Button} from './widgets/button';
@@ -266,7 +274,7 @@
function computeDuration(ts: TPTime, dur: TPDuration): m.Children {
if (dur === -1n) {
const minDuration = globals.state.traceTime.end - ts;
- return [m(Duration, {dur: minDuration}), ' (Did not end)'];
+ return `${formatDuration(minDuration)} (Did not end)`;
} else {
return m(Duration, {dur});
}
@@ -398,7 +406,28 @@
{title: 'Details'},
m(
Tree,
- m(TreeNode, {left: 'Name', right: slice.name}),
+ m(TreeNode, {
+ left: 'Name',
+ right: m(
+ PopupMenu2,
+ {
+ trigger: m(Anchor, slice.name),
+ },
+ m(MenuItem, {
+ label: 'Slices with the same name',
+ onclick: () => {
+ addTab({
+ kind: SqlTableTab.kind,
+ config: {
+ table: SqlTables.slice,
+ displayName: 'slice',
+ filters: [`name = ${sqliteString(slice.name)}`],
+ },
+ });
+ },
+ }),
+ ),
+ }),
m(TreeNode, {
left: 'Category',
right: !slice.category || slice.category === '[NULL]' ?
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index be1f6cd..dfa4e0f 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -578,12 +578,16 @@
this._ftracePanelData = data;
}
- makeSelection(action: DeferredAction<{}>, tabToOpen = 'current_selection') {
+ makeSelection(
+ action: DeferredAction<{}>, tab: string|null = 'current_selection') {
const previousState = this.state;
// A new selection should cancel the current search selection.
globals.dispatch(Actions.setSearchIndex({index: -1}));
- const tab = action.type === 'deselect' ? undefined : tabToOpen;
- globals.dispatch(Actions.setCurrentTab({tab}));
+ if (action.type === 'deselect') {
+ globals.dispatch(Actions.setCurrentTab({tab: undefined}));
+ } else if (tab !== null) {
+ globals.dispatch(Actions.setCurrentTab({tab: tab}));
+ }
globals.dispatch(action);
// HACK(stevegolton + altimin): This is a workaround to allow passing the
diff --git a/ui/src/frontend/overview_timeline_panel.ts b/ui/src/frontend/overview_timeline_panel.ts
index 4c3b1db..19fde6a 100644
--- a/ui/src/frontend/overview_timeline_panel.ts
+++ b/ui/src/frontend/overview_timeline_panel.ts
@@ -24,7 +24,6 @@
import {
OVERVIEW_TIMELINE_NON_VISIBLE_COLOR,
- SIDEBAR_WIDTH,
TRACK_SHELL_WIDTH,
} from './css_constants';
import {BorderDragStrategy} from './drag/border_drag_strategy';
@@ -179,19 +178,17 @@
if (this.gesture === undefined || this.gesture.isDragging) {
return;
}
- (e.target as HTMLElement).style.cursor = this.chooseCursor(e.x);
+ (e.target as HTMLElement).style.cursor = this.chooseCursor(e.offsetX);
}
private chooseCursor(x: number) {
if (this.timeScale === undefined) return 'default';
- const [vizStartPx, vizEndPx] =
+ const [startBound, endBound] =
OverviewTimelinePanel.extractBounds(this.timeScale);
- const startBound = vizStartPx - 1 + SIDEBAR_WIDTH;
- const endBound = vizEndPx + SIDEBAR_WIDTH;
if (OverviewTimelinePanel.inBorderRange(x, startBound) ||
OverviewTimelinePanel.inBorderRange(x, endBound)) {
return 'ew-resize';
- } else if (x < SIDEBAR_WIDTH + TRACK_SHELL_WIDTH) {
+ } else if (x < TRACK_SHELL_WIDTH) {
return 'default';
} else if (x < startBound || endBound < x) {
return 'crosshair';
diff --git a/ui/src/frontend/semantic_icons.ts b/ui/src/frontend/semantic_icons.ts
index a80acca..da6326e 100644
--- a/ui/src/frontend/semantic_icons.ts
+++ b/ui/src/frontend/semantic_icons.ts
@@ -19,4 +19,12 @@
static readonly ContextMenu = 'arrow_drop_down'; // Could be 'more_vert'
static readonly Copy = 'content_copy';
static readonly Delete = 'delete';
+ static readonly SortedAsc = 'arrow_upward';
+ static readonly SortedDesc = 'arrow_downward';
+ static readonly GoBack = 'chevron_left';
+ static readonly GoForward = 'chevron_right';
+ static readonly AddColumn = 'add';
+ static readonly Close = 'close';
+ static readonly Hide = 'visibility_off';
+ static readonly Filter = 'filter_list';
}
diff --git a/ui/src/frontend/slice_layout.ts b/ui/src/frontend/slice_layout.ts
index 1f28617..ca655bd 100644
--- a/ui/src/frontend/slice_layout.ts
+++ b/ui/src/frontend/slice_layout.ts
@@ -20,6 +20,8 @@
// We have a optimization for when maxDepth - minDepth == 1 so it is useful
// to set this correctly:
maxDepth: number;
+ titleSizePx?: number;
+ subtitleSizePx?: number;
}
export const SLICE_LAYOUT_BASE_DEFAULTS: SliceLayoutBase = Object.freeze({
@@ -66,7 +68,9 @@
minDepth: 0,
maxDepth: 1,
heightMode: 'FIXED',
- fixedHeight: 30,
+ fixedHeight: 18,
+ titleSizePx: 10,
+ padding: 3,
});
export type SliceLayout =
diff --git a/ui/src/frontend/sql/slice.ts b/ui/src/frontend/sql/slice.ts
index d29706b..fa9c5d6 100644
--- a/ui/src/frontend/sql/slice.ts
+++ b/ui/src/frontend/sql/slice.ts
@@ -185,10 +185,16 @@
readonly ts: TPTimestamp;
readonly dur: TPDuration;
readonly sqlTrackId: number;
+
+ // Whether clicking on the reference should change the current tab
+ // to "current selection" tab in addition to updating the selection
+ // and changing the viewport. True by default.
+ readonly switchToCurrentSelectionTab?: boolean;
}
export class SliceRef implements m.ClassComponent<SliceRefAttrs> {
view(vnode: m.Vnode<SliceRefAttrs>) {
+ const switchTab = vnode.attrs.switchToCurrentSelectionTab ?? true;
return m(
Anchor,
{
@@ -201,8 +207,10 @@
// Clamp duration to 1 - i.e. for instant events
const dur = BigintMath.max(1n, vnode.attrs.dur);
focusHorizontalRange(vnode.attrs.ts, vnode.attrs.ts + dur);
- globals.makeSelection(Actions.selectChromeSlice(
- {id: vnode.attrs.id, trackId: uiTrackId, table: 'slice'}));
+ globals.makeSelection(
+ Actions.selectChromeSlice(
+ {id: vnode.attrs.id, trackId: uiTrackId, table: 'slice'}),
+ switchTab ? 'current_selection' : null);
},
},
vnode.attrs.name);
diff --git a/ui/src/frontend/sql_table/argument_selector.ts b/ui/src/frontend/sql_table/argument_selector.ts
new file mode 100644
index 0000000..0189244
--- /dev/null
+++ b/ui/src/frontend/sql_table/argument_selector.ts
@@ -0,0 +1,85 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+
+import {EngineProxy} from '../../common/engine';
+import {STR} from '../../common/query_result';
+import {globals} from '../globals';
+import {constraintsToQueryFragment} from '../sql_utils';
+import {FilterableSelect} from '../widgets/select';
+import {Spinner} from '../widgets/spinner';
+
+import {argColumn} from './column';
+import {ArgSetIdColumn} from './table_description';
+
+const MAX_ARGS_TO_DISPLAY = 15;
+
+interface ArgumentSelectorAttrs {
+ engine: EngineProxy;
+ argSetId: ArgSetIdColumn;
+ tableName: string;
+ filters: string[];
+ // List of aliases for existing columns by the table.
+ alreadySelectedColumns: Set<string>;
+ onArgumentSelected: (argument: string) => void;
+}
+
+// A widget which allows the user to select a new argument to display.
+// Dinamically queries Trace Processor to find the relevant set of arg_set_ids
+// and which args are present in these arg sets.
+export class ArgumentSelector implements
+ m.ClassComponent<ArgumentSelectorAttrs> {
+ argList?: string[];
+
+ constructor({attrs}: m.Vnode<ArgumentSelectorAttrs>) {
+ this.load(attrs);
+ }
+
+ private async load(attrs: ArgumentSelectorAttrs) {
+ const queryResult = await attrs.engine.query(`
+ -- Encapsulate the query in a CTE to avoid clashes between filters
+ -- and columns of the 'args' table.
+ WITH arg_sets AS (
+ SELECT DISTINCT ${attrs.argSetId.name} as arg_set_id
+ FROM ${attrs.tableName}
+ ${constraintsToQueryFragment({
+ filters: attrs.filters,
+ })}
+ )
+ SELECT
+ DISTINCT args.key as key
+ FROM arg_sets
+ JOIN args USING (arg_set_id)
+ `);
+ this.argList = [];
+ const it = queryResult.iter({key: STR});
+ for (; it.valid(); it.next()) {
+ const arg = argColumn(attrs.argSetId, it.key);
+ if (attrs.alreadySelectedColumns.has(arg.alias)) continue;
+ this.argList.push(it.key);
+ }
+ globals.rafScheduler.scheduleFullRedraw();
+ }
+
+ view({attrs}: m.Vnode<ArgumentSelectorAttrs>) {
+ if (this.argList === undefined) return m(Spinner);
+ return m(FilterableSelect, {
+ values: this.argList,
+ onSelected: (value: string) => attrs.onArgumentSelected(value),
+ maxDisplayedItems: MAX_ARGS_TO_DISPLAY,
+ autofocusInput: true,
+ });
+ }
+}
diff --git a/ui/src/frontend/sql_table/column.ts b/ui/src/frontend/sql_table/column.ts
new file mode 100644
index 0000000..a57a85b
--- /dev/null
+++ b/ui/src/frontend/sql_table/column.ts
@@ -0,0 +1,66 @@
+// 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 {sqliteString} from '../../base/string_utils';
+
+import {
+ ArgSetIdColumn,
+ dependendentColumns,
+ DisplayConfig,
+ RegularSqlTableColumn,
+} from './table_description';
+
+// This file contains the defintions of different column types that can be
+// displayed in the table viewer.
+
+export interface Column {
+ // SQL expression calculating the value of this column.
+ expression: string;
+ // Unique name for this column.
+ // The relevant bit of SQL fetching this column will be ${expression} as
+ // ${alias}.
+ alias: string;
+ // Title to be displayed in the table header.
+ title: string;
+ // How the value of this column should be rendered.
+ display?: DisplayConfig;
+}
+
+export function columnFromSqlTableColumn(c: RegularSqlTableColumn): Column {
+ return {
+ expression: c.name,
+ alias: c.name,
+ title: c.title || c.name,
+ display: c.display,
+ };
+}
+
+export function argColumn(c: ArgSetIdColumn, argName: string): Column {
+ const escape = (name: string) => name.replace(/\.|\[|\]/g, '_');
+ return {
+ expression: `extract_arg(${c.name}, ${sqliteString(argName)}`,
+ alias: `_arg_${c.name}_${escape(argName)}`,
+ title: `${c.title ?? c.name} ${argName}`,
+ };
+}
+
+// Returns a list of projections (i.e. parts of the SELECT clause) that should
+// be added to the query fetching the data to be able to display the given
+// column (e.g. `foo` or `f(bar) as baz`).
+// Some table columns are backed by multiple SQL columns (e.g. slice_id is
+// backed by id, ts, dur and track_id), so we need to return a list.
+export function sqlProjectionsForColumn(column: Column): string[] {
+ return [`${column.expression} as ${column.alias}`].concat(
+ dependendentColumns(column.display).map((c) => `${c} as ${c}`));
+}
diff --git a/ui/src/frontend/sql_table/render_cell.ts b/ui/src/frontend/sql_table/render_cell.ts
new file mode 100644
index 0000000..4fdc99b
--- /dev/null
+++ b/ui/src/frontend/sql_table/render_cell.ts
@@ -0,0 +1,206 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+
+import {sqliteString} from '../../base/string_utils';
+import {Row, SqlValue} from '../../common/query_result';
+import {formatDuration, TPTime} from '../../common/time';
+import {Anchor} from '../anchor';
+import {copyToClipboard} from '../clipboard';
+import {Icons} from '../semantic_icons';
+import {SliceRef} from '../sql/slice';
+import {asSliceSqlId, asTPTimestamp} from '../sql_types';
+import {sqlValueToString} from '../sql_utils';
+import {Err} from '../widgets/error';
+import {MenuItem, PopupMenu2} from '../widgets/menu';
+import {renderTimecode} from '../widgets/timestamp';
+
+import {Column} from './column';
+import {SqlTableState} from './state';
+import {SliceIdDisplayConfig} from './table_description';
+
+// This file is responsible for rendering a value in a given sell based on the
+// column type.
+
+function filterOptionMenuItem(
+ label: string, filter: string, state: SqlTableState): m.Child {
+ return m(MenuItem, {
+ label,
+ onclick: () => {
+ state.addFilter(filter);
+ },
+ });
+}
+
+function getStandardFilters(
+ c: Column, value: SqlValue, state: SqlTableState): m.Child[] {
+ if (value === null) {
+ return [
+ filterOptionMenuItem('is null', `${c.expression} is null`, state),
+ filterOptionMenuItem('is not null', `${c.expression} is not null`, state),
+ ];
+ }
+ if (typeof value === 'string') {
+ return [
+ filterOptionMenuItem(
+ 'equals to', `${c.expression} = ${sqliteString(value)}`, state),
+ filterOptionMenuItem(
+ 'not equals to', `${c.expression} != ${sqliteString(value)}`, state),
+ ];
+ }
+ if (typeof value === 'bigint' || typeof value === 'number') {
+ return [
+ filterOptionMenuItem('equals to', `${c.expression} = ${value}`, state),
+ filterOptionMenuItem(
+ 'not equals to', `${c.expression} != ${value}`, state),
+ filterOptionMenuItem('greater than', `${c.expression} > ${value}`, state),
+ filterOptionMenuItem(
+ 'greater or equals than', `${c.expression} >= ${value}`, state),
+ filterOptionMenuItem('less than', `${c.expression} < ${value}`, state),
+ filterOptionMenuItem(
+ 'less or equals than', `${c.expression} <= ${value}`, state),
+ ];
+ }
+ return [];
+}
+
+function displayValue(value: SqlValue): m.Child {
+ if (value === null) {
+ return m('i', 'NULL');
+ }
+ return sqlValueToString(value);
+}
+
+function displayTimestamp(value: SqlValue): m.Children {
+ if (typeof value !== 'bigint') return displayValue(value);
+ return renderTimecode(asTPTimestamp(value));
+}
+
+function displayDuration(value: TPTime): string;
+function displayDuration(value: SqlValue): m.Children;
+function displayDuration(value: SqlValue): m.Children {
+ if (typeof value !== 'bigint') return displayValue(value);
+ return formatDuration(value);
+}
+
+function display(column: Column, row: Row): m.Children {
+ const value = row[column.alias];
+
+ // Handle all cases when we have non-trivial formatting.
+ switch (column.display?.type) {
+ case 'timestamp':
+ return displayTimestamp(value);
+ case 'duration':
+ case 'thread_duration':
+ return displayDuration(value);
+ }
+
+ return displayValue(value);
+}
+
+function copyMenuItem(label: string, value: string): m.Child {
+ return m(MenuItem, {
+ icon: Icons.Copy,
+ label,
+ onclick: () => {
+ copyToClipboard(value);
+ },
+ });
+}
+
+function getContextMenuItems(
+ column: Column, row: Row, state: SqlTableState): m.Child[] {
+ const result: m.Child[] = [];
+ const value = row[column.alias];
+
+ if (column.display?.type === 'timestamp' && typeof value === 'bigint') {
+ result.push(copyMenuItem('Copy raw timestamp', `${value}`));
+ // result.push(
+ // copyMenuItem('Copy formatted timestamp', displayTimestamp(value)));
+ }
+ if ((column.display?.type === 'duration' ||
+ column.display?.type === 'thread_duration') &&
+ typeof value === 'bigint') {
+ result.push(copyMenuItem('Copy raw duration', `${value}`));
+ result.push(
+ copyMenuItem('Copy formatted duration', displayDuration(value)));
+ }
+ if (typeof value === 'string') {
+ result.push(copyMenuItem('Copy', value));
+ }
+
+ const filters = getStandardFilters(column, value, state);
+ if (filters.length > 0) {
+ result.push(
+ m(MenuItem, {label: 'Add filter', icon: Icons.Filter}, ...filters));
+ }
+
+ return result;
+}
+
+function renderSliceIdColumn(
+ column: {alias: string, display: SliceIdDisplayConfig},
+ row: Row): m.Children {
+ const config = column.display;
+ const id = row[column.alias];
+ const ts = row[config.ts];
+ const dur = row[config.dur] === null ? -1n : row[config.dur];
+ const trackId = row[config.trackId];
+
+ const columnNotFoundError = (type: string, name: string) =>
+ m(Err, `${type} column ${name} not found`);
+ const wrongTypeError = (type: string, name: string, value: SqlValue) =>
+ m(Err,
+ `Wrong type for ${type} column ${name}: bigint expected, ${
+ typeof value} found`);
+
+ if (typeof id !== 'bigint') return sqlValueToString(id);
+ if (ts === undefined) return columnNotFoundError('Timestamp', config.ts);
+ if (typeof ts !== 'bigint') return wrongTypeError('timestamp', config.ts, ts);
+ if (dur === undefined) return columnNotFoundError('Duration', config.dur);
+ if (typeof dur !== 'bigint') {
+ return wrongTypeError('duration', config.dur, ts);
+ }
+ if (trackId === undefined) return columnNotFoundError('Track id', trackId);
+ if (typeof trackId !== 'bigint') {
+ return wrongTypeError('track id', config.trackId, trackId);
+ }
+
+ return m(SliceRef, {
+ id: asSliceSqlId(Number(id)),
+ name: `${id}`,
+ ts: asTPTimestamp(ts as bigint),
+ dur: dur,
+ sqlTrackId: Number(trackId),
+ switchToCurrentSelectionTab: false,
+ });
+}
+
+export function renderCell(
+ column: Column, row: Row, state: SqlTableState): m.Children {
+ if (column.display && column.display.type === 'slice_id') {
+ return renderSliceIdColumn(
+ {alias: column.alias, display: column.display}, row);
+ }
+ const displayValue = display(column, row);
+ const contextMenuItems: m.Child[] = getContextMenuItems(column, row, state);
+ return m(
+ PopupMenu2,
+ {
+ trigger: m(Anchor, displayValue),
+ },
+ ...contextMenuItems,
+ );
+}
diff --git a/ui/src/frontend/sql_table/state.ts b/ui/src/frontend/sql_table/state.ts
new file mode 100644
index 0000000..81fd2e7
--- /dev/null
+++ b/ui/src/frontend/sql_table/state.ts
@@ -0,0 +1,295 @@
+// 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 {arrayEquals} from '../../base/array_utils';
+import {SortDirection} from '../../base/comparison_utils';
+import {EngineProxy} from '../../common/engine';
+import {NUM, Row} from '../../common/query_result';
+import {globals} from '../globals';
+import {constraintsToQueryFragment} from '../sql_utils';
+import {
+ Column,
+ columnFromSqlTableColumn,
+ sqlProjectionsForColumn,
+} from './column';
+import {SqlTableDescription} from './table_description';
+
+interface ColumnOrderClause {
+ // We only allow the table to be sorted by the columns which are displayed to
+ // the user to avoid confusion, so we use a reference to the underlying Column
+ // here and compare it by reference down the line.
+ column: Column;
+ direction: SortDirection;
+}
+
+const ROW_LIMIT = 100;
+
+// Result of the execution of the query.
+interface Data {
+ // Rows to show, including pagination.
+ rows: Row[];
+ error?: string;
+}
+
+interface RowCount {
+ // Total number of rows in view, excluding the pagination.
+ // Undefined if the query returned an error.
+ count: number;
+ // Filters which were used to compute this row count.
+ // We need to recompute the totalRowCount only when filters change and not
+ // when the set of columns / order by changes.
+ filters: string[];
+}
+
+export class SqlTableState {
+ private readonly engine_: EngineProxy;
+ private readonly table_: SqlTableDescription;
+
+ get engine() {
+ return this.engine_;
+ }
+ get table() {
+ return this.table_;
+ }
+
+ private filters: string[];
+ private columns: Column[];
+ private orderBy: ColumnOrderClause[];
+ private offset = 0;
+ private data?: Data;
+ private rowCount?: RowCount;
+
+ constructor(
+ engine: EngineProxy, table: SqlTableDescription, filters?: string[]) {
+ this.engine_ = engine;
+ this.table_ = table;
+
+ this.filters = filters || [];
+ this.columns = [];
+ for (const column of this.table.columns) {
+ if (column.startsHidden) continue;
+ this.columns.push(columnFromSqlTableColumn(column));
+ }
+ this.orderBy = [];
+
+ this.reload();
+ }
+
+ // Compute the actual columns to fetch.
+ private getSQLProjections(): string[] {
+ const result = new Set<string>();
+ for (const column of this.columns) {
+ for (const p of sqlProjectionsForColumn(column)) {
+ result.add(p);
+ }
+ }
+ return Array.from(result);
+ }
+
+ private getSQLImports() {
+ return (this.table.imports || [])
+ .map((i) => `SELECT IMPORT("${i}");`)
+ .join('\n');
+ }
+
+ private getCountRowsSQLQuery(): string {
+ return `
+ ${this.getSQLImports()}
+
+ SELECT
+ COUNT() AS count
+ FROM ${this.table.name}
+ ${constraintsToQueryFragment({
+ filters: this.filters,
+ })}
+ `;
+ }
+
+ getNonPaginatedSQLQuery(): string {
+ const orderBy = this.orderBy.map((c) => ({
+ fieldName: c.column.alias,
+ direction: c.direction,
+ }));
+ return `
+ ${this.getSQLImports()}
+
+ SELECT
+ ${this.getSQLProjections().join(',\n')}
+ FROM ${this.table.name}
+ ${constraintsToQueryFragment({
+ filters: this.filters,
+ orderBy: orderBy,
+ })}
+ `;
+ }
+
+ getPaginatedSQLQuery():
+ string { // We fetch one more row to determine if we can go forward.
+ return `
+ ${this.getNonPaginatedSQLQuery()}
+ LIMIT ${ROW_LIMIT + 1}
+ OFFSET ${this.offset}
+ `;
+ }
+
+ canGoForward(): boolean {
+ if (this.data === undefined) return false;
+ return this.data.rows.length > ROW_LIMIT;
+ }
+
+ canGoBack(): boolean {
+ if (this.data === undefined) return false;
+ return this.offset > 0;
+ }
+
+ goForward() {
+ if (!this.canGoForward()) return;
+ this.offset += ROW_LIMIT;
+ this.reload({offset: 'keep'});
+ }
+
+ goBack() {
+ if (!this.canGoBack()) return;
+ this.offset -= ROW_LIMIT;
+ this.reload({offset: 'keep'});
+ }
+
+ getDisplayedRange(): {from: number, to: number}|undefined {
+ if (this.data === undefined) return undefined;
+ return {
+ from: this.offset + 1,
+ to: this.offset + Math.min(this.data.rows.length, ROW_LIMIT),
+ };
+ }
+
+ private async loadRowCount(): Promise<RowCount|undefined> {
+ const filters = Array.from(this.filters);
+ const res = await this.engine.query(this.getCountRowsSQLQuery());
+ if (res.error() !== undefined) return undefined;
+ return {
+ count: res.firstRow({count: NUM}).count,
+ filters: filters,
+ };
+ }
+
+ private async loadData(): Promise<Data> {
+ const queryRes = await this.engine.query(this.getPaginatedSQLQuery());
+ const rows: Row[] = [];
+ for (const it = queryRes.iter({}); it.valid(); it.next()) {
+ const row: Row = {};
+ for (const column of queryRes.columns()) {
+ row[column] = it.get(column);
+ }
+ rows.push(row);
+ }
+
+ return {
+ rows,
+ error: queryRes.error(),
+ };
+ }
+
+ private async reload(params?: {offset: 'reset'|'keep'}) {
+ if ((params?.offset ?? 'reset') === 'reset') {
+ this.offset = 0;
+ }
+ const updateRowCount = !arrayEquals(this.rowCount?.filters, this.filters);
+ this.data = undefined;
+ if (updateRowCount) {
+ this.rowCount = undefined;
+ }
+
+ // Delay the visual update by 50ms to avoid flickering (if the query returns
+ // before the data is loaded.
+ setTimeout(() => globals.rafScheduler.scheduleFullRedraw(), 50);
+
+ if (updateRowCount) {
+ this.rowCount = await this.loadRowCount();
+ }
+ this.data = await this.loadData();
+
+ globals.rafScheduler.scheduleFullRedraw();
+ }
+
+ getTotalRowCount(): number|undefined {
+ return this.rowCount?.count;
+ }
+
+ getDisplayedRows(): Row[] {
+ return this.data?.rows || [];
+ }
+
+ getQueryError(): string|undefined {
+ return this.data?.error;
+ }
+
+ isLoading() {
+ return this.data === undefined;
+ }
+
+ removeFilter(filter: string) {
+ this.filters.splice(this.filters.indexOf(filter), 1);
+ this.reload();
+ }
+
+ addFilter(filter: string) {
+ this.filters.push(filter);
+ this.reload();
+ }
+
+ getFilters(): string[] {
+ return this.filters;
+ }
+
+ sortBy(clause: ColumnOrderClause) {
+ this.orderBy = this.orderBy || [];
+ // Remove previous sort by the same column.
+ this.orderBy = this.orderBy.filter((c) => c.column !== clause.column);
+ // Add the new sort clause to the front, so we effectively stable-sort the
+ // data currently displayed to the user.
+ this.orderBy.unshift(clause);
+ this.reload();
+ }
+
+ unsort() {
+ this.orderBy = [];
+ this.reload();
+ }
+
+ isSortedBy(column: Column): SortDirection|undefined {
+ if (!this.orderBy) return undefined;
+ if (this.orderBy.length === 0) return undefined;
+ if (this.orderBy[0].column !== column) return undefined;
+ return this.orderBy[0].direction;
+ }
+
+ addColumn(column: Column, index: number) {
+ this.columns.splice(index + 1, 0, column);
+ this.reload({offset: 'keep'});
+ }
+
+ hideColumnAtIndex(index: number) {
+ const column = this.columns[index];
+ this.columns.splice(index, 1);
+ // We can only filter by the visibile columns to avoid confusing the user,
+ // so we remove order by clauses that refer to the hidden column.
+ this.orderBy = this.orderBy.filter((c) => c.column !== column);
+ // TODO(altimin): we can avoid the fetch here if the orderBy hasn't changed.
+ this.reload({offset: 'keep'});
+ }
+
+ getSelectedColumns(): Column[] {
+ return this.columns;
+ }
+};
diff --git a/ui/src/frontend/sql_table/tab.ts b/ui/src/frontend/sql_table/tab.ts
new file mode 100644
index 0000000..177ce43
--- /dev/null
+++ b/ui/src/frontend/sql_table/tab.ts
@@ -0,0 +1,106 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+
+import {BottomTab, bottomTabRegistry, NewBottomTabArgs} from '../bottom_tab';
+import {copyToClipboard} from '../clipboard';
+import {Icons} from '../semantic_icons';
+import {Button} from '../widgets/button';
+import {DetailsShell} from '../widgets/details_shell';
+import {exists} from '../widgets/utils';
+
+import {SqlTableState} from './state';
+import {SqlTable} from './table';
+import {SqlTableDescription} from './table_description';
+
+interface SqlTableTabConfig {
+ table: SqlTableDescription;
+ displayName?: string;
+ filters?: string[];
+}
+
+export class SqlTableTab extends BottomTab<SqlTableTabConfig> {
+ static readonly kind = 'org.perfetto.SqlTableTab';
+
+ private state: SqlTableState;
+
+ constructor(args: NewBottomTabArgs) {
+ super(args);
+
+ this.state =
+ new SqlTableState(this.engine, this.config.table, this.config.filters);
+ }
+
+ static create(args: NewBottomTabArgs): SqlTableTab {
+ return new SqlTableTab(args);
+ }
+
+ viewTab() {
+ const range = this.state.getDisplayedRange();
+ const rowCount = this.state.getTotalRowCount();
+ const navigation = [
+ exists(range) && exists(rowCount) &&
+ `Showing rows ${range.from}-${range.to} of ${rowCount}`,
+ m(Button, {
+ icon: Icons.GoBack,
+ disabled: !this.state.canGoBack(),
+ onclick: () => this.state.goBack(),
+ minimal: true,
+ }),
+ m(Button, {
+ icon: Icons.GoForward,
+ disabled: !this.state.canGoForward(),
+ onclick: () => this.state.goForward(),
+ minimal: true,
+ }),
+ ];
+
+ return m(
+ DetailsShell,
+ {
+ title: 'Table',
+ description: this.config.displayName ?? this.config.table.name,
+ buttons: [
+ ...navigation,
+ m(Button, {
+ label: 'Copy SQL query',
+ onclick: () =>
+ copyToClipboard(this.state.getNonPaginatedSQLQuery()),
+ }),
+ m(Button, {
+ label: 'Close',
+ onclick: () => this.close(),
+ }),
+ ],
+ },
+ m(SqlTable, {
+ state: this.state,
+ }));
+ }
+
+ renderTabCanvas() {}
+
+ getTitle(): string {
+ const rowCount = this.state.getTotalRowCount();
+ const rows = rowCount === undefined ? '' : `(${rowCount})`;
+ return `Table ${this.config.displayName ?? this.config.table.name} ${rows}`;
+ }
+
+ isLoading(): boolean {
+ return this.state.isLoading();
+ }
+}
+
+bottomTabRegistry.register(SqlTableTab);
diff --git a/ui/src/frontend/sql_table/table.ts b/ui/src/frontend/sql_table/table.ts
new file mode 100644
index 0000000..a42ad89
--- /dev/null
+++ b/ui/src/frontend/sql_table/table.ts
@@ -0,0 +1,165 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+
+import {EngineProxy} from '../../common/engine';
+import {Row} from '../../common/query_result';
+import {Anchor} from '../anchor';
+import {Icons} from '../semantic_icons';
+import {BasicTable} from '../widgets/basic_table';
+import {Button} from '../widgets/button';
+import {MenuDivider, MenuItem, PopupMenu2} from '../widgets/menu';
+
+import {ArgumentSelector} from './argument_selector';
+import {argColumn, Column, columnFromSqlTableColumn} from './column';
+import {renderCell} from './render_cell';
+import {SqlTableState} from './state';
+import {isArgSetIdColumn, SqlTableDescription} from './table_description';
+
+export interface SqlTableConfig {
+ readonly state: SqlTableState;
+}
+
+export class SqlTable implements m.ClassComponent<SqlTableConfig> {
+ private readonly table: SqlTableDescription;
+ private readonly engine: EngineProxy;
+
+ private state: SqlTableState;
+
+ constructor(vnode: m.Vnode<SqlTableConfig>) {
+ this.state = vnode.attrs.state;
+ this.table = this.state.table;
+ this.engine = this.state.engine;
+ }
+
+ renderFilters(): m.Children {
+ const filters: m.Child[] = [];
+ for (const filter of this.state.getFilters()) {
+ filters.push(m(Button, {
+ label: filter,
+ icon: 'close',
+ onclick: () => {
+ this.state.removeFilter(filter);
+ },
+ }));
+ }
+ return filters;
+ }
+
+ renderAddColumnOptions(addColumn: (column: Column) => void): m.Children {
+ // We do not want to add columns which already exist, so we track the
+ // columns which we are already showing here.
+ // TODO(altimin): Theoretically a single table can have two different
+ // arg_set_ids, so we should track (arg_set_id_column, arg_name) pairs here.
+ const existingColumns = new Set<string>();
+
+ for (const column of this.state.getSelectedColumns()) {
+ existingColumns.add(column.alias);
+ }
+
+ const result = [];
+ for (const column of this.table.columns) {
+ if (existingColumns.has(column.name)) continue;
+ if (isArgSetIdColumn(column)) {
+ result.push(
+ m(MenuItem,
+ {
+ label: column.name,
+ },
+ m(ArgumentSelector, {
+ engine: this.engine,
+ argSetId: column,
+ tableName: this.table.name,
+ filters: this.state.getFilters(),
+ alreadySelectedColumns: existingColumns,
+ onArgumentSelected: (argument: string) => {
+ addColumn(argColumn(column, argument));
+ },
+ })));
+ continue;
+ }
+ result.push(m(MenuItem, {
+ label: column.name,
+ onclick: () => addColumn(
+ columnFromSqlTableColumn(column),
+ ),
+ }));
+ }
+ return result;
+ }
+
+ renderColumnHeader(column: Column, index: number) {
+ const sorted = this.state.isSortedBy(column);
+ const icon = sorted === 'ASC' ?
+ Icons.SortedAsc :
+ sorted === 'DESC' ? Icons.SortedDesc : Icons.ContextMenu;
+ return m(
+ PopupMenu2,
+ {
+ trigger: m(Anchor, {icon}, column.title),
+ },
+ sorted !== 'DESC' && m(MenuItem, {
+ label: 'Sort: highest first',
+ icon: Icons.SortedDesc,
+ onclick: () => {
+ this.state.sortBy({column, direction: 'DESC'});
+ },
+ }),
+ sorted !== 'ASC' && m(MenuItem, {
+ label: 'Sort: lowest first',
+ icon: Icons.SortedAsc,
+ onclick: () => {
+ this.state.sortBy({column, direction: 'ASC'});
+ },
+ }),
+ sorted !== undefined && m(MenuItem, {
+ label: 'Unsort',
+ icon: Icons.Close,
+ onclick: () => this.state.unsort(),
+ }),
+ this.state.getSelectedColumns().length > 1 && m(MenuItem, {
+ label: 'Hide',
+ icon: Icons.Hide,
+ onclick: () => this.state.hideColumnAtIndex(index),
+ }),
+ m(MenuDivider),
+ m(MenuItem,
+ {label: 'Add column', icon: Icons.AddColumn},
+ this.renderAddColumnOptions((column) => {
+ this.state.addColumn(column, index);
+ })),
+ );
+ }
+
+ view() {
+ const rows = this.state.getDisplayedRows();
+
+ return [
+ m('div', this.renderFilters()),
+ m(BasicTable, {
+ data: rows,
+ columns: this.state.getSelectedColumns().map(
+ (column, i) => ({
+ title: this.renderColumnHeader(column, i),
+ render: (row: Row) => renderCell(column, row, this.state),
+ })),
+ }),
+ this.state.getQueryError() !== undefined &&
+ m('.query-error', this.state.getQueryError()),
+ ];
+ }
+};
+
+export {SqlTableDescription};
diff --git a/ui/src/frontend/sql_table/table_description.ts b/ui/src/frontend/sql_table/table_description.ts
new file mode 100644
index 0000000..b3fa312
--- /dev/null
+++ b/ui/src/frontend/sql_table/table_description.ts
@@ -0,0 +1,97 @@
+// 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.
+
+
+// Definition of the SQL table to be displayed in the SQL table widget,
+// including the semantic definitions of the columns (e.g. timestamp
+// column which requires special formatting). Also note that some of the
+// columns require other columns for advanced display features (e.g. timestamp
+// and duration taken together define "a time range", which can be used for
+// additional filtering.
+
+export type DisplayConfig =
+ SliceIdDisplayConfig|Timestamp|Duration|ThreadDuration|ArgSetId;
+
+// Common properties for all columns.
+interface SqlTableColumnBase {
+ // Name of the column in the SQL table.
+ name: string;
+ // Display name of the column in the UI.
+ title?: string;
+ // Whether the column should be hidden by default.
+ startsHidden?: boolean;
+}
+
+export interface ArgSetIdColumn extends SqlTableColumnBase {
+ type: 'arg_set_id';
+}
+
+export interface RegularSqlTableColumn extends SqlTableColumnBase {
+ // Special rendering instructions for this column, including the list
+ // of additional columns required for the rendering.
+ display?: DisplayConfig;
+}
+
+export type SqlTableColumn = RegularSqlTableColumn|ArgSetIdColumn;
+
+export function isArgSetIdColumn(c: SqlTableColumn): c is ArgSetIdColumn {
+ return (c as {type?: string}).type === 'arg_set_id';
+}
+
+export interface SqlTableDescription {
+ readonly imports?: string[];
+ name: string;
+ columns: SqlTableColumn[];
+}
+
+// Additional columns needed to display the given column.
+export function dependendentColumns(display?: DisplayConfig): string[] {
+ switch (display?.type) {
+ case 'slice_id':
+ return [display.ts, display.dur, display.trackId];
+ default:
+ return [];
+ }
+}
+
+// Column displaying ids into the `slice` table. Requires the ts, dur and
+// track_id columns to be able to display the value, including the
+// "go-to-slice-on-click" functionality.
+export interface SliceIdDisplayConfig {
+ type: 'slice_id';
+ ts: string;
+ dur: string;
+ trackId: string;
+}
+
+// Column displaying timestamps.
+interface Timestamp {
+ type: 'timestamp';
+}
+
+// Column displaying durations.
+export interface Duration {
+ type: 'duration';
+}
+
+// Column displaying thread durations.
+export interface ThreadDuration {
+ type: 'thread_duration';
+}
+
+// Column corresponding to an arg_set_id. Will never be directly displayed,
+// but will allow the user select an argument to display from the arg_set.
+export interface ArgSetId {
+ type: 'arg_set_id';
+}
diff --git a/ui/src/frontend/sql_table/well_known_tables.ts b/ui/src/frontend/sql_table/well_known_tables.ts
new file mode 100644
index 0000000..4b503a4
--- /dev/null
+++ b/ui/src/frontend/sql_table/well_known_tables.ts
@@ -0,0 +1,114 @@
+// 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 {SqlTableDescription} from './table';
+
+const sliceTable: SqlTableDescription = {
+ imports: ['experimental.slices'],
+ name: 'experimental_slice_with_thread_and_process_info',
+ columns: [
+ {
+ name: 'id',
+ title: 'ID',
+ display: {
+ type: 'slice_id',
+ ts: 'ts',
+ dur: 'dur',
+ trackId: 'track_id',
+ },
+ },
+ {
+ name: 'ts',
+ title: 'Timestamp',
+ display: {
+ type: 'timestamp',
+ },
+ },
+ {
+ name: 'dur',
+ title: 'Duration',
+ display: {
+ type: 'duration',
+ },
+ },
+ {
+ name: 'thread_dur',
+ title: 'Thread duration',
+ display: {
+ type: 'thread_duration',
+ },
+ },
+ {
+ name: 'category',
+ title: 'Category',
+ },
+ {
+ name: 'name',
+ title: 'Name',
+ },
+ {
+ name: 'track_id',
+ title: 'Track ID',
+ startsHidden: true,
+ },
+ {
+ name: 'track_name',
+ title: 'Track name',
+ startsHidden: true,
+ },
+ {
+ name: 'thread_name',
+ title: 'Thread name',
+ },
+ {
+ name: 'utid',
+ startsHidden: true,
+ },
+ {
+ name: 'tid',
+ },
+ {
+ name: 'process_name',
+ title: 'Process name',
+ },
+ {
+ name: 'upid',
+ startsHidden: true,
+ },
+ {
+ name: 'pid',
+ },
+ {
+ name: 'depth',
+ title: 'Depth',
+ startsHidden: true,
+ },
+ {
+ name: 'parent_id',
+ title: 'Parent slice ID',
+ startsHidden: true,
+ },
+ {
+ name: 'arg_set_id',
+ title: 'Arg',
+ display: {
+ type: 'arg_set_id',
+ },
+ },
+ ],
+};
+
+export class SqlTables {
+ static readonly slice = sliceTable;
+}
diff --git a/ui/src/frontend/sql_utils.ts b/ui/src/frontend/sql_utils.ts
index 2bbcd0e..6ef80f6 100644
--- a/ui/src/frontend/sql_utils.ts
+++ b/ui/src/frontend/sql_utils.ts
@@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {EngineProxy} from '../common/engine';
+import {ColumnType, NUM} from '../common/query_result';
import {SortDirection} from '../common/state';
-import {ColumnType} from '../common/query_result';
-interface OrderClause {
+export interface OrderClause {
fieldName: string;
direction?: SortDirection;
}
@@ -81,7 +82,10 @@
return n;
}
-export function sqlValueToString(val: ColumnType): string {
+export function sqlValueToString(val: ColumnType): string;
+export function sqlValueToString(val?: ColumnType): string|undefined;
+export function sqlValueToString(val?: ColumnType): string|undefined {
+ if (val === undefined) return undefined;
if (val instanceof Uint8Array) {
return `<blob length=${val.length}>`;
}
@@ -90,3 +94,17 @@
}
return val.toString();
}
+
+export async function getTableRowCount(
+ engine: EngineProxy, tableName: string): Promise<number|undefined> {
+ const result =
+ await engine.query(`SELECT COUNT() as count FROM ${tableName}`);
+ if (result.numRows() === 0) {
+ return undefined;
+ }
+ return result
+ .firstRow({
+ count: NUM,
+ })
+ .count;
+}
diff --git a/ui/src/frontend/widgets/basic_table.ts b/ui/src/frontend/widgets/basic_table.ts
new file mode 100644
index 0000000..578b9d7
--- /dev/null
+++ b/ui/src/frontend/widgets/basic_table.ts
@@ -0,0 +1,56 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use size file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+
+export interface ColumnDescriptor<T> {
+ title: m.Children;
+ render: (row: T) => m.Children;
+}
+
+export interface TableAttrs<T> {
+ data: T[];
+ columns: ColumnDescriptor<T>[];
+}
+
+export class BasicTable implements m.ClassComponent<TableAttrs<any>> {
+ renderColumnHeader(
+ _vnode: m.Vnode<TableAttrs<any>>,
+ column: ColumnDescriptor<any>): m.Children {
+ return m('td', column.title);
+ }
+
+ view(vnode: m.Vnode<TableAttrs<any>>): m.Child {
+ const attrs = vnode.attrs;
+
+ return m(
+ 'table.generic-table',
+ {
+ // TODO(altimin, stevegolton): this should be the default for
+ // generic-table, but currently it is overriden by
+ // .pf-details-shell .pf-content table, so specify this here for now.
+ style: {
+ 'table-layout': 'auto',
+ },
+ },
+ m('thead',
+ m('tr.header',
+ attrs.columns.map(
+ (column) => this.renderColumnHeader(vnode, column)))),
+ attrs.data.map(
+ (row) =>
+ m('tr',
+ attrs.columns.map((column) => m('td', column.render(row))))));
+ }
+}
diff --git a/ui/src/frontend/widgets/error.ts b/ui/src/frontend/widgets/error.ts
new file mode 100644
index 0000000..17c91df
--- /dev/null
+++ b/ui/src/frontend/widgets/error.ts
@@ -0,0 +1,21 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+
+export class Err implements m.Component {
+ view(vnode: m.Vnode) {
+ return m('.pf-error', vnode.children);
+ }
+}
diff --git a/ui/src/frontend/widgets/select.ts b/ui/src/frontend/widgets/select.ts
index 7fdbdd4..2ec2bc2 100644
--- a/ui/src/frontend/widgets/select.ts
+++ b/ui/src/frontend/widgets/select.ts
@@ -14,8 +14,16 @@
import m from 'mithril';
+import {globals} from '../globals';
+
+import {Menu, MenuItem} from './menu';
+import {TextInput} from './text_input';
+import {exists} from './utils';
+
export interface SelectAttrs {
disabled?: boolean;
+ // Whether to show a search box. Defaults to false.
+ filterable?: boolean;
[htmlAttrs: string]: any;
}
@@ -29,3 +37,50 @@
children);
}
}
+
+export interface FilterableSelectAttrs extends SelectAttrs {
+ values: string[];
+ onSelected: (value: string) => void;
+ maxDisplayedItems?: number;
+ autofocusInput?: boolean;
+}
+
+export class FilterableSelect implements
+ m.ClassComponent<FilterableSelectAttrs> {
+ searchText = '';
+
+ view({attrs}: m.CVnode<FilterableSelectAttrs>) {
+ const filteredValues = attrs.values.filter((name) => {
+ return name.toLowerCase().includes(this.searchText.toLowerCase());
+ });
+
+ const extraItems = exists(attrs.maxDisplayedItems) &&
+ Math.max(0, filteredValues.length - attrs.maxDisplayedItems);
+
+ // TODO(altimin): when the user presses enter and there is only one item,
+ // select the first one.
+ // MAYBE(altimin): when the user presses enter and there are multiple items,
+ // select the first one.
+ return m(
+ 'div',
+ m('.pf-search-bar',
+ m(TextInput, {
+ autofocus: attrs.autofocusInput,
+ oninput: (event: Event) => {
+ const eventTarget = event.target as HTMLTextAreaElement;
+ this.searchText = eventTarget.value;
+ globals.rafScheduler.scheduleFullRedraw();
+ },
+ value: this.searchText,
+ placeholder: 'Filter options...',
+ extraClasses: 'pf-search-box',
+ }),
+ m(Menu,
+ ...filteredValues.map(
+ (value) => m(MenuItem, {
+ label: value,
+ onclick: () => attrs.onSelected(value),
+ }),
+ extraItems && m('i', `+${extraItems} more`)))));
+ }
+}
diff --git a/ui/src/frontend/widgets/timestamp.ts b/ui/src/frontend/widgets/timestamp.ts
index b945489..a138f17 100644
--- a/ui/src/frontend/widgets/timestamp.ts
+++ b/ui/src/frontend/widgets/timestamp.ts
@@ -57,7 +57,7 @@
}
}
-function renderTimecode(ts: TPTimestamp): m.Children {
+export function renderTimecode(ts: TPTimestamp): m.Children {
const relTime = toDomainTime(ts);
const {dhhmmss, millis, micros, nanos} = new Timecode(relTime);
return [
diff --git a/ui/src/tracks/custom_sql_table_slices/index.ts b/ui/src/tracks/custom_sql_table_slices/index.ts
index 1211917..aba4d4c 100644
--- a/ui/src/tracks/custom_sql_table_slices/index.ts
+++ b/ui/src/tracks/custom_sql_table_slices/index.ts
@@ -90,7 +90,7 @@
(detailsPanelConfig.config as GenericSliceDetailsTabConfig).id =
args.slice.id;
- globals.dispatch(Actions.selectGenericSlice({
+ globals.makeSelection(Actions.selectGenericSlice({
id: args.slice.id,
sqlTableName: this.tableName,
start: args.slice.start,
diff --git a/ui/src/tracks/debug/slice_track.ts b/ui/src/tracks/debug/slice_track.ts
index 664840d..2a40482 100644
--- a/ui/src/tracks/debug/slice_track.ts
+++ b/ui/src/tracks/debug/slice_track.ts
@@ -75,7 +75,7 @@
}
onSliceClick(args: OnSliceClickArgs<DebugTrackV2Types['slice']>) {
- globals.dispatch(Actions.selectDebugSlice({
+ globals.makeSelection(Actions.selectDebugSlice({
id: args.slice.id,
sqlTableName: this.config.sqlTableName,
start: args.slice.start,
diff --git a/ui/src/tracks/scroll_jank/event_latency_track.ts b/ui/src/tracks/scroll_jank/event_latency_track.ts
index 4db9620..3a786a8 100644
--- a/ui/src/tracks/scroll_jank/event_latency_track.ts
+++ b/ui/src/tracks/scroll_jank/event_latency_track.ts
@@ -76,7 +76,7 @@
// Table name must be unique - it cannot include '-' characters or begin with
// a numeric value.
const baseTable =
- `table_${uuidv4().split('-').join('_')}_janky_event_latencies_v2`;
+ `table_${uuidv4().split('-').join('_')}_janky_event_latencies_v3`;
const tableDefSql = `CREATE TABLE ${baseTable} AS
WITH event_latencies AS (
${subTableSql}
@@ -100,7 +100,7 @@
dur,
CASE
WHEN id IN (
- SELECT id FROM chrome_janky_event_latencies_v2)
+ SELECT id FROM chrome_janky_event_latencies_v3)
THEN 'Janky EventLatency'
ELSE name
END
diff --git a/ui/src/tracks/scroll_jank/top_level_jank_track.ts b/ui/src/tracks/scroll_jank/top_level_jank_track.ts
index a2b7268..31b1550 100644
--- a/ui/src/tracks/scroll_jank/top_level_jank_track.ts
+++ b/ui/src/tracks/scroll_jank/top_level_jank_track.ts
@@ -93,7 +93,7 @@
"Janky Scrolling Time" AS name,
ts,
dur
- FROM chrome_scroll_jank_intervals_v2
+ FROM chrome_scroll_jank_intervals_v3
)
SELECT
ROW_NUMBER() OVER(ORDER BY ts) AS id,
diff --git a/ui/src/tracks/scroll_jank/top_level_janky_event_latencies.ts b/ui/src/tracks/scroll_jank/top_level_janky_event_latencies.ts
index 0ba2785..f57da22 100644
--- a/ui/src/tracks/scroll_jank/top_level_janky_event_latencies.ts
+++ b/ui/src/tracks/scroll_jank/top_level_janky_event_latencies.ts
@@ -65,7 +65,7 @@
'name AS type',
'sub_cause_of_jank',
],
- sqlTableName: 'chrome_janky_event_latencies_v2',
+ sqlTableName: 'chrome_janky_event_latencies_v3',
};
}
diff --git a/ui/src/tracks/thread_state_v2/index.ts b/ui/src/tracks/thread_state_v2/index.ts
new file mode 100644
index 0000000..b198693
--- /dev/null
+++ b/ui/src/tracks/thread_state_v2/index.ts
@@ -0,0 +1,137 @@
+// 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 {Actions} from '../../common/actions';
+import {colorForState} from '../../common/colorizer';
+import {
+ Color,
+} from '../../common/colorizer';
+import {PluginContext} from '../../common/plugin_api';
+import {NUM_NULL, STR} from '../../common/query_result';
+import {Selection} from '../../common/state';
+import {translateState} from '../../common/thread_state';
+import {
+ BASE_SLICE_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';
+
+export const THREAD_STATE_ROW = {
+ ...BASE_SLICE_ROW,
+ state: STR,
+ ioWait: NUM_NULL,
+};
+export type ThreadStateRow = typeof THREAD_STATE_ROW;
+
+
+export interface ThreadStateTrackConfig {
+ utid: number;
+}
+
+export interface ThreadStateTrackTypes extends BaseSliceTrackTypes {
+ row: ThreadStateRow;
+ config: ThreadStateTrackConfig;
+}
+
+export const THREAD_STATE_TRACK_V2_KIND = 'ThreadStateTrackV2';
+
+export class ThreadStateTrack extends BaseSliceTrack<ThreadStateTrackTypes> {
+ static readonly kind = THREAD_STATE_TRACK_V2_KIND;
+ static create(args: NewTrackArgs) {
+ return new ThreadStateTrack(args);
+ }
+
+ protected sliceLayout: SliceLayout = {...SLICE_LAYOUT_FLAT_DEFAULTS};
+
+ constructor(args: NewTrackArgs) {
+ super(args);
+ }
+
+ // This is used by the base class to call iter().
+ getRowSpec(): ThreadStateTrackTypes['row'] {
+ return THREAD_STATE_ROW;
+ }
+
+ async initSqlTable(tableName: string): Promise<void> {
+ // Do not display states 'x' and 'S' (dead & sleeping).
+ const sql = `
+ create view ${tableName} as
+ select
+ id,
+ ts,
+ dur,
+ cpu,
+ state,
+ io_wait as ioWait,
+ 0 as depth
+ from thread_state
+ where
+ utid = ${this.config.utid} and
+ state != 'x' and
+ state != 'S'
+ `;
+ await this.engine.query(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 = {
+ c: slice.baseColor.c,
+ 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,
+ trackId: this.trackId,
+ }));
+ }
+
+ protected isSelectionHandled(selection: Selection): boolean {
+ return selection.kind === 'THREAD_STATE';
+ }
+}
+
+function activate(ctx: PluginContext) {
+ ctx.registerTrack(ThreadStateTrack);
+}
+
+export const plugin = {
+ pluginId: 'perfetto.ThreadStateTrackV2',
+ activate,
+};