Merge "tp: migrate RuntimeTableFunction away from SqliteTable" into main
diff --git a/Android.bp b/Android.bp
index 9bc3137..d43bcb7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12852,6 +12852,7 @@
srcs: [
"src/trace_redaction/build_timeline.cc",
"src/trace_redaction/filter_ftrace_using_allowlist.cc",
+ "src/trace_redaction/filter_print_events.cc",
"src/trace_redaction/filter_sched_waking_events.cc",
"src/trace_redaction/find_package_uid.cc",
"src/trace_redaction/optimize_timeline.cc",
@@ -12861,6 +12862,7 @@
"src/trace_redaction/prune_package_list.cc",
"src/trace_redaction/redact_sched_switch.cc",
"src/trace_redaction/scrub_ftrace_events.cc",
+ "src/trace_redaction/scrub_process_stats.cc",
"src/trace_redaction/scrub_process_trees.cc",
"src/trace_redaction/scrub_task_rename.cc",
"src/trace_redaction/scrub_trace_packet.cc",
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index f908e96..e4b35a1 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -32,6 +32,8 @@
"build_timeline.h",
"filter_ftrace_using_allowlist.cc",
"filter_ftrace_using_allowlist.h",
+ "filter_print_events.cc",
+ "filter_print_events.h",
"filter_sched_waking_events.cc",
"filter_sched_waking_events.h",
"find_package_uid.cc",
@@ -50,6 +52,8 @@
"redact_sched_switch.h",
"scrub_ftrace_events.cc",
"scrub_ftrace_events.h",
+ "scrub_process_stats.cc",
+ "scrub_process_stats.h",
"scrub_process_trees.cc",
"scrub_process_trees.h",
"scrub_task_rename.cc",
@@ -82,6 +86,7 @@
"filter_sched_waking_events_integrationtest.cc",
"redact_sched_switch_integrationtest.cc",
"scrub_ftrace_events_integrationtest.cc",
+ "scrub_process_stats_integrationtest.cc",
"scrub_process_trees_integrationtest.cc",
"scrub_task_rename_integrationtest.cc",
"trace_redaction_integration_fixture.cc",
diff --git a/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc b/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc
index c0abf3f..89946ae 100644
--- a/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc
+++ b/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc
@@ -170,6 +170,7 @@
ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kTimestampFieldNumber));
// These are events.
+ ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kPrintFieldNumber));
ASSERT_TRUE(
events.count(protos::pbzero::FtraceEvent::kCpuFrequencyFieldNumber));
ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kCpuIdleFieldNumber));
@@ -197,7 +198,6 @@
// These are events.
ASSERT_FALSE(
events.count(protos::pbzero::FtraceEvent::kOomScoreAdjUpdateFieldNumber));
- ASSERT_FALSE(events.count(protos::pbzero::FtraceEvent::kPrintFieldNumber));
ASSERT_FALSE(
events.count(protos::pbzero::FtraceEvent::kSchedProcessExitFieldNumber));
ASSERT_FALSE(
diff --git a/src/trace_redaction/filter_print_events.cc b/src/trace_redaction/filter_print_events.cc
new file mode 100644
index 0000000..de0c952
--- /dev/null
+++ b/src/trace_redaction/filter_print_events.cc
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_redaction/filter_print_events.h"
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+base::Status FilterPrintEvents::VerifyContext(const Context& context) const {
+ if (!context.package_uid.has_value()) {
+ return base::ErrStatus("FilterPrintEvents: missing packet uid.");
+ }
+
+ if (!context.timeline) {
+ return base::ErrStatus("FilterPrintEvents: missing timeline.");
+ }
+
+ return base::OkStatus();
+}
+
+bool FilterPrintEvents::KeepEvent(const Context& context,
+ protozero::ConstBytes bytes) const {
+ PERFETTO_DCHECK(context.timeline);
+ PERFETTO_DCHECK(context.package_uid.has_value());
+
+ const auto* timeline = context.timeline.get();
+ auto package_uid = context.package_uid;
+
+ protozero::ProtoDecoder event(bytes);
+
+ // This is not a print packet. Keep the packet.
+ if (event.FindField(protos::pbzero::FtraceEvent::kPrintFieldNumber).valid()) {
+ return true;
+ }
+
+ auto time =
+ event.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber);
+ auto pid = event.FindField(protos::pbzero::FtraceEvent::kPidFieldNumber);
+
+ // Pid + Time --> UID, if the uid matches the target package, keep the event.
+ return pid.valid() && time.valid() &&
+ timeline->Search(time.as_uint64(), pid.as_int32()).uid == package_uid;
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/filter_print_events.h b/src/trace_redaction/filter_print_events.h
new file mode 100644
index 0000000..36ef92b
--- /dev/null
+++ b/src/trace_redaction/filter_print_events.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_REDACTION_FILTER_PRINT_EVENTS_H_
+#define SRC_TRACE_REDACTION_FILTER_PRINT_EVENTS_H_
+
+#include "perfetto/protozero/field.h"
+#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+// event {
+// timestamp: 6702093749982230
+// pid: 7947 <-- target
+// print {
+// buf: "B|7105|virtual void
+// swappy::ChoreographerThread::onChoreographer()\n"
+// }
+// }
+//
+// If the target pid doesn't belong to the target package (context.package_uid),
+// then the event will be marked as "don't keep".
+class FilterPrintEvents : public FtraceEventFilter {
+ public:
+ base::Status VerifyContext(const Context& context) const override;
+ bool KeepEvent(const Context& context,
+ protozero::ConstBytes bytes) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_FILTER_PRINT_EVENTS_H_
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index c0061b6..4620ed2 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -18,6 +18,7 @@
#include "perfetto/base/status.h"
#include "src/trace_redaction/build_timeline.h"
#include "src/trace_redaction/filter_ftrace_using_allowlist.h"
+#include "src/trace_redaction/filter_print_events.h"
#include "src/trace_redaction/filter_sched_waking_events.h"
#include "src/trace_redaction/find_package_uid.h"
#include "src/trace_redaction/optimize_timeline.h"
@@ -25,6 +26,7 @@
#include "src/trace_redaction/prune_package_list.h"
#include "src/trace_redaction/redact_sched_switch.h"
#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/scrub_process_stats.h"
#include "src/trace_redaction/scrub_process_trees.h"
#include "src/trace_redaction/scrub_task_rename.h"
#include "src/trace_redaction/scrub_trace_packet.h"
@@ -55,11 +57,13 @@
// number of events they need to iterate over.
auto scrub_ftrace_events = redactor.emplace_transform<ScrubFtraceEvents>();
scrub_ftrace_events->emplace_back<FilterFtraceUsingAllowlist>();
+ scrub_ftrace_events->emplace_back<FilterPrintEvents>();
scrub_ftrace_events->emplace_back<FilterSchedWakingEvents>();
redactor.emplace_transform<ScrubProcessTrees>();
redactor.emplace_transform<ScrubTaskRename>();
redactor.emplace_transform<RedactSchedSwitch>();
+ redactor.emplace_transform<ScrubProcessStats>();
Context context;
context.package_name = package_name;
diff --git a/src/trace_redaction/populate_allow_lists.cc b/src/trace_redaction/populate_allow_lists.cc
index 2ba81b6..7a5b48a 100644
--- a/src/trace_redaction/populate_allow_lists.cc
+++ b/src/trace_redaction/populate_allow_lists.cc
@@ -77,6 +77,7 @@
protos::pbzero::FtraceEvent::kIonBufferDestroyFieldNumber,
protos::pbzero::FtraceEvent::kDmaHeapStatFieldNumber,
protos::pbzero::FtraceEvent::kRssStatThrottledFieldNumber,
+ protos::pbzero::FtraceEvent::kPrintFieldNumber,
};
// TODO: Some ftrace fields should be retained, but they carry too much risk
diff --git a/src/trace_redaction/scrub_process_stats.cc b/src/trace_redaction/scrub_process_stats.cc
new file mode 100644
index 0000000..991c02f
--- /dev/null
+++ b/src/trace_redaction/scrub_process_stats.cc
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_redaction/scrub_process_stats.h"
+
+#include <string>
+
+#include "perfetto/base/status.h"
+#include "perfetto/protozero/field.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/ps/process_stats.pbzero.h"
+#include "src/trace_redaction/proto_util.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+base::Status ScrubProcessStats::Transform(const Context& context,
+ std::string* packet) const {
+ if (!context.package_uid.has_value()) {
+ return base::ErrStatus("FilterProcessStats: missing package uid.");
+ }
+
+ if (!context.timeline) {
+ return base::ErrStatus("FilterProcessStats: missing timeline.");
+ }
+
+ protozero::ProtoDecoder packet_decoder(*packet);
+
+ // Very few packets will have process stats. It's best to avoid
+ // reserialization whenever possible.
+ if (!packet_decoder
+ .FindField(protos::pbzero::TracePacket::kProcessStatsFieldNumber)
+ .valid()) {
+ return base::OkStatus();
+ }
+
+ protozero::HeapBuffered<protos::pbzero::TracePacket> message;
+
+ // TODO(vaage): Add primitive to drop all packets that don't have a
+ // timestamp, allowing all other packets assume there are timestamps.
+ auto time_field = packet_decoder.FindField(
+ protos::pbzero::TracePacket::kTimestampFieldNumber);
+ PERFETTO_DCHECK(time_field.valid());
+ auto time = time_field.as_uint64();
+
+ auto* timeline = context.timeline.get();
+ auto uid = context.package_uid.value();
+
+ for (auto packet_field = packet_decoder.ReadField(); packet_field.valid();
+ packet_field = packet_decoder.ReadField()) {
+ if (packet_field.id() !=
+ protos::pbzero::TracePacket::kProcessStatsFieldNumber) {
+ proto_util::AppendField(packet_field, message.get());
+ continue;
+ }
+
+ auto process_stats = std::move(packet_field);
+ protozero::ProtoDecoder process_stats_decoder(process_stats.as_bytes());
+
+ auto* process_stats_message = message->set_process_stats();
+
+ for (auto process_stats_field = process_stats_decoder.ReadField();
+ process_stats_field.valid();
+ process_stats_field = process_stats_decoder.ReadField()) {
+ bool keep_field;
+
+ if (process_stats_field.id() ==
+ protos::pbzero::ProcessStats::kProcessesFieldNumber) {
+ protozero::ProtoDecoder process_decoder(process_stats_field.as_bytes());
+ auto pid = process_decoder.FindField(
+ protos::pbzero::ProcessStats::Process::kPidFieldNumber);
+ keep_field =
+ pid.valid() && timeline->Search(time, pid.as_int32()).uid == uid;
+ } else {
+ keep_field = true;
+ }
+
+ if (keep_field) {
+ proto_util::AppendField(process_stats_field, process_stats_message);
+ }
+ }
+ }
+
+ packet->assign(message.SerializeAsString());
+
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_process_stats.h b/src/trace_redaction/scrub_process_stats.h
new file mode 100644
index 0000000..99b6697
--- /dev/null
+++ b/src/trace_redaction/scrub_process_stats.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_REDACTION_SCRUB_PROCESS_STATS_H_
+#define SRC_TRACE_REDACTION_SCRUB_PROCESS_STATS_H_
+
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+class ScrubProcessStats : public TransformPrimitive {
+ public:
+ base::Status Transform(const Context& context,
+ std::string* packet) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_SCRUB_PROCESS_STATS_H_
diff --git a/src/trace_redaction/scrub_process_stats_integrationtest.cc b/src/trace_redaction/scrub_process_stats_integrationtest.cc
new file mode 100644
index 0000000..01c61b1
--- /dev/null
+++ b/src/trace_redaction/scrub_process_stats_integrationtest.cc
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdint>
+#include <string>
+
+#include "perfetto/base/status.h"
+#include "src/base/test/status_matchers.h"
+#include "src/trace_redaction/build_timeline.h"
+#include "src/trace_redaction/optimize_timeline.h"
+#include "src/trace_redaction/scrub_process_stats.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+#include "src/trace_redaction/trace_redaction_integration_fixture.h"
+#include "src/trace_redaction/trace_redactor.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/ps/process_stats.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+class ScrubProcessStatsTest : public testing::Test,
+ protected TraceRedactionIntegrationFixure {
+ protected:
+ void SetUp() override {
+ trace_redactor()->emplace_collect<BuildTimeline>();
+ trace_redactor()->emplace_build<OptimizeTimeline>();
+ trace_redactor()->emplace_transform<ScrubProcessStats>();
+
+ // Package "com.Unity.com.unity.multiplayer.samples.coop";
+ context()->package_uid = 10252;
+ }
+
+ // Gets pids from all process_stats messages in the trace (bytes).
+ base::FlatSet<int32_t> GetAllPids(const std::string& bytes) const {
+ base::FlatSet<int32_t> pids;
+
+ protos::pbzero::Trace::Decoder decoder(bytes);
+
+ for (auto packet = decoder.packet(); packet; ++packet) {
+ protos::pbzero::TracePacket::Decoder trace_packet(packet->as_bytes());
+
+ if (!trace_packet.has_process_stats()) {
+ continue;
+ }
+
+ protos::pbzero::ProcessStats::Decoder process_stats(
+ trace_packet.process_stats());
+
+ for (auto process = process_stats.processes(); process; ++process) {
+ protos::pbzero::ProcessStats::Process::Decoder p(process->as_bytes());
+ PERFETTO_DCHECK(p.has_pid());
+ pids.insert(p.pid());
+ }
+ }
+
+ return pids;
+ }
+};
+
+// This test is a canary for changes to the test data. If the test data was to
+// change, every test in this file would fail.
+//
+// SELECT DISTINCT pid
+// FROM process
+// WHERE upid IN (
+// SELECT DISTINCT upid
+// FROM counter
+// JOIN process_counter_track ON counter.track_id=process_counter_track.id
+// WHERE name!='oom_score_adj'
+// )
+// ORDER BY pid
+//
+// NOTE: WHERE name!='oom_score_adj' is used because there are two sources for
+// oom_score_adj values and we only want process stats here.
+TEST_F(ScrubProcessStatsTest, VerifyTraceStats) {
+ base::FlatSet<int32_t> expected = {
+ 1, 578, 581, 696, 697, 698, 699, 700, 701, 704,
+ 709, 710, 718, 728, 749, 750, 751, 752, 756, 760,
+ 761, 762, 873, 874, 892, 1046, 1047, 1073, 1074, 1091,
+ 1092, 1093, 1101, 1103, 1104, 1105, 1106, 1107, 1110, 1111,
+ 1112, 1113, 1115, 1116, 1118, 1119, 1120, 1121, 1123, 1124,
+ 1125, 1126, 1127, 1129, 1130, 1131, 1133, 1140, 1145, 1146,
+ 1147, 1151, 1159, 1163, 1164, 1165, 1166, 1167, 1168, 1175,
+ 1177, 1205, 1206, 1235, 1237, 1238, 1248, 1251, 1254, 1255,
+ 1295, 1296, 1298, 1300, 1301, 1303, 1304, 1312, 1317, 1325,
+ 1339, 1340, 1363, 1374, 1379, 1383, 1388, 1392, 1408, 1409,
+ 1410, 1413, 1422, 1426, 1427, 1428, 1429, 1433, 1436, 1448,
+ 1450, 1451, 1744, 1774, 1781, 1814, 2262, 2268, 2286, 2392,
+ 2456, 2502, 2510, 2518, 2528, 2569, 3171, 3195, 3262, 3286,
+ 3310, 3338, 3442, 3955, 4386, 4759, 5935, 6034, 6062, 6167,
+ 6547, 6573, 6720, 6721, 6725, 6944, 6984, 7105, 7207, 7557,
+ 7636, 7786, 7874, 7958, 7960, 7967, 15449, 15685, 15697, 16453,
+ 19683, 21124, 21839, 23150, 23307, 23876, 24317, 25017, 25126, 25450,
+ 25474, 27271, 30604, 32289,
+ };
+
+ auto original = LoadOriginal();
+ ASSERT_OK(original) << original.status().c_message();
+
+ auto actual = GetAllPids(*original);
+
+ for (auto pid : expected) {
+ ASSERT_TRUE(actual.count(pid))
+ << "pid " << pid << " was not found in the trace";
+ }
+
+ for (auto pid : actual) {
+ ASSERT_TRUE(expected.count(pid))
+ << "pid " << pid << " was found in the trace";
+ }
+}
+
+// Package name: "com.Unity.com.unity.multiplayer.samples.coop"
+// Package pid: 7105
+TEST_F(ScrubProcessStatsTest, OnlyKeepsStatsForPackage) {
+ auto result = Redact();
+ ASSERT_OK(result) << result.c_message();
+
+ auto redacted = LoadRedacted();
+ ASSERT_OK(redacted) << redacted.status().c_message();
+
+ auto actual = GetAllPids(*redacted);
+ ASSERT_EQ(actual.size(), 1u);
+ ASSERT_TRUE(actual.count(7105));
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/ui/src/assets/details.scss b/ui/src/assets/details.scss
index f3e529a..d09690a 100644
--- a/ui/src/assets/details.scss
+++ b/ui/src/assets/details.scss
@@ -173,43 +173,6 @@
width: 50%;
}
}
- &.flamegraph-profile {
- display: flex;
- justify-content: space-between;
- align-content: center;
- height: 30px;
- padding: 0;
- font-size: 12px;
- * {
- align-self: center;
- }
- .options {
- display: inline-flex;
- justify-content: space-around;
- }
- .details {
- display: inline-flex;
- justify-content: flex-end;
- }
- .title {
- justify-self: start;
- margin-left: 5px;
- font-size: 14px;
- margin-right: 10px;
- }
- .time {
- justify-self: end;
- margin-right: 10px;
- }
- .selected {
- justify-self: end;
- margin-right: 10px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- width: 200px;
- }
- }
}
table {
@@ -709,3 +672,35 @@
.pf-noselection {
height: 100%;
}
+
+.flamegraph-profile {
+ height: 100%;
+ // This is required to position locally-scoped (i.e. non-full-screen) modal
+ // dialogs within the panel, as they use position: absolute.
+ position: relative;
+
+ .time {
+ justify-self: end;
+ margin-right: 10px;
+ }
+ .selected {
+ justify-self: end;
+ margin-right: 10px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 200px;
+ }
+ .flamegraph-content {
+ overflow: auto;
+ height: 100%;
+
+ .loading-container {
+ font-size: larger;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ }
+ }
+}
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index a10252c..b1c00a4 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -300,6 +300,7 @@
await this.args.engine.query(`select value from stats
where severity = 'error' and name = 'heap_graph_non_finalized_graph'`)
).firstRow({value: NUM}).value > 0;
+ flamegraphDetails.graphLoading = false;
publishFlamegraphDetails(flamegraphDetails);
}
@@ -317,8 +318,10 @@
if (this.flamegraphDatasets.has(key)) {
currentData = this.flamegraphDatasets.get(key)!;
} else {
- // TODO(b/330703412): Show loading state.
-
+ publishFlamegraphDetails({
+ ...globals.flamegraphDetails,
+ graphLoading: true,
+ });
// Collecting data for drawing flamegraph for selected profile.
// Data needs to be in following format:
// id, name, parent_id, depth, total_size
diff --git a/ui/src/frontend/flamegraph_panel.ts b/ui/src/frontend/flamegraph_panel.ts
index fea7fb2..0f4cba2 100644
--- a/ui/src/frontend/flamegraph_panel.ts
+++ b/ui/src/frontend/flamegraph_panel.ts
@@ -30,6 +30,8 @@
import {Icon} from '../widgets/icon';
import {Modal, ModalAttrs} from '../widgets/modal';
import {Popup} from '../widgets/popup';
+import {EmptyState} from '../widgets/empty_state';
+import {Spinner} from '../widgets/spinner';
import {Flamegraph, NodeRendering} from './flamegraph';
import {globals} from './globals';
@@ -37,7 +39,9 @@
import {Router} from './router';
import {getCurrentTrace} from './sidebar';
import {convertTraceToPprofAndDownload} from './trace_converter';
+import {ButtonBar} from '../widgets/button';
import {DurationWidget} from './widgets/duration';
+import {DetailsShell} from '../widgets/details_shell';
const HEADER_HEIGHT = 30;
@@ -90,33 +94,31 @@
? this.flamegraph.getHeight() + HEADER_HEIGHT
: 0;
return m(
- '.details-panel',
+ '.flamegraph-profile',
this.maybeShowModal(flamegraphDetails.graphIncomplete),
m(
- '.details-panel-heading.flamegraph-profile',
- {onclick: (e: MouseEvent) => e.stopPropagation()},
- [
- m('div.options', [
- m(
- 'div.title',
- this.getTitle(),
- this.profileType === ProfileType.MIXED_HEAP_PROFILE &&
+ DetailsShell,
+ {
+ fillParent: true,
+ title: m(
+ 'div.title',
+ this.getTitle(),
+ this.profileType === ProfileType.MIXED_HEAP_PROFILE &&
+ m(
+ Popup,
+ {
+ trigger: m(Icon, {icon: 'warning'}),
+ },
m(
- Popup,
- {
- trigger: m(Icon, {icon: 'warning'}),
- },
- m(
- '',
- {style: {width: '300px'}},
- 'This is a mixed java/native heap profile, free()s are not visualized. To visualize free()s, remove "all_heaps: true" from the config.',
- ),
+ '',
+ {style: {width: '300px'}},
+ 'This is a mixed java/native heap profile, free()s are not visualized. To visualize free()s, remove "all_heaps: true" from the config.',
),
- ':',
- ),
- this.getViewingOptionButtons(),
- ]),
- m('div.details', [
+ ),
+ ':',
+ ),
+ description: this.getViewingOptionButtons(),
+ buttons: [
m(
'div.selected',
`Selected function: ${toSelectedCallsite(
@@ -145,23 +147,39 @@
this.downloadPprof();
},
}),
- ]),
- ],
+ ],
+ },
+ m(
+ '.flamegraph-content',
+ flamegraphDetails.graphLoading
+ ? m(
+ '.loading-container',
+ m(
+ EmptyState,
+ {
+ icon: 'bar_chart',
+ title: 'Computing graph ...',
+ className: 'flamegraph-loading',
+ },
+ m(Spinner, {easing: true}),
+ ),
+ )
+ : m(`canvas[ref=canvas]`, {
+ style: `height:${height}px; width:100%`,
+ onmousemove: (e: MouseEvent) => {
+ const {offsetX, offsetY} = e;
+ this.onMouseMove({x: offsetX, y: offsetY});
+ },
+ onmouseout: () => {
+ this.onMouseOut();
+ },
+ onclick: (e: MouseEvent) => {
+ const {offsetX, offsetY} = e;
+ this.onMouseClick({x: offsetX, y: offsetY});
+ },
+ }),
+ ),
),
- m(`canvas[ref=canvas]`, {
- style: `height:${height}px; width:100%`,
- onmousemove: (e: MouseEvent) => {
- const {offsetX, offsetY} = e;
- this.onMouseMove({x: offsetX, y: offsetY});
- },
- onmouseout: () => {
- this.onMouseOut();
- },
- onclick: (e: MouseEvent) => {
- const {offsetX, offsetY} = e;
- this.onMouseClick({x: offsetX, y: offsetY});
- },
- }),
);
} else {
return m(
@@ -260,7 +278,7 @@
getViewingOptionButtons(): m.Children {
return m(
- 'div',
+ ButtonBar,
...FlamegraphDetailsPanel.selectViewingOptions(
assertExists(this.profileType),
),
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 1af1399..5cecd7e 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -160,6 +160,8 @@
// When heap_graph_non_finalized_graph has a count >0, we mark the graph
// as incomplete.
graphIncomplete?: boolean;
+ // About to show a new graph whose data is not ready yet.
+ graphLoading?: boolean;
}
export interface CpuProfileDetails {