Merge "Use DetailsShell in ftrace panel, logs panel, and query panel."
diff --git a/Android.bp b/Android.bp
index 2586c75..722dca6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -4364,6 +4364,7 @@
srcs: [
"protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
+ "protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
"protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/batt_metric.proto",
"protos/perfetto/metrics/android/binder_metric.proto",
@@ -4437,6 +4438,7 @@
srcs: [
"protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
+ "protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
"protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/batt_metric.proto",
"protos/perfetto/metrics/android/binder_metric.proto",
@@ -4493,6 +4495,7 @@
srcs: [
"protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
+ "protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
"protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/batt_metric.proto",
"protos/perfetto/metrics/android/binder_metric.proto",
@@ -10055,6 +10058,7 @@
"src/trace_processor/metrics/sql/android/android_simpleperf.sql",
"src/trace_processor/metrics/sql/android/android_startup.sql",
"src/trace_processor/metrics/sql/android/android_surfaceflinger.sql",
+ "src/trace_processor/metrics/sql/android/android_sysui_notifications_blocking_calls_metric.sql",
"src/trace_processor/metrics/sql/android/android_task_names.sql",
"src/trace_processor/metrics/sql/android/android_trace_quality.sql",
"src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql",
diff --git a/BUILD b/BUILD
index e590dd7..d076160 100644
--- a/BUILD
+++ b/BUILD
@@ -1790,6 +1790,7 @@
"src/trace_processor/metrics/sql/android/android_simpleperf.sql",
"src/trace_processor/metrics/sql/android/android_startup.sql",
"src/trace_processor/metrics/sql/android/android_surfaceflinger.sql",
+ "src/trace_processor/metrics/sql/android/android_sysui_notifications_blocking_calls_metric.sql",
"src/trace_processor/metrics/sql/android/android_task_names.sql",
"src/trace_processor/metrics/sql/android/android_trace_quality.sql",
"src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql",
@@ -3933,6 +3934,7 @@
srcs = [
"protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
+ "protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
"protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/batt_metric.proto",
"protos/perfetto/metrics/android/binder_metric.proto",
diff --git a/OWNERS b/OWNERS
index 5c57ecb..3c29a20 100644
--- a/OWNERS
+++ b/OWNERS
@@ -25,6 +25,9 @@
# UI, Chromium-related metrics and simpler trace processor changes.
altimin@google.com
+# UI
+stevegolton@google.com
+
# Most Android-related metrics.
ilkos@google.com
diff --git a/protos/perfetto/metrics/android/BUILD.gn b/protos/perfetto/metrics/android/BUILD.gn
index 2fed17a..5a48545 100644
--- a/protos/perfetto/metrics/android/BUILD.gn
+++ b/protos/perfetto/metrics/android/BUILD.gn
@@ -20,6 +20,7 @@
"source_set",
]
sources = [
+ "android_sysui_notifications_blocking_calls_metric.proto",
"android_blocking_calls_cuj_metric.proto",
"android_frame_timeline_metric.proto",
"android_trusty_workqueues.proto",
diff --git a/protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto b/protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto
new file mode 100644
index 0000000..909c48c
--- /dev/null
+++ b/protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+import "protos/perfetto/metrics/android/process_metadata.proto";
+
+// Blocking calls inside System UI Notifications. Shows count and total duration for each.
+message AndroidSysUINotificationsBlockingCallsMetric {
+ repeated BlockingCall blocking_calls = 1;
+
+ // Blocking call on the main thread.
+ message BlockingCall {
+ // Name of the blocking call
+ optional string name = 1;
+ // Number of times it happened within the CUJ
+ optional int64 cnt = 2;
+ // Total duration within the CUJ
+ optional int64 total_dur_ms = 3;
+ // Maximal duration within the CUJ
+ optional int64 max_dur_ms = 4;
+ // Minimal duration within the CUJ
+ optional int64 min_dur_ms = 5;
+ // Total duration within the CUJ in nanoseconds
+ optional int64 total_dur_ns = 6;
+ // Maximal duration within the CUJ in nanoseconds
+ optional int64 max_dur_ns = 7;
+ // Minimal duration within the CUJ in nanoseconds
+ optional int64 min_dur_ns = 8;
+ }
+}
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index 8b5e5e4..1beec18 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -20,6 +20,7 @@
import "protos/perfetto/metrics/android/android_frame_timeline_metric.proto";
import "protos/perfetto/metrics/android/batt_metric.proto";
+import "protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto";
import "protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto";
import "protos/perfetto/metrics/android/cpu_metric.proto";
import "protos/perfetto/metrics/android/camera_metric.proto";
@@ -102,7 +103,7 @@
// Root message for all Perfetto-based metrics.
//
-// Next id: 50
+// Next id: 52
message TraceMetrics {
reserved 4, 10, 13, 14, 16, 19;
@@ -239,6 +240,8 @@
optional AndroidMonitorContentionMetric android_monitor_contention = 50;
+ optional AndroidSysUINotificationsBlockingCallsMetric android_sysui_notifications_blocking_calls_metric = 51;
+
// Demo extensions.
extensions 450 to 499;
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 393095c..0758d23 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -133,6 +133,35 @@
// End of protos/perfetto/metrics/android/android_frame_timeline_metric.proto
+// Begin of protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto
+
+// Blocking calls inside System UI Notifications. Shows count and total duration for each.
+message AndroidSysUINotificationsBlockingCallsMetric {
+ repeated BlockingCall blocking_calls = 1;
+
+ // Blocking call on the main thread.
+ message BlockingCall {
+ // Name of the blocking call
+ optional string name = 1;
+ // Number of times it happened within the CUJ
+ optional int64 cnt = 2;
+ // Total duration within the CUJ
+ optional int64 total_dur_ms = 3;
+ // Maximal duration within the CUJ
+ optional int64 max_dur_ms = 4;
+ // Minimal duration within the CUJ
+ optional int64 min_dur_ms = 5;
+ // Total duration within the CUJ in nanoseconds
+ optional int64 total_dur_ns = 6;
+ // Maximal duration within the CUJ in nanoseconds
+ optional int64 max_dur_ns = 7;
+ // Minimal duration within the CUJ in nanoseconds
+ optional int64 min_dur_ns = 8;
+ }
+}
+
+// End of protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto
+
// Begin of protos/perfetto/metrics/android/android_trusty_workqueues.proto
// Metric used to generate a simplified view of the Trusty kworker events.
@@ -2042,7 +2071,7 @@
// Root message for all Perfetto-based metrics.
//
-// Next id: 50
+// Next id: 52
message TraceMetrics {
reserved 4, 10, 13, 14, 16, 19;
@@ -2179,6 +2208,8 @@
optional AndroidMonitorContentionMetric android_monitor_contention = 50;
+ optional AndroidSysUINotificationsBlockingCallsMetric android_sysui_notifications_blocking_calls_metric = 51;
+
// Demo extensions.
extensions 450 to 499;
diff --git a/python/BUILD.gn b/python/BUILD.gn
index 17ad6f3..dbd1e9d 100644
--- a/python/BUILD.gn
+++ b/python/BUILD.gn
@@ -26,7 +26,6 @@
sources = [
"generators/stdlib_docs/extractor.py",
"generators/stdlib_docs/parse.py",
- "generators/stdlib_docs/types.py",
"generators/stdlib_docs/utils.py",
]
}
diff --git a/python/generators/stdlib_docs/extractor.py b/python/generators/stdlib_docs/extractor.py
index 94db4d3..fce88f2 100644
--- a/python/generators/stdlib_docs/extractor.py
+++ b/python/generators/stdlib_docs/extractor.py
@@ -17,7 +17,7 @@
from re import Match
from typing import List, Optional, Tuple
-from python.generators.stdlib_docs.types import ObjKind
+from python.generators.stdlib_docs.utils import ObjKind
from python.generators.stdlib_docs.utils import extract_comment
from python.generators.stdlib_docs.utils import match_pattern
from python.generators.stdlib_docs.utils import PATTERN_BY_KIND
diff --git a/python/generators/stdlib_docs/parse.py b/python/generators/stdlib_docs/parse.py
index a78bcca..0bb4c28 100644
--- a/python/generators/stdlib_docs/parse.py
+++ b/python/generators/stdlib_docs/parse.py
@@ -20,7 +20,7 @@
from typing import Any, Dict, List, Optional, Set, Tuple, Union
from python.generators.stdlib_docs.extractor import DocsExtractor
-from python.generators.stdlib_docs.types import ObjKind
+from python.generators.stdlib_docs.utils import ObjKind
from python.generators.stdlib_docs.utils import ARG_ANNOTATION_PATTERN
from python.generators.stdlib_docs.utils import NAME_AND_TYPE_PATTERN
from python.generators.stdlib_docs.utils import FUNCTION_RETURN_PATTERN
diff --git a/python/generators/stdlib_docs/types.py b/python/generators/stdlib_docs/types.py
deleted file mode 100644
index a9baad3..0000000
--- a/python/generators/stdlib_docs/types.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright (C) 2023 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the 'License');
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an 'AS IS' BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from enum import Enum
-
-
-class ObjKind(str, Enum):
- table_view = 'table_view'
- function = 'function'
- view_function = 'view_function'
diff --git a/python/generators/stdlib_docs/utils.py b/python/generators/stdlib_docs/utils.py
index eeccc01..d635b08 100644
--- a/python/generators/stdlib_docs/utils.py
+++ b/python/generators/stdlib_docs/utils.py
@@ -12,14 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from enum import Enum
import re
from typing import Dict, List
-from python.generators.stdlib_docs.types import ObjKind
-
LOWER_NAME = r'[a-z_\d]+'
UPPER_NAME = r'[A-Z_\d]+'
ANY_WORDS = r'[^\s].*'
+ANY_NON_QUOTE = r'[^\']*.*'
TYPE = r'[A-Z]+'
SQL = r'[\s\S]*?'
@@ -47,10 +47,17 @@
# Args: anything before closing bracket with '.
fr"({ANY_WORDS})\)',\s*"
# Return columns: anything between two '.
- fr"'\s*({ANY_WORDS})',\s*"
+ fr"'\s*({ANY_NON_QUOTE})\s*',\s*"
# Sql: Anything between ' and ');. We are catching \'.
fr"'({SQL})'\s*\);")
+
+class ObjKind(str, Enum):
+ table_view = 'table_view'
+ function = 'function'
+ view_function = 'view_function'
+
+
PATTERN_BY_KIND = {
ObjKind.table_view: CREATE_TABLE_VIEW_PATTERN,
ObjKind.function: CREATE_FUNCTION_PATTERN,
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index 536dff9..fcdc866 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/python/perfetto/trace_processor/metrics.descriptor.sha1 b/python/perfetto/trace_processor/metrics.descriptor.sha1
new file mode 100644
index 0000000..d0befe1
--- /dev/null
+++ b/python/perfetto/trace_processor/metrics.descriptor.sha1
@@ -0,0 +1,6 @@
+
+// SHA1(tools/gen_binary_descriptors)
+// 6886b319e65925c037179e71a803b8473d06dc7d
+// SHA1(protos/perfetto/metrics/metrics.proto)
+// fe539c6187d701d117ab2f757e1a6b20a035af9f
+
\ No newline at end of file
diff --git a/src/trace_processor/db/overlays/null_overlay.cc b/src/trace_processor/db/overlays/null_overlay.cc
index 204f113..7bc1b43 100644
--- a/src/trace_processor/db/overlays/null_overlay.cc
+++ b/src/trace_processor/db/overlays/null_overlay.cc
@@ -16,6 +16,7 @@
#include "src/trace_processor/db/overlays/null_overlay.h"
#include "perfetto/ext/base/flat_hash_map.h"
+#include "src/trace_processor/containers/bit_vector.h"
#include "src/trace_processor/db/overlays/types.h"
namespace perfetto {
@@ -31,6 +32,18 @@
return StorageRange(start, end);
}
+TableRangeOrBitVector NullOverlay::MapToTableRangeOrBitVector(
+ StorageRange s_range,
+ OverlayOp op) const {
+ PERFETTO_DCHECK(s_range.range.end <= non_null_->CountSetBits());
+
+ BitVector range_to_bv(s_range.range.start, false);
+ range_to_bv.Resize(s_range.range.end, true);
+
+ return TableRangeOrBitVector(
+ MapToTableBitVector(StorageBitVector{std::move(range_to_bv)}, op).bv);
+}
+
TableBitVector NullOverlay::MapToTableBitVector(StorageBitVector s_bv,
OverlayOp op) const {
BitVector res = non_null_->Copy();
diff --git a/src/trace_processor/db/overlays/null_overlay.h b/src/trace_processor/db/overlays/null_overlay.h
index 328dc9b..5753fa3 100644
--- a/src/trace_processor/db/overlays/null_overlay.h
+++ b/src/trace_processor/db/overlays/null_overlay.h
@@ -18,6 +18,7 @@
#define SRC_TRACE_PROCESSOR_DB_OVERLAYS_NULL_OVERLAY_H_
#include "src/trace_processor/db/overlays/storage_overlay.h"
+#include "src/trace_processor/db/overlays/types.h"
namespace perfetto {
namespace trace_processor {
@@ -31,6 +32,9 @@
StorageRange MapToStorageRange(TableRange) const override;
+ TableRangeOrBitVector MapToTableRangeOrBitVector(StorageRange,
+ OverlayOp) const override;
+
TableBitVector MapToTableBitVector(StorageBitVector,
OverlayOp) const override;
diff --git a/src/trace_processor/db/overlays/null_overlay_unittest.cc b/src/trace_processor/db/overlays/null_overlay_unittest.cc
index 5782c3e..d69780c 100644
--- a/src/trace_processor/db/overlays/null_overlay_unittest.cc
+++ b/src/trace_processor/db/overlays/null_overlay_unittest.cc
@@ -40,6 +40,25 @@
ASSERT_EQ(r.range.end, 4u);
}
+TEST(NullOverlay, MapToTableRangeOutsideBoundary) {
+ BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
+ NullOverlay overlay(&bv);
+ auto r =
+ overlay.MapToTableRangeOrBitVector(StorageRange(1, 3), OverlayOp::kOther);
+
+ // All set bits between |bv| index 3 and 6.
+ ASSERT_EQ(r.TakeIfBitVector().CountSetBits(), 2u);
+}
+
+TEST(NullOverlay, MapToTableRangeOnBoundary) {
+ BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
+ NullOverlay overlay(&bv);
+ auto r =
+ overlay.MapToTableRangeOrBitVector(StorageRange(0, 5), OverlayOp::kOther);
+
+ ASSERT_EQ(r.TakeIfBitVector().CountSetBits(), 5u);
+}
+
TEST(NullOverlay, MapToTableBitVector) {
BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
NullOverlay overlay(&bv);
diff --git a/src/trace_processor/db/overlays/selector_overlay.cc b/src/trace_processor/db/overlays/selector_overlay.cc
index bc6fcfb..fc40bc6 100644
--- a/src/trace_processor/db/overlays/selector_overlay.cc
+++ b/src/trace_processor/db/overlays/selector_overlay.cc
@@ -16,6 +16,7 @@
#include "src/trace_processor/db/overlays/selector_overlay.h"
#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/overlays/types.h"
namespace perfetto {
namespace trace_processor {
@@ -30,6 +31,18 @@
selected_->IndexOfNthSet(t_range.range.end - 1) + 1)};
}
+TableRangeOrBitVector SelectorOverlay::MapToTableRangeOrBitVector(
+ StorageRange s_range,
+ OverlayOp) const {
+ if (s_range.range.size() == 0)
+ return TableRangeOrBitVector(Range());
+
+ uint32_t start = selected_->CountSetBits(s_range.range.start);
+ uint32_t end = selected_->CountSetBits(s_range.range.end);
+
+ return TableRangeOrBitVector(Range(start, end));
+}
+
TableBitVector SelectorOverlay::MapToTableBitVector(StorageBitVector s_bv,
OverlayOp) const {
PERFETTO_DCHECK(selected_->size() >= s_bv.bv.size());
diff --git a/src/trace_processor/db/overlays/selector_overlay.h b/src/trace_processor/db/overlays/selector_overlay.h
index 73499b2..ac591d3 100644
--- a/src/trace_processor/db/overlays/selector_overlay.h
+++ b/src/trace_processor/db/overlays/selector_overlay.h
@@ -31,6 +31,9 @@
StorageRange MapToStorageRange(TableRange) const override;
+ TableRangeOrBitVector MapToTableRangeOrBitVector(StorageRange,
+ OverlayOp) const override;
+
TableBitVector MapToTableBitVector(StorageBitVector,
OverlayOp) const override;
diff --git a/src/trace_processor/db/overlays/selector_overlay_unittest.cc b/src/trace_processor/db/overlays/selector_overlay_unittest.cc
index ccc6980..1e4a130 100644
--- a/src/trace_processor/db/overlays/selector_overlay_unittest.cc
+++ b/src/trace_processor/db/overlays/selector_overlay_unittest.cc
@@ -15,6 +15,7 @@
*/
#include "src/trace_processor/db/overlays/selector_overlay.h"
+#include "src/trace_processor/db/overlays/types.h"
#include "test/gtest_and_gmock.h"
namespace perfetto {
@@ -40,6 +41,26 @@
ASSERT_EQ(r.range.end, 7u);
}
+TEST(SelectorOverlay, MapToTableRangeFirst) {
+ BitVector selector{0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1};
+ SelectorOverlay overlay(&selector);
+ auto r =
+ overlay.MapToTableRangeOrBitVector(StorageRange(2, 5), OverlayOp::kOther);
+
+ ASSERT_EQ(r.TakeIfRange().start, 1u);
+ ASSERT_EQ(r.TakeIfRange().end, 3u);
+}
+
+TEST(SelectorOverlay, MapToTableRangeSecond) {
+ BitVector selector{0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0};
+ SelectorOverlay overlay(&selector);
+ auto r = overlay.MapToTableRangeOrBitVector(StorageRange(0, 10),
+ OverlayOp::kOther);
+
+ ASSERT_EQ(r.TakeIfRange().start, 0u);
+ ASSERT_EQ(r.TakeIfRange().end, 6u);
+}
+
TEST(SelectorOverlay, MapToTableBitVector) {
BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
SelectorOverlay overlay(&selector);
diff --git a/src/trace_processor/db/overlays/storage_overlay.h b/src/trace_processor/db/overlays/storage_overlay.h
index c31610b..d6ad8df 100644
--- a/src/trace_processor/db/overlays/storage_overlay.h
+++ b/src/trace_processor/db/overlays/storage_overlay.h
@@ -50,6 +50,11 @@
// indices in the storage space.
virtual StorageRange MapToStorageRange(TableRange) const = 0;
+ // Returns the smallest Range or BitVector containing all of the elements
+ // matching the OverlayOp.
+ virtual TableRangeOrBitVector MapToTableRangeOrBitVector(StorageRange,
+ OverlayOp) const = 0;
+
// Maps a BitVector of indices in storage space to an equivalent range of
// indices in the table space.
virtual TableBitVector MapToTableBitVector(StorageBitVector,
diff --git a/src/trace_processor/db/overlays/types.h b/src/trace_processor/db/overlays/types.h
index 0ea50c5..83af6bc 100644
--- a/src/trace_processor/db/overlays/types.h
+++ b/src/trace_processor/db/overlays/types.h
@@ -16,6 +16,8 @@
#ifndef SRC_TRACE_PROCESSOR_DB_OVERLAYS_TYPES_H_
#define SRC_TRACE_PROCESSOR_DB_OVERLAYS_TYPES_H_
+#include <variant>
+#include "perfetto/base/logging.h"
#include "src/trace_processor/containers/bit_vector.h"
#include "src/trace_processor/containers/row_map.h"
#include "src/trace_processor/db/storage/types.h"
@@ -24,20 +26,22 @@
namespace trace_processor {
namespace overlays {
+using Range = RowMap::Range;
+
// 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) {}
+ explicit TableRange(Range r) : range(r) {}
- RowMap::Range range;
+ 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) {}
+ explicit StorageRange(Range r) : range(r) {}
- RowMap::Range range;
+ Range range;
};
// A BitVector with set bits corresponding to indices in the table space.
@@ -50,6 +54,27 @@
BitVector bv;
};
+using RangeOrBitVector = std::variant<Range, BitVector>;
+
+struct TableRangeOrBitVector {
+ explicit TableRangeOrBitVector(Range range) : val(range) {}
+ explicit TableRangeOrBitVector(BitVector bv) : val(std::move(bv)) {}
+
+ bool IsRange() const { return std::holds_alternative<Range>(val); }
+ bool IsBitVector() const { return std::holds_alternative<BitVector>(val); }
+
+ BitVector TakeIfBitVector() {
+ PERFETTO_DCHECK(IsBitVector());
+ return std::move(*std::get_if<BitVector>(&val));
+ }
+ Range TakeIfRange() {
+ PERFETTO_DCHECK(IsRange());
+ return std::move(*std::get_if<Range>(&val));
+ }
+
+ RangeOrBitVector val = Range();
+};
+
// Represents a vector of indices in the table space.
struct TableIndexVector {
std::vector<uint32_t> indices;
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
index 923e321..8fceb62 100644
--- a/src/trace_processor/db/query_executor.cc
+++ b/src/trace_processor/db/query_executor.cc
@@ -22,11 +22,14 @@
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/containers/row_map.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/overlays/types.h"
#include "src/trace_processor/db/query_executor.h"
#include "src/trace_processor/db/storage/numeric_storage.h"
+#include "src/trace_processor/db/storage/types.h"
#include "src/trace_processor/db/table.h"
namespace perfetto {
@@ -125,10 +128,16 @@
if (rm->empty())
return;
+ if (col.sorted_intrinsically && c.op != FilterOp::kNe) {
+ BinarySearch(c, col, rm);
+ return;
+ }
+
uint32_t rm_size = rm->size();
uint32_t rm_first = rm->Get(0);
uint32_t rm_last = rm->Get(rm_size - 1);
uint32_t range_size = rm_last - rm_first;
+
// If the number of elements in the rowmap is small or the number of elements
// is less than 1/10th of the range, use indexed filtering.
// TODO(b/283763282): use Overlay estimations.
@@ -180,6 +189,45 @@
return std::move(filtered_storage.bv);
}
+void QueryExecutor::BinarySearch(const Constraint& c,
+ 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)};
+ 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});
+ }
+
+ // Use binary search algorithm on storage.
+ overlays::TableRangeOrBitVector res(
+ col.storage->BinarySearchIntrinsic(c.op, c.value, table_range.range));
+
+ OverlayOp op = overlays::FilterOpToOverlayOp(c.op);
+ for (uint32_t i = 0; i < col.overlays.size(); ++i) {
+ uint32_t rev_i = static_cast<uint32_t>(col.overlays.size()) - 1 - i;
+
+ if (res.IsBitVector()) {
+ TableBitVector t_bv = col.overlays[rev_i]->MapToTableBitVector(
+ StorageBitVector{res.TakeIfBitVector()}, op);
+ res.val = std::move(t_bv.bv);
+ } else {
+ res = col.overlays[rev_i]->MapToTableRangeOrBitVector(
+ StorageRange(res.TakeIfRange()), op);
+ }
+ }
+
+ if (res.IsBitVector()) {
+ rm->Intersect(RowMap(res.TakeIfBitVector()));
+ return;
+ }
+
+ rm->Intersect(RowMap(res.TakeIfRange().start, res.TakeIfRange().end));
+}
+
RowMap QueryExecutor::IndexSearch(const Constraint& c,
const SimpleColumn& col,
RowMap* rm) {
@@ -253,10 +301,12 @@
use_legacy = use_legacy || (overlays::FilterOpToOverlayOp(c.op) ==
overlays::OverlayOp::kOther &&
col.type() != c.value.type);
- use_legacy = use_legacy || col.IsSorted() || col.IsDense() || col.IsSetId();
+ use_legacy = use_legacy || col.IsDense() || col.IsSetId();
use_legacy =
use_legacy || (col.overlay().size() != col.storage_base().size() &&
!col.overlay().row_map().IsBitVector());
+ use_legacy = use_legacy ||
+ (col.IsSorted() && col.overlay().row_map().IsIndexVector());
if (use_legacy) {
col.FilterInto(c.op, c.value, &rm);
continue;
@@ -266,7 +316,7 @@
uint32_t s_size = col.storage_base().non_null_size();
storage::NumericStorage storage(s_data, s_size, col.col_type());
- SimpleColumn s_col{OverlaysVec(), &storage};
+ SimpleColumn s_col{OverlaysVec(), &storage, col.IsSorted()};
overlays::SelectorOverlay selector_overlay(
col.overlay().row_map().GetIfBitVector());
diff --git a/src/trace_processor/db/query_executor.h b/src/trace_processor/db/query_executor.h
index 2e04bb9..d875453 100644
--- a/src/trace_processor/db/query_executor.h
+++ b/src/trace_processor/db/query_executor.h
@@ -42,6 +42,8 @@
base::SmallVector<const overlays::StorageOverlay*, kMaxOverlayCount>
overlays;
const storage::Storage* storage;
+ // TODO(b/283763282): Move knowledge about sorted state to Storage
+ bool sorted_intrinsically = false;
};
// |row_count| is the size of the last overlay.
@@ -85,6 +87,12 @@
return IndexSearch(c, col, rm);
}
+ static void BinarySearchForTesting(const Constraint& c,
+ const SimpleColumn& col,
+ RowMap* rm) {
+ BinarySearch(c, col, rm);
+ }
+
private:
// Updates RowMap with result of filtering single column using the Constraint.
static void FilterColumn(const Constraint&, const SimpleColumn&, RowMap*);
@@ -95,6 +103,8 @@
const SimpleColumn&,
RowMap*);
+ static void BinarySearch(const Constraint&, const SimpleColumn&, RowMap*);
+
// Filters the column using Index algorithm - finds the indices to filter the
// storage with.
static RowMap IndexSearch(const Constraint&, const SimpleColumn&, RowMap*);
diff --git a/src/trace_processor/db/query_executor_benchmark.cc b/src/trace_processor/db/query_executor_benchmark.cc
index 9024a50..e6eb97d 100644
--- a/src/trace_processor/db/query_executor_benchmark.cc
+++ b/src/trace_processor/db/query_executor_benchmark.cc
@@ -15,6 +15,7 @@
*/
#include <benchmark/benchmark.h>
+#include <string>
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/string_utils.h"
@@ -23,6 +24,7 @@
#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"
+#include "src/trace_processor/tables/track_tables_py.h"
namespace perfetto {
namespace trace_processor {
@@ -247,6 +249,13 @@
BENCHMARK(BM_QESliceTableParentIdEq)->ArgsProduct({{DB::V1, DB::V2}});
+static void BM_QESliceTableSorted(benchmark::State& state) {
+ SliceTableForBenchmark table(state);
+ BenchmarkSliceTable(state, table, table.table_.ts().gt(1000));
+}
+
+BENCHMARK(BM_QESliceTableSorted)->ArgsProduct({{DB::V1, DB::V2}});
+
static void BM_QEFilterWithSparseSelector(benchmark::State& state) {
ExpectedFrameTimelineTableForBenchmark table(state);
BenchmarkExpectedFrameTable(state, table, table.table_.track_id().eq(88));
diff --git a/src/trace_processor/db/query_executor_unittest.cc b/src/trace_processor/db/query_executor_unittest.cc
index fa914c0..3e02d79 100644
--- a/src/trace_processor/db/query_executor_unittest.cc
+++ b/src/trace_processor/db/query_executor_unittest.cc
@@ -283,6 +283,63 @@
ASSERT_EQ(res.Get(1), 3u);
}
+TEST(QueryExecutor, BinarySearch) {
+ std::vector<int64_t> storage_data{0, 1, 2, 3, 4, 5, 6};
+ NumericStorage storage(storage_data.data(), 7, ColumnType::kInt64);
+
+ // Add nulls - {0, 1, NULL, NULL, 2, 3, NULL, NULL, 4, 5, 6, NULL}
+ BitVector null_bv{1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0};
+ NullOverlay null_overlay(&null_bv);
+
+ // Final vector {1, NULL, 3, NULL, 5, NULL}.
+ BitVector selector_bv{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
+ SelectorOverlay selector_overlay(&selector_bv);
+
+ // Create the column.
+ OverlaysVec overlays_vec;
+ overlays_vec.emplace_back(&selector_overlay);
+ overlays_vec.emplace_back(&null_overlay);
+ SimpleColumn col{overlays_vec, &storage, true};
+
+ // Filter.
+ Constraint c{0, FilterOp::kGe, SqlValue::Long(3)};
+ QueryExecutor exec({col}, 6);
+ RowMap res = exec.Filter({c});
+
+ ASSERT_EQ(res.size(), 2u);
+ ASSERT_EQ(res.Get(0), 2u);
+ ASSERT_EQ(res.Get(1), 4u);
+}
+
+TEST(QueryExecutor, BinarySearchIsNull) {
+ std::vector<int64_t> storage_data{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ NumericStorage storage(storage_data.data(), 10, ColumnType::kInt64);
+
+ // Select 6 elements from storage, resulting in a vector {0, 1, 3, 4, 6, 7}.
+ BitVector selector_bv{1, 1, 0, 1, 1, 0, 1, 1, 0, 0};
+ SelectorOverlay selector_overlay(&selector_bv);
+
+ // Add nulls, final vector {NULL, NULL, NULL 0, 1, 3, 4, 6, 7}.
+ BitVector null_bv{0, 0, 0, 1, 1, 1, 1, 1, 1};
+ NullOverlay null_overlay(&null_bv);
+
+ // Create the column.
+ OverlaysVec overlays_vec;
+ overlays_vec.emplace_back(&null_overlay);
+ overlays_vec.emplace_back(&selector_overlay);
+ SimpleColumn col{overlays_vec, &storage, true};
+
+ // Filter.
+ Constraint c{0, FilterOp::kIsNull, SqlValue::Long(0)};
+ QueryExecutor exec({col}, 9);
+ RowMap res = exec.Filter({c});
+
+ ASSERT_EQ(res.size(), 3u);
+ ASSERT_EQ(res.Get(0), 0u);
+ ASSERT_EQ(res.Get(1), 1u);
+ ASSERT_EQ(res.Get(2), 2u);
+}
+
} // namespace
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/db/storage/numeric_storage.cc b/src/trace_processor/db/storage/numeric_storage.cc
index 0c3f345..71eebec 100644
--- a/src/trace_processor/db/storage/numeric_storage.cc
+++ b/src/trace_processor/db/storage/numeric_storage.cc
@@ -321,7 +321,7 @@
RowMap::Range search_range) const {
std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
if (op == FilterOp::kIsNotNull)
- return RowMap::Range(0, size());
+ return search_range;
if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
return RowMap::Range();
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index a566661..d61d0e3 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -22,6 +22,7 @@
"android_batt.sql",
"android_binder.sql",
"android_blocking_calls_cuj_metric.sql",
+ "android_sysui_notifications_blocking_calls_metric.sql",
"android_camera.sql",
"android_camera_unagg.sql",
"android_cpu.sql",
diff --git a/src/trace_processor/metrics/sql/android/android_sysui_notifications_blocking_calls_metric.sql b/src/trace_processor/metrics/sql/android/android_sysui_notifications_blocking_calls_metric.sql
new file mode 100644
index 0000000..470b245
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/android_sysui_notifications_blocking_calls_metric.sql
@@ -0,0 +1,53 @@
+--
+-- Copyright 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
+--
+-- https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+SELECT IMPORT('android.slices');
+
+DROP TABLE IF EXISTS android_sysui_notifications_blocking_calls;
+CREATE TABLE android_sysui_notifications_blocking_calls AS
+SELECT
+ s.name name,
+ COUNT(s.name) count,
+ MAX(dur) AS max_dur_ns,
+ MIN(dur) AS min_dur_ns,
+ SUM(dur) AS total_dur_ns
+FROM slice s
+ JOIN thread_track ON s.track_id = thread_track.id
+ JOIN thread USING (utid)
+WHERE
+ thread.is_main_thread AND
+ s.dur > 0 AND (
+ s.name GLOB 'NotificationStackScrollLayout#onMeasure'
+ OR s.name GLOB 'NotificationToplineView#onMeasure'
+ OR s.name GLOB 'ExpNotRow#*'
+)
+GROUP BY s.name;
+
+DROP VIEW IF EXISTS android_sysui_notifications_blocking_calls_metric_output;
+CREATE VIEW android_sysui_notifications_blocking_calls_metric_output AS
+SELECT AndroidSysUINotificationsBlockingCallsMetric('blocking_calls', (
+ SELECT RepeatedField(
+ AndroidSysUINotificationsBlockingCallsMetric_BlockingCall(
+ 'name', a.name,
+ 'cnt', a.count,
+ 'total_dur_ns', a.total_dur_ns,
+ 'max_dur_ns', a.max_dur_ns,
+ 'min_dur_ns', a.min_dur_ns
+ )
+ )
+ FROM android_sysui_notifications_blocking_calls a
+ ORDER BY total_dur_ns DESC
+ )
+);
diff --git a/src/trace_processor/stdlib/experimental/thread_executing_span.sql b/src/trace_processor/stdlib/experimental/thread_executing_span.sql
index 338ec66..44bac68 100644
--- a/src/trace_processor/stdlib/experimental/thread_executing_span.sql
+++ b/src/trace_processor/stdlib/experimental/thread_executing_span.sql
@@ -339,6 +339,8 @@
-- @column blocked_dur Duration of blocking thread state before waking up.
-- @column blocked_state Thread state ('D' or 'S') of blocked thread_state before waking up.
-- @column blocked_function Kernel blocking function of thread state before waking up.
+-- @column is_root Whether this span is the root in the slice tree.
+-- @column is_leaf Whether this span is the leaf in the slice tree.
-- @column depth Tree depth from |root_id|
-- @column root_id Thread state id used to start the recursion. Helpful for SQL JOINs
SELECT CREATE_VIEW_FUNCTION(
@@ -410,6 +412,8 @@
-- @column blocked_dur Duration of blocking thread state before waking up.
-- @column blocked_state Thread state ('D' or 'S') of blocked thread_state before waking up.
-- @column blocked_function Kernel blocking function of thread state before waking up.
+-- @column is_root Whether this span is the root in the slice tree.
+-- @column is_leaf Whether this span is the leaf in the slice tree.
-- @column height Tree height from |leaf_id|
-- @column leaf_id Thread state id used to start the recursion. Helpful for SQL JOINs
SELECT CREATE_VIEW_FUNCTION(
diff --git a/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.out b/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.out
new file mode 100644
index 0000000..5b69129
--- /dev/null
+++ b/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.out
@@ -0,0 +1,23 @@
+android_sysui_notifications_blocking_calls_metric {
+ blocking_calls {
+ name: "ExpNotRow#onMeasure(BigTextStyle)"
+ cnt: 1
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "ExpNotRow#onMeasure(MessagingStyle)"
+ cnt: 1
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+ blocking_calls {
+ name: "NotificationStackScrollLayout#onMeasure"
+ cnt: 1
+ total_dur_ns: 10000000
+ max_dur_ns: 10000000
+ min_dur_ns: 10000000
+ }
+}
diff --git a/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.py b/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.py
new file mode 100644
index 0000000..c5df8b4
--- /dev/null
+++ b/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License 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.
+
+from os import sys, path
+import synth_common
+
+# com.android.systemui
+SYSUI_PID = 1000
+
+THIRD_PROCESS_PID = 3000
+
+# List of blocking calls
+blocking_call_names = [
+ 'NotificationStackScrollLayout#onMeasure', 'ExpNotRow#onMeasure(MessagingStyle)',
+ 'ExpNotRow#onMeasure(BigTextStyle)',
+ 'Should not be in the metric'
+]
+
+
+def add_main_thread_atrace(trace, ts, ts_end, buf, pid):
+ trace.add_atrace_begin(ts=ts, tid=pid, pid=pid, buf=buf)
+ trace.add_atrace_end(ts=ts_end, tid=pid, pid=pid)
+
+
+def add_async_trace(trace, ts, ts_end, buf, pid):
+ trace.add_atrace_async_begin(ts=ts, tid=pid, pid=pid, buf=buf)
+ trace.add_atrace_async_end(ts=ts_end, tid=pid, pid=pid, buf=buf)
+
+# Creates a trace that contains one of each blocking call.
+def add_all_sysui_notifications_blocking_calls(trace, pid):
+ blocking_call_dur = 10_000_000
+ blocking_call_ts = 2_000_000
+
+ cuj_dur = len(blocking_call_names) * blocking_call_dur
+ add_async_trace(
+ trace,
+ ts=blocking_call_ts,
+ ts_end=blocking_call_ts + cuj_dur,
+ buf="L<TEST_WITH_MANY_BLOCKING_CALLS>",
+ pid=pid)
+
+ for blocking_call in blocking_call_names:
+ add_main_thread_atrace(
+ trace,
+ ts=blocking_call_ts,
+ ts_end=blocking_call_ts + blocking_call_dur,
+ buf=blocking_call,
+ pid=pid)
+ blocking_call_ts += blocking_call_dur
+
+
+def add_process(trace, package_name, uid, pid):
+ trace.add_package_list(ts=0, name=package_name, uid=uid, version_code=1)
+ trace.add_process(
+ pid=pid, ppid=0, cmdline=package_name, uid=uid)
+ trace.add_thread(tid=pid, tgid=pid, cmdline="MainThread", name="MainThread")
+
+
+def setup_trace():
+ trace = synth_common.create_trace()
+ trace.add_packet()
+ add_process(trace, package_name="com.android.systemui", uid=10001,
+ pid=SYSUI_PID)
+ trace.add_ftrace_packet(cpu=0)
+ return trace
+
+
+trace = setup_trace()
+
+
+add_all_sysui_notifications_blocking_calls(trace, pid=SYSUI_PID)
+
+# See test_android_sysui_notifications_blocking_calls.
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/diff_tests/android/tests.py b/test/trace_processor/diff_tests/android/tests.py
index 7d683cf..ba2d198 100644
--- a/test/trace_processor/diff_tests/android/tests.py
+++ b/test/trace_processor/diff_tests/android/tests.py
@@ -396,6 +396,12 @@
""",
out=Path('android_slice_standardization.out'))
+ def test_android_sysui_notifications_blocking_calls(self):
+ return DiffTestBlueprint(
+ trace=Path('android_sysui_notifications_blocking_calls_metric.py'),
+ query=Metric('android_sysui_notifications_blocking_calls_metric'),
+ out=Path('android_sysui_notifications_blocking_calls_metric.out'))
+
def test_monitor_contention_extraction(self):
return DiffTestBlueprint(
trace=DataPath('android_monitor_contention_trace.atr'),
diff --git a/tools/gen_stdlib_docs_json.py b/tools/gen_stdlib_docs_json.py
index 1f2e5be..dcb4023 100755
--- a/tools/gen_stdlib_docs_json.py
+++ b/tools/gen_stdlib_docs_json.py
@@ -71,6 +71,11 @@
import_key = path.split(".sql")[0].replace("/", ".")
docs = parse_file_to_dict(path, sql)
+ if isinstance(docs, list):
+ for d in docs:
+ print(d)
+ return 1
+
assert isinstance(docs, dict)
if not any(docs.values()):
continue
diff --git a/ui/OWNERS b/ui/OWNERS
index 7b9c12b..aaf587f 100644
--- a/ui/OWNERS
+++ b/ui/OWNERS
@@ -1,5 +1,6 @@
hjd@google.com
primiano@google.com
+stevegolton@google.com
# Chrome-related bits (but also good escalations for many other UI changes).
ddrone@google.com
diff --git a/ui/src/assets/widgets/timestamp.scss b/ui/src/assets/widgets/timestamp.scss
index f361ff4..c71bb8d 100644
--- a/ui/src/assets/widgets/timestamp.scss
+++ b/ui/src/assets/widgets/timestamp.scss
@@ -14,23 +14,17 @@
@import "theme";
-// Make millis, micros, & nanos slightly smaller than hh:mm:ss for readability.
-$subsec-font-size: 0.9em;
-
.pf-timecode {
// Spacing the sub sections using CSS rather than spaces makes the spaces
// disappear when copying.
.pf-timecode-millis {
margin-left: 1px;
- font-size: $subsec-font-size;
}
.pf-timecode-micros {
margin-left: 2px;
- font-size: $subsec-font-size;
}
.pf-timecode-nanos {
margin-left: 2px;
- font-size: $subsec-font-size;
}
.pf-button {
margin-left: 2px;
diff --git a/ui/src/chrome_extension/chrome_tracing_controller.ts b/ui/src/chrome_extension/chrome_tracing_controller.ts
index 3b95c53..42f2a48 100644
--- a/ui/src/chrome_extension/chrome_tracing_controller.ts
+++ b/ui/src/chrome_extension/chrome_tracing_controller.ts
@@ -178,7 +178,7 @@
};
this.sendMessage(response);
if (res.eof) return;
- this.readBuffers(offset + res.data.length);
+ this.readBuffers(offset + chunk.length);
}
async disableTracing() {
diff --git a/ui/src/frontend/chrome_slice_details_tab.ts b/ui/src/frontend/chrome_slice_details_tab.ts
index 54bfb5d..1d34836 100644
--- a/ui/src/frontend/chrome_slice_details_tab.ts
+++ b/ui/src/frontend/chrome_slice_details_tab.ts
@@ -334,7 +334,7 @@
export class ChromeSliceDetailsTab extends
BottomTab<ChromeSliceDetailsTabConfig> {
- static readonly kind = 'org.perfetto.ChromeSliceDetailsTab';
+ static readonly kind = 'dev.perfetto.ChromeSliceDetailsTab';
private sliceDetails?: SliceDetails;
diff --git a/ui/src/frontend/generic_slice_details_tab.ts b/ui/src/frontend/generic_slice_details_tab.ts
index 0cf3043..5d206f7 100644
--- a/ui/src/frontend/generic_slice_details_tab.ts
+++ b/ui/src/frontend/generic_slice_details_tab.ts
@@ -46,7 +46,7 @@
// need to be rendered and how.
export class GenericSliceDetailsTab extends
BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'org.perfetto.GenericSliceDetailsTab';
+ static readonly kind = 'dev.perfetto.GenericSliceDetailsTab';
data: {[key: string]: ColumnType}|undefined;
diff --git a/ui/src/frontend/query_result_tab.ts b/ui/src/frontend/query_result_tab.ts
index 7f370f6..dfcaf8c 100644
--- a/ui/src/frontend/query_result_tab.ts
+++ b/ui/src/frontend/query_result_tab.ts
@@ -56,7 +56,7 @@
}
export class QueryResultTab extends BottomTab<QueryResultTabConfig> {
- static readonly kind = 'org.perfetto.QueryResultTab';
+ static readonly kind = 'dev.perfetto.QueryResultTab';
queryResponse?: QueryResponse;
sqlViewName?: string;
diff --git a/ui/src/frontend/sql/args.ts b/ui/src/frontend/sql/args.ts
index a03a8b5..6d76ad4 100644
--- a/ui/src/frontend/sql/args.ts
+++ b/ui/src/frontend/sql/args.ts
@@ -27,7 +27,7 @@
} from '../sql_types';
export type ArgValue = bigint|string|number|boolean|null;
-type ArgValueType = 'int'|'uint'|'string'|'bool'|'real'|'null';
+type ArgValueType = 'int'|'uint'|'pointer'|'string'|'bool'|'real'|'null';
export interface Arg {
id: ArgsId;
@@ -90,6 +90,9 @@
case 'int':
case 'uint':
return value.intValue;
+ case 'pointer':
+ return value.intValue === null ? null :
+ `0x${value.intValue.toString(16)}`;
case 'string':
return value.stringValue;
case 'bool':
diff --git a/ui/src/frontend/sql_table/column.ts b/ui/src/frontend/sql_table/column.ts
index a57a85b..e94da6f 100644
--- a/ui/src/frontend/sql_table/column.ts
+++ b/ui/src/frontend/sql_table/column.ts
@@ -49,7 +49,7 @@
export function argColumn(c: ArgSetIdColumn, argName: string): Column {
const escape = (name: string) => name.replace(/\.|\[|\]/g, '_');
return {
- expression: `extract_arg(${c.name}, ${sqliteString(argName)}`,
+ expression: `extract_arg(${c.name}, ${sqliteString(argName)})`,
alias: `_arg_${c.name}_${escape(argName)}`,
title: `${c.title ?? c.name} ${argName}`,
};
diff --git a/ui/src/frontend/sql_table/render_cell.ts b/ui/src/frontend/sql_table/render_cell.ts
index 4fdc99b..b3d0a4b 100644
--- a/ui/src/frontend/sql_table/render_cell.ts
+++ b/ui/src/frontend/sql_table/render_cell.ts
@@ -25,7 +25,7 @@
import {sqlValueToString} from '../sql_utils';
import {Err} from '../widgets/error';
import {MenuItem, PopupMenu2} from '../widgets/menu';
-import {renderTimecode} from '../widgets/timestamp';
+import {Timestamp} from '../widgets/timestamp';
import {Column} from './column';
import {SqlTableState} from './state';
@@ -83,11 +83,6 @@
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 {
@@ -100,8 +95,6 @@
// 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);
@@ -125,11 +118,6 @@
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') {
@@ -150,6 +138,32 @@
return result;
}
+function renderStandardColumn(
+ column: Column, row: Row, state: SqlTableState): m.Children {
+ const displayValue = display(column, row);
+ const contextMenuItems: m.Child[] = getContextMenuItems(column, row, state);
+ return m(
+ PopupMenu2,
+ {
+ trigger: m(Anchor, displayValue),
+ },
+ ...contextMenuItems,
+ );
+}
+
+function renderTimestampColumn(
+ column: Column, row: Row, state: SqlTableState): m.Children {
+ const value = row[column.alias];
+ if (typeof value !== 'bigint') {
+ return renderStandardColumn(column, row, state);
+ }
+
+ return m(Timestamp, {
+ ts: asTPTimestamp(value),
+ extraMenuItems: getContextMenuItems(column, row, state),
+ });
+}
+
function renderSliceIdColumn(
column: {alias: string, display: SliceIdDisplayConfig},
row: Row): m.Children {
@@ -193,14 +207,8 @@
if (column.display && column.display.type === 'slice_id') {
return renderSliceIdColumn(
{alias: column.alias, display: column.display}, row);
+ } else if (column.display && column.display.type === 'timestamp') {
+ return renderTimestampColumn(column, row, state);
}
- const displayValue = display(column, row);
- const contextMenuItems: m.Child[] = getContextMenuItems(column, row, state);
- return m(
- PopupMenu2,
- {
- trigger: m(Anchor, displayValue),
- },
- ...contextMenuItems,
- );
+ return renderStandardColumn(column, row, state);
}
diff --git a/ui/src/frontend/sql_table/state.ts b/ui/src/frontend/sql_table/state.ts
index 81fd2e7..409d409 100644
--- a/ui/src/frontend/sql_table/state.ts
+++ b/ui/src/frontend/sql_table/state.ts
@@ -18,12 +18,13 @@
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';
+import {SqlTableDescription, startsHidden} from './table_description';
interface ColumnOrderClause {
// We only allow the table to be sorted by the columns which are displayed to
@@ -78,7 +79,7 @@
this.filters = filters || [];
this.columns = [];
for (const column of this.table.columns) {
- if (column.startsHidden) continue;
+ if (startsHidden(column)) continue;
this.columns.push(columnFromSqlTableColumn(column));
}
this.orderBy = [];
diff --git a/ui/src/frontend/sql_table/tab.ts b/ui/src/frontend/sql_table/tab.ts
index 177ce43..bc3318f 100644
--- a/ui/src/frontend/sql_table/tab.ts
+++ b/ui/src/frontend/sql_table/tab.ts
@@ -32,7 +32,7 @@
}
export class SqlTableTab extends BottomTab<SqlTableTabConfig> {
- static readonly kind = 'org.perfetto.SqlTableTab';
+ static readonly kind = 'dev.perfetto.SqlTableTab';
private state: SqlTableState;
diff --git a/ui/src/frontend/sql_table/table_description.ts b/ui/src/frontend/sql_table/table_description.ts
index b3fa312..1aef50b 100644
--- a/ui/src/frontend/sql_table/table_description.ts
+++ b/ui/src/frontend/sql_table/table_description.ts
@@ -21,7 +21,7 @@
// additional filtering.
export type DisplayConfig =
- SliceIdDisplayConfig|Timestamp|Duration|ThreadDuration|ArgSetId;
+ SliceIdDisplayConfig|Timestamp|Duration|ThreadDuration;
// Common properties for all columns.
interface SqlTableColumnBase {
@@ -29,8 +29,6 @@
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 {
@@ -41,10 +39,17 @@
// Special rendering instructions for this column, including the list
// of additional columns required for the rendering.
display?: DisplayConfig;
+ // Whether the column should be hidden by default.
+ startsHidden?: boolean;
}
export type SqlTableColumn = RegularSqlTableColumn|ArgSetIdColumn;
+export function startsHidden(c: SqlTableColumn): boolean {
+ if (isArgSetIdColumn(c)) return true;
+ return c.startsHidden ?? false;
+}
+
export function isArgSetIdColumn(c: SqlTableColumn): c is ArgSetIdColumn {
return (c as {type?: string}).type === 'arg_set_id';
}
@@ -89,9 +94,3 @@
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
index 4b503a4..0af9690 100644
--- a/ui/src/frontend/sql_table/well_known_tables.ts
+++ b/ui/src/frontend/sql_table/well_known_tables.ts
@@ -102,9 +102,7 @@
{
name: 'arg_set_id',
title: 'Arg',
- display: {
- type: 'arg_set_id',
- },
+ type: 'arg_set_id',
},
],
};
diff --git a/ui/src/frontend/thread_and_process_info.ts b/ui/src/frontend/thread_and_process_info.ts
index 46ab39e..a255902 100644
--- a/ui/src/frontend/thread_and_process_info.ts
+++ b/ui/src/frontend/thread_and_process_info.ts
@@ -119,3 +119,11 @@
export function getThreadName(info?: ThreadInfo): string|undefined {
return getDisplayName(info?.name, info?.tid);
}
+
+// Return the full thread name, including the process name.
+export function getFullThreadName(info?: ThreadInfo): string|undefined {
+ if (info?.process === undefined) {
+ return getThreadName(info);
+ }
+ return `${getThreadName(info)} ${getProcessName(info.process)}`;
+}
diff --git a/ui/src/frontend/thread_state.ts b/ui/src/frontend/thread_state.ts
index a4a71a1..e2355d6 100644
--- a/ui/src/frontend/thread_state.ts
+++ b/ui/src/frontend/thread_state.ts
@@ -22,14 +22,17 @@
TPDuration,
TPTime,
} from '../common/time';
-import {Anchor} from './anchor';
+import {Anchor} from './anchor';
import {globals} from './globals';
import {scrollToTrackAndTs} from './scroll_helper';
+import {Icons} from './semantic_icons';
import {
+ asTPTimestamp,
asUtid,
SchedSqlId,
ThreadStateSqlId,
+ TPTimestamp,
Utid,
} from './sql_types';
import {
@@ -50,7 +53,7 @@
// Id of the corresponding entry in the |sched| table.
schedSqlId?: SchedSqlId;
// Timestamp of the beginning of this thread state in nanoseconds.
- ts: TPTime;
+ ts: TPTimestamp;
// Duration of this thread state in nanoseconds.
dur: TPDuration;
// CPU id if this thread state corresponds to a thread running on the CPU.
@@ -109,7 +112,7 @@
result.push({
threadStateSqlId: it.threadStateSqlId as ThreadStateSqlId,
schedSqlId: fromNumNull(it.schedSqlId) as (SchedSqlId | undefined),
- ts: it.ts,
+ ts: asTPTimestamp(it.ts),
dur: it.dur,
cpu: fromNumNull(it.cpu),
state: translateState(it.state || undefined, ioWait),
@@ -153,7 +156,7 @@
interface ThreadStateRefAttrs {
id: ThreadStateSqlId;
- ts: TPTime;
+ ts: TPTimestamp;
dur: TPDuration;
utid: Utid;
// If not present, a placeholder name will be used.
@@ -165,7 +168,7 @@
return m(
Anchor,
{
- icon: 'open_in_new',
+ icon: Icons.UpdateSelection,
onclick: () => {
let trackId: string|number|undefined;
for (const track of Object.values(globals.state.tracks)) {
diff --git a/ui/src/frontend/thread_state_tab.ts b/ui/src/frontend/thread_state_tab.ts
index c4a7295..3d38cc5 100644
--- a/ui/src/frontend/thread_state_tab.ts
+++ b/ui/src/frontend/thread_state_tab.ts
@@ -14,18 +14,25 @@
import m from 'mithril';
-import {TPTime} from '../common/time';
+import {formatDurationShort, TPTime} from '../common/time';
import {Anchor} from './anchor';
import {BottomTab, bottomTabRegistry, NewBottomTabArgs} from './bottom_tab';
import {globals} from './globals';
import {asTPTimestamp, SchedSqlId, ThreadStateSqlId} from './sql_types';
import {
+ getFullThreadName,
getProcessName,
getThreadName,
ThreadInfo,
} from './thread_and_process_info';
-import {getThreadState, goToSchedSlice, ThreadState} from './thread_state';
+import {
+ getThreadState,
+ getThreadStateFromConstraints,
+ goToSchedSlice,
+ ThreadState,
+ ThreadStateRef,
+} from './thread_state';
import {DetailsShell} from './widgets/details_shell';
import {Duration} from './widgets/duration';
import {GridLayout} from './widgets/grid_layout';
@@ -39,10 +46,18 @@
readonly id: ThreadStateSqlId;
}
+interface RelatedThreadStates {
+ prev?: ThreadState;
+ next?: ThreadState;
+ waker?: ThreadState;
+ wakee?: ThreadState[];
+}
+
export class ThreadStateTab extends BottomTab<ThreadStateTabConfig> {
- static readonly kind = 'org.perfetto.ThreadStateTab';
+ static readonly kind = 'dev.perfetto.ThreadStateTab';
state?: ThreadState;
+ relatedStates?: RelatedThreadStates;
loaded: boolean = false;
static create(args: NewBottomTabArgs): ThreadStateTab {
@@ -52,13 +67,55 @@
constructor(args: NewBottomTabArgs) {
super(args);
- getThreadState(this.engine, this.config.id).then((state?: ThreadState) => {
+ this.load().then(() => {
this.loaded = true;
- this.state = state;
globals.rafScheduler.scheduleFullRedraw();
});
}
+ async load() {
+ this.state = await getThreadState(this.engine, this.config.id);
+
+ if (!this.state) {
+ return;
+ }
+
+ const relatedStates: RelatedThreadStates = {};
+ relatedStates.prev = (await getThreadStateFromConstraints(this.engine, {
+ filters: [
+ `ts + dur = ${this.state.ts}`,
+ `utid = ${this.state.thread?.utid}`,
+ ],
+ limit: 1,
+ }))[0];
+ relatedStates.next = (await getThreadStateFromConstraints(this.engine, {
+ filters: [
+ `ts = ${this.state.ts + this.state.dur}`,
+ `utid = ${this.state.thread?.utid}`,
+ ],
+ limit: 1,
+ }))[0];
+ if (this.state.wakerThread?.utid !== undefined) {
+ relatedStates.waker = (await getThreadStateFromConstraints(this.engine, {
+ filters: [
+ `utid = ${this.state.wakerThread?.utid}`,
+ `ts <= ${this.state.ts}`,
+ `ts + dur >= ${this.state.ts}`,
+ ],
+ }))[0];
+ }
+ relatedStates.wakee = await getThreadStateFromConstraints(this.engine, {
+ filters: [
+ `waker_utid = ${this.state.thread?.utid}`,
+ `state = 'R'`,
+ `ts >= ${this.state.ts}`,
+ `ts <= ${this.state.ts + this.state.dur}`,
+ ],
+ });
+
+ this.relatedStates = relatedStates;
+ }
+
getTitle() {
// TODO(altimin): Support dynamic titles here.
return 'Current Selection';
@@ -75,7 +132,10 @@
Section,
{title: 'Details'},
this.state && this.renderTree(this.state),
- )),
+ ),
+ m(Section,
+ {title: 'Related thread states'},
+ this.renderRelatedThreadStates())),
);
}
@@ -154,8 +214,63 @@
);
}
+ private renderRelatedThreadStates(): m.Children {
+ if (this.state === undefined || this.relatedStates === undefined) {
+ return 'Loading';
+ }
+ const startTs = this.state.ts;
+ const renderRef = (state: ThreadState, name?: string) => m(ThreadStateRef, {
+ id: state.threadStateSqlId,
+ ts: state.ts,
+ dur: state.dur,
+ utid: state.thread!.utid,
+ name,
+ });
+
+ const nameForNextOrPrev = (state: ThreadState) =>
+ `${state.state} for ${formatDurationShort(state.dur)}`;
+ return m(
+ Tree,
+ this.relatedStates.waker && m(TreeNode, {
+ left: 'Waker',
+ right: renderRef(
+ this.relatedStates.waker,
+ getFullThreadName(this.relatedStates.waker.thread)),
+ }),
+ this.relatedStates.prev && m(TreeNode, {
+ left: 'Previous state',
+ right: renderRef(
+ this.relatedStates.prev,
+ nameForNextOrPrev(this.relatedStates.prev)),
+ }),
+ this.relatedStates.next && m(TreeNode, {
+ left: 'Next state',
+ right: renderRef(
+ this.relatedStates.next,
+ nameForNextOrPrev(this.relatedStates.next)),
+ }),
+ this.relatedStates.wakee && this.relatedStates.wakee.length > 0 &&
+ m(TreeNode,
+ {
+ left: 'Woken threads',
+ },
+ this.relatedStates.wakee.map(
+ (state) =>
+ m(TreeNode, ({
+ left: m(Timestamp, {
+ ts: state.ts,
+ display: `Start+${
+ formatDurationShort(state.ts - startTs)}`,
+ }),
+ right:
+ renderRef(state, getFullThreadName(state.thread)),
+ })))),
+ );
+ }
+
+
isLoading() {
- return this.state === undefined;
+ return this.state === undefined || this.relatedStates === undefined;
}
renderTabCanvas(): void {}
diff --git a/ui/src/frontend/widgets/timestamp.ts b/ui/src/frontend/widgets/timestamp.ts
index a138f17..91b9b82 100644
--- a/ui/src/frontend/widgets/timestamp.ts
+++ b/ui/src/frontend/widgets/timestamp.ts
@@ -14,12 +14,14 @@
import m from 'mithril';
+import {Actions} from '../../common/actions';
import {Timecode, toDomainTime} from '../../common/time';
+import {Anchor} from '../anchor';
import {copyToClipboard} from '../clipboard';
+import {globals} from '../globals';
import {Icons} from '../semantic_icons';
import {TPTimestamp} from '../sql_types';
-import {Button} from './button';
import {MenuItem, PopupMenu2} from './menu';
// import {MenuItem, PopupMenu2} from './menu';
@@ -28,31 +30,38 @@
// The timestamp to print, this should be the absolute, raw timestamp as
// found in trace processor.
ts: TPTimestamp;
+ // Custom text value to show instead of the default HH:MM:SS.mmm uuu nnn
+ // formatting.
+ display?: m.Children;
+ extraMenuItems?: m.Child[];
}
export class Timestamp implements m.ClassComponent<TimestampAttrs> {
view({attrs}: m.Vnode<TimestampAttrs>) {
const {ts} = attrs;
return m(
- 'span.pf-timecode',
- renderTimecode(ts),
- m(
- PopupMenu2,
- {
- trigger: m(Button, {
- icon: Icons.ContextMenu,
- compact: true,
- minimal: true,
- }),
- },
- m(MenuItem, {
- icon: Icons.Copy,
- label: `Copy raw value`,
- onclick: () => {
- copyToClipboard(ts.toString());
+ PopupMenu2,
+ {
+ trigger: m(
+ Anchor,
+ {
+ onmouseover: () => {
+ globals.dispatch(Actions.setHoverCursorTimestamp({ts}));
+ },
+ onmouseout: () => {
+ globals.dispatch(Actions.setHoverCursorTimestamp({ts: -1n}));
+ },
},
- }),
- ),
+ attrs.display ?? renderTimecode(ts)),
+ },
+ m(MenuItem, {
+ icon: Icons.Copy,
+ label: `Copy raw value`,
+ onclick: () => {
+ copyToClipboard(ts.toString());
+ },
+ }),
+ ...(attrs.extraMenuItems ?? []),
);
}
}
@@ -60,11 +69,12 @@
export function renderTimecode(ts: TPTimestamp): m.Children {
const relTime = toDomainTime(ts);
const {dhhmmss, millis, micros, nanos} = new Timecode(relTime);
- return [
- m('span.pf-timecode-hms', dhhmmss),
- '.',
- m('span.pf-timecode-millis', millis),
- m('span.pf-timecode-micros', micros),
- m('span.pf-timecode-nanos', nanos),
- ];
+ return m(
+ 'span.pf-timecode',
+ m('span.pf-timecode-hms', dhhmmss),
+ '.',
+ m('span.pf-timecode-millis', millis),
+ m('span.pf-timecode-micros', micros),
+ m('span.pf-timecode-nanos', nanos),
+ );
}
diff --git a/ui/src/tracks/debug/details_tab.ts b/ui/src/tracks/debug/details_tab.ts
index 1a743fb..0451cbe 100644
--- a/ui/src/tracks/debug/details_tab.ts
+++ b/ui/src/tracks/debug/details_tab.ts
@@ -87,7 +87,7 @@
export class DebugSliceDetailsTab extends
BottomTab<DebugSliceDetailsTabConfig> {
- static readonly kind = 'org.perfetto.DebugSliceDetailsTab';
+ static readonly kind = 'dev.perfetto.DebugSliceDetailsTab';
data?: {
name: string,