Commit 7bd0e67d authored by Sigurdur Asgeirsson's avatar Sigurdur Asgeirsson Committed by Commit Bot

PM: Push changes to chrome://discards graph view.

This is in preparation for pushing favicons for pages out to the graph,
plus this exposes transient graph nodes reliably.

Change-Id: Iee06bcf2e87c2c28c4119b70a8b4ddb9b05ff943
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1600362
Commit-Queue: Sigurður Ásgeirsson <siggi@chromium.org>
Reviewed-by: default avatarChris Hamilton <chrisha@chromium.org>
Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Reviewed-by: default avatarWill Harris <wfh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#658769}
parent 10107c8f
...@@ -131,7 +131,4 @@ TEST_F(PerformanceManagerTest, CallOnGraph) { ...@@ -131,7 +131,4 @@ TEST_F(PerformanceManagerTest, CallOnGraph) {
performance_manager()->DeleteNode(std::move(page_node)); performance_manager()->DeleteNode(std::move(page_node));
} }
// TODO(siggi): More tests!
// - Test the WebUI interface.
} // namespace performance_manager } // namespace performance_manager
...@@ -15,8 +15,6 @@ import "url/mojom/url.mojom"; ...@@ -15,8 +15,6 @@ import "url/mojom/url.mojom";
struct WebUIPageInfo { struct WebUIPageInfo {
int64 id; int64 id;
int64 main_frame_id;
url.mojom.Url main_frame_url; url.mojom.Url main_frame_url;
// TODO(siggi): Estimate data. // TODO(siggi): Estimate data.
...@@ -27,6 +25,7 @@ struct WebUIFrameInfo { ...@@ -27,6 +25,7 @@ struct WebUIFrameInfo {
int64 id; int64 id;
url.mojom.Url url; url.mojom.Url url;
int64 page_id;
int64 parent_frame_id; int64 parent_frame_id;
int64 process_id; int64 process_id;
}; };
...@@ -40,16 +39,20 @@ struct WebUIProcessInfo { ...@@ -40,16 +39,20 @@ struct WebUIProcessInfo {
uint64 private_footprint_kb; uint64 private_footprint_kb;
}; };
// Represents the momentary state of an entire RC graph. interface WebUIGraphChangeStream {
struct WebUIGraph { FrameCreated(WebUIFrameInfo frame);
array<WebUIPageInfo> pages; PageCreated(WebUIPageInfo pages);
array<WebUIFrameInfo> frames; ProcessCreated(WebUIProcessInfo process);
array<WebUIProcessInfo> processes;
FrameChanged(WebUIFrameInfo frame);
PageChanged(WebUIPageInfo page);
ProcessChanged(WebUIProcessInfo process);
NodeDeleted(int64 node_id);
}; };
// This interface allows grabbing the momentary state of the RC graph for // This interface allows subscribing to a stream of events that track the state
// visualization or inspection. This is exposed on the RC service, and used // of the performance manager graph.
// from the chrome://discards WebUI graph view tab.
interface WebUIGraphDump { interface WebUIGraphDump {
GetCurrentGraph() => (WebUIGraph graph); SubscribeToChanges(WebUIGraphChangeStream change_subscriber);
}; };
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "chrome/browser/performance_manager/graph/graph_impl.h" #include "chrome/browser/performance_manager/graph/graph_impl.h"
#include "chrome/browser/performance_manager/graph/page_node_impl.h" #include "chrome/browser/performance_manager/graph/page_node_impl.h"
#include "chrome/browser/performance_manager/graph/process_node_impl.h" #include "chrome/browser/performance_manager/graph/process_node_impl.h"
#include "chrome/browser/performance_manager/graph/system_node_impl.h"
namespace performance_manager { namespace performance_manager {
...@@ -17,7 +18,13 @@ WebUIGraphDumpImpl::WebUIGraphDumpImpl(GraphImpl* graph) ...@@ -17,7 +18,13 @@ WebUIGraphDumpImpl::WebUIGraphDumpImpl(GraphImpl* graph)
DCHECK(graph); DCHECK(graph);
} }
WebUIGraphDumpImpl::~WebUIGraphDumpImpl() {} WebUIGraphDumpImpl::~WebUIGraphDumpImpl() {
if (change_subscriber_) {
graph_->UnregisterObserver(this);
for (auto* node : graph_->nodes())
node->RemoveObserver(this);
}
}
void WebUIGraphDumpImpl::Bind(mojom::WebUIGraphDumpRequest request, void WebUIGraphDumpImpl::Bind(mojom::WebUIGraphDumpRequest request,
base::OnceClosure error_handler) { base::OnceClosure error_handler) {
...@@ -25,60 +32,176 @@ void WebUIGraphDumpImpl::Bind(mojom::WebUIGraphDumpRequest request, ...@@ -25,60 +32,176 @@ void WebUIGraphDumpImpl::Bind(mojom::WebUIGraphDumpRequest request,
binding_.set_connection_error_handler(std::move(error_handler)); binding_.set_connection_error_handler(std::move(error_handler));
} }
void WebUIGraphDumpImpl::GetCurrentGraph(GetCurrentGraphCallback callback) { namespace {
mojom::WebUIGraphPtr graph = mojom::WebUIGraph::New();
template <typename FunctionType>
void ForFrameAndOffspring(FrameNodeImpl* parent_frame, FunctionType on_frame) {
on_frame(parent_frame);
{ for (FrameNodeImpl* child_frame : parent_frame->child_frame_nodes())
auto processes = graph_->GetAllProcessNodes(); ForFrameAndOffspring(child_frame, on_frame);
graph->processes.reserve(processes.size()); }
for (auto* process : processes) {
mojom::WebUIProcessInfoPtr process_info = mojom::WebUIProcessInfo::New(); } // namespace
process_info->id = NodeBase::GetSerializationId(process); void WebUIGraphDumpImpl::SubscribeToChanges(
process_info->pid = process->process_id(); mojom::WebUIGraphChangeStreamPtr change_subscriber) {
process_info->cumulative_cpu_usage = process->cumulative_cpu_usage(); change_subscriber_ = std::move(change_subscriber);
process_info->private_footprint_kb = process->private_footprint_kb();
graph->processes.push_back(std::move(process_info)); // Send creation notifications for all existing nodes and subscribe to them.
for (ProcessNodeImpl* process_node : graph_->GetAllProcessNodes()) {
SendProcessNotification(process_node, true);
process_node->AddObserver(this);
}
for (PageNodeImpl* page_node : graph_->GetAllPageNodes()) {
SendPageNotification(page_node, true);
page_node->AddObserver(this);
// Dispatch preorder frame notifications.
for (FrameNodeImpl* main_frame_node : page_node->main_frame_nodes()) {
ForFrameAndOffspring(main_frame_node, [this](FrameNodeImpl* frame_node) {
frame_node->AddObserver(this);
this->SendFrameNotification(frame_node, true);
});
} }
} }
{ // Subscribe to future changes to the graph.
auto frames = graph_->GetAllFrameNodes(); graph_->RegisterObserver(this);
graph->frames.reserve(frames.size()); }
for (auto* frame : frames) {
mojom::WebUIFrameInfoPtr frame_info = mojom::WebUIFrameInfo::New(); bool WebUIGraphDumpImpl::ShouldObserve(const NodeBase* node) {
return true;
}
void WebUIGraphDumpImpl::OnNodeAdded(NodeBase* node) {
switch (node->type()) {
case FrameNodeImpl::Type():
SendFrameNotification(FrameNodeImpl::FromNodeBase(node), true);
break;
case PageNodeImpl::Type():
SendPageNotification(PageNodeImpl::FromNodeBase(node), true);
break;
case ProcessNodeImpl::Type():
SendProcessNotification(ProcessNodeImpl::FromNodeBase(node), true);
break;
case SystemNodeImpl::Type():
break;
case NodeTypeEnum::kInvalidType:
break;
}
}
frame_info->id = NodeBase::GetSerializationId(frame); void WebUIGraphDumpImpl::OnBeforeNodeRemoved(NodeBase* node) {
SendDeletionNotification(node);
}
auto* parent_frame = frame->parent_frame_node(); void WebUIGraphDumpImpl::OnIsCurrentChanged(FrameNodeImpl* frame_node) {
frame_info->parent_frame_id = NodeBase::GetSerializationId(parent_frame); SendFrameNotification(frame_node, false);
}
auto* process = frame->process_node(); void WebUIGraphDumpImpl::OnNetworkAlmostIdleChanged(FrameNodeImpl* frame_node) {
frame_info->process_id = NodeBase::GetSerializationId(process); SendFrameNotification(frame_node, false);
}
frame_info->url = frame->url(); void WebUIGraphDumpImpl::OnLifecycleStateChanged(FrameNodeImpl* frame_node) {
SendFrameNotification(frame_node, false);
}
graph->frames.push_back(std::move(frame_info)); void WebUIGraphDumpImpl::OnIsVisibleChanged(PageNodeImpl* page_node) {
} SendPageNotification(page_node, false);
} }
{ void WebUIGraphDumpImpl::OnIsLoadingChanged(PageNodeImpl* page_node) {
auto pages = graph_->GetAllPageNodes(); SendPageNotification(page_node, false);
graph->pages.reserve(pages.size()); }
for (auto* page : pages) {
mojom::WebUIPageInfoPtr page_info = mojom::WebUIPageInfo::New();
page_info->id = NodeBase::GetSerializationId(page); void WebUIGraphDumpImpl::OnUkmSourceIdChanged(PageNodeImpl* page_node) {
page_info->main_frame_url = page->main_frame_url(); SendPageNotification(page_node, false);
}
auto* main_frame = page->GetMainFrameNode(); void WebUIGraphDumpImpl::OnLifecycleStateChanged(PageNodeImpl* page_node) {
page_info->main_frame_id = NodeBase::GetSerializationId(main_frame); SendPageNotification(page_node, false);
}
graph->pages.push_back(std::move(page_info)); void WebUIGraphDumpImpl::OnPageAlmostIdleChanged(PageNodeImpl* page_node) {
} SendPageNotification(page_node, false);
} }
std::move(callback).Run(std::move(graph));
void WebUIGraphDumpImpl::OnMainFrameNavigationCommitted(
PageNodeImpl* page_node) {
SendPageNotification(page_node, false);
}
void WebUIGraphDumpImpl::OnExpectedTaskQueueingDurationSample(
ProcessNodeImpl* process_node) {
SendProcessNotification(process_node, false);
}
void WebUIGraphDumpImpl::OnMainThreadTaskLoadIsLow(
ProcessNodeImpl* process_node) {
SendProcessNotification(process_node, false);
}
void WebUIGraphDumpImpl::OnRendererIsBloated(ProcessNodeImpl* process_node) {
SendProcessNotification(process_node, false);
}
void WebUIGraphDumpImpl::SendFrameNotification(FrameNodeImpl* frame,
bool created) {
// TODO(https://crbug.com/961785): Add more frame properties.
mojom::WebUIFrameInfoPtr frame_info = mojom::WebUIFrameInfo::New();
frame_info->id = NodeBase::GetSerializationId(frame);
auto* parent_frame = frame->parent_frame_node();
frame_info->parent_frame_id = NodeBase::GetSerializationId(parent_frame);
auto* process = frame->process_node();
frame_info->process_id = NodeBase::GetSerializationId(process);
auto* page = frame->page_node();
frame_info->page_id = NodeBase::GetSerializationId(page);
frame_info->url = frame->url();
if (created)
change_subscriber_->FrameCreated(std::move(frame_info));
else
change_subscriber_->FrameChanged(std::move(frame_info));
}
void WebUIGraphDumpImpl::SendPageNotification(PageNodeImpl* page,
bool created) {
// TODO(https://crbug.com/961785): Add more page properties.
mojom::WebUIPageInfoPtr page_info = mojom::WebUIPageInfo::New();
page_info->id = NodeBase::GetSerializationId(page);
page_info->main_frame_url = page->main_frame_url();
if (created)
change_subscriber_->PageCreated(std::move(page_info));
else
change_subscriber_->PageChanged(std::move(page_info));
}
void WebUIGraphDumpImpl::SendProcessNotification(ProcessNodeImpl* process,
bool created) {
// TODO(https://crbug.com/961785): Add more process properties.
mojom::WebUIProcessInfoPtr process_info = mojom::WebUIProcessInfo::New();
process_info->id = NodeBase::GetSerializationId(process);
process_info->pid = process->process_id();
process_info->cumulative_cpu_usage = process->cumulative_cpu_usage();
process_info->private_footprint_kb = process->private_footprint_kb();
if (created)
change_subscriber_->ProcessCreated(std::move(process_info));
else
change_subscriber_->ProcessChanged(std::move(process_info));
}
void WebUIGraphDumpImpl::SendDeletionNotification(NodeBase* node) {
change_subscriber_->NodeDeleted(NodeBase::GetSerializationId(node));
} }
} // namespace performance_manager } // namespace performance_manager
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_WEBUI_GRAPH_DUMP_IMPL_H_ #ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_WEBUI_GRAPH_DUMP_IMPL_H_
#define CHROME_BROWSER_PERFORMANCE_MANAGER_WEBUI_GRAPH_DUMP_IMPL_H_ #define CHROME_BROWSER_PERFORMANCE_MANAGER_WEBUI_GRAPH_DUMP_IMPL_H_
#include "chrome/browser/performance_manager/observers/graph_observer.h"
#include "chrome/browser/performance_manager/webui_graph_dump.mojom.h" #include "chrome/browser/performance_manager/webui_graph_dump.mojom.h"
#include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/binding.h"
...@@ -12,20 +13,60 @@ namespace performance_manager { ...@@ -12,20 +13,60 @@ namespace performance_manager {
class GraphImpl; class GraphImpl;
class WebUIGraphDumpImpl : public mojom::WebUIGraphDump { class WebUIGraphDumpImpl : public mojom::WebUIGraphDump, public GraphObserver {
public: public:
explicit WebUIGraphDumpImpl(GraphImpl* graph); explicit WebUIGraphDumpImpl(GraphImpl* graph);
~WebUIGraphDumpImpl() override; ~WebUIGraphDumpImpl() override;
// WebUIGraphDump implementation.
void GetCurrentGraph(GetCurrentGraphCallback callback) override;
// Bind this instance to |request| with the |error_handler|. // Bind this instance to |request| with the |error_handler|.
void Bind(mojom::WebUIGraphDumpRequest request, void Bind(mojom::WebUIGraphDumpRequest request,
base::OnceClosure error_handler); base::OnceClosure error_handler);
// WebUIGraphDump implementation.
void SubscribeToChanges(
mojom::WebUIGraphChangeStreamPtr change_subscriber) override;
// GraphObserver implementation.
bool ShouldObserve(const NodeBase* node) override;
void OnNodeAdded(NodeBase* node) override;
void OnBeforeNodeRemoved(NodeBase* node) override;
void OnIsCurrentChanged(FrameNodeImpl* frame_node) override;
void OnNetworkAlmostIdleChanged(FrameNodeImpl* frame_node) override;
void OnLifecycleStateChanged(FrameNodeImpl* frame_node) override;
// Event notification.
void OnNonPersistentNotificationCreated(FrameNodeImpl* frame_node) override {}
void OnIsVisibleChanged(PageNodeImpl* page_node) override;
void OnIsLoadingChanged(PageNodeImpl* page_node) override;
void OnUkmSourceIdChanged(PageNodeImpl* page_node) override;
void OnLifecycleStateChanged(PageNodeImpl* page_node) override;
void OnPageAlmostIdleChanged(PageNodeImpl* page_node) override;
// Event notification.
void OnFaviconUpdated(PageNodeImpl* page_node) override {}
// Event notification.
void OnTitleUpdated(PageNodeImpl* page_node) override {}
// Event notification that also implies the main_frame_url changed.
void OnMainFrameNavigationCommitted(PageNodeImpl* page_node) override;
void OnExpectedTaskQueueingDurationSample(
ProcessNodeImpl* process_node) override;
void OnMainThreadTaskLoadIsLow(ProcessNodeImpl* process_node) override;
void OnRendererIsBloated(ProcessNodeImpl* process_node) override;
// Event notification.
void OnAllFramesInProcessFrozen(ProcessNodeImpl* process_node) override {}
private: private:
void SendFrameNotification(FrameNodeImpl* frame, bool created);
void SendPageNotification(PageNodeImpl* page, bool created);
void SendProcessNotification(ProcessNodeImpl* process, bool created);
void SendDeletionNotification(NodeBase* node);
GraphImpl* graph_; GraphImpl* graph_;
// The current change subscriber to this dumper. This instance is subscribed
// to every node in |graph_| save for the system node, so long as there is a
// subscriber.
mojom::WebUIGraphChangeStreamPtr change_subscriber_;
mojo::Binding<mojom::WebUIGraphDump> binding_; mojo::Binding<mojom::WebUIGraphDump> binding_;
DISALLOW_COPY_AND_ASSIGN(WebUIGraphDumpImpl); DISALLOW_COPY_AND_ASSIGN(WebUIGraphDumpImpl);
......
...@@ -4,19 +4,115 @@ ...@@ -4,19 +4,115 @@
#include "chrome/browser/performance_manager/webui_graph_dump_impl.h" #include "chrome/browser/performance_manager/webui_graph_dump_impl.h"
#include <map>
#include <set>
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/test/bind_test_util.h" #include "base/test/bind_test_util.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "chrome/browser/performance_manager/graph/graph_test_harness.h" #include "chrome/browser/performance_manager/graph/graph_test_harness.h"
#include "chrome/browser/performance_manager/graph/mock_graphs.h" #include "chrome/browser/performance_manager/graph/mock_graphs.h"
#include "chrome/browser/performance_manager/graph/page_node_impl.h" #include "chrome/browser/performance_manager/graph/page_node_impl.h"
#include "chrome/browser/performance_manager/performance_manager_clock.h" #include "chrome/browser/performance_manager/performance_manager_clock.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace performance_manager { namespace performance_manager {
namespace {
class TestChangeStream : public mojom::WebUIGraphChangeStream {
public:
using FrameMap = std::map<int64_t, mojom::WebUIFrameInfoPtr>;
using PageMap = std::map<int64_t, mojom::WebUIPageInfoPtr>;
using ProcessMap = std::map<int64_t, mojom::WebUIProcessInfoPtr>;
using IdSet = std::set<int64_t>;
TestChangeStream() : binding_(this) {}
mojom::WebUIGraphChangeStreamPtr GetProxy() {
mojom::WebUIGraphChangeStreamPtr proxy;
binding_.Bind(mojo::MakeRequest(&proxy));
return proxy;
}
// mojom::WebUIGraphChangeStream implementation
void FrameCreated(mojom::WebUIFrameInfoPtr frame) override {
EXPECT_FALSE(HasId(frame->id));
// If the node has a parent frame, we must have heard of it.
EXPECT_TRUE(HasIdIfValid(frame->parent_frame_id));
EXPECT_TRUE(HasId(frame->page_id));
EXPECT_TRUE(HasId(frame->process_id));
id_set_.insert(frame->id);
frame_map_.insert(std::make_pair(frame->id, std::move(frame)));
}
void PageCreated(mojom::WebUIPageInfoPtr page) override {
EXPECT_FALSE(HasId(page->id));
id_set_.insert(page->id);
page_map_.insert(std::make_pair(page->id, std::move(page)));
}
void ProcessCreated(mojom::WebUIProcessInfoPtr process) override {
EXPECT_FALSE(HasId(process->id));
id_set_.insert(process->id);
process_map_.insert(std::make_pair(process->id, std::move(process)));
}
void FrameChanged(mojom::WebUIFrameInfoPtr frame) override {
EXPECT_TRUE(HasId(frame->id));
frame_map_[frame->id] = std::move(frame);
++num_changes_;
}
void PageChanged(mojom::WebUIPageInfoPtr page) override {
EXPECT_TRUE(HasId(page->id));
page_map_[page->id] = std::move(page);
++num_changes_;
}
void ProcessChanged(mojom::WebUIProcessInfoPtr process) override {
EXPECT_TRUE(HasId(process->id));
process_map_[process->id] = std::move(process);
++num_changes_;
}
void NodeDeleted(int64_t node_id) override {
EXPECT_EQ(1u, id_set_.erase(node_id));
size_t erased = frame_map_.erase(node_id) + page_map_.erase(node_id) +
process_map_.erase(node_id);
EXPECT_EQ(1u, erased);
}
const FrameMap& frame_map() const { return frame_map_; }
const PageMap& page_map() const { return page_map_; }
const ProcessMap& process_map() const { return process_map_; }
const IdSet& id_set() const { return id_set_; }
size_t num_changes() const { return num_changes_; }
private:
bool HasId(int64_t id) { return base::ContainsKey(id_set_, id); }
bool HasIdIfValid(int64_t id) { return id == 0u || HasId(id); }
FrameMap frame_map_;
PageMap page_map_;
ProcessMap process_map_;
IdSet id_set_;
size_t num_changes_ = 0;
mojo::Binding<mojom::WebUIGraphChangeStream> binding_;
};
} // namespace
class WebUIGraphDumpImplTest : public GraphTestHarness {}; class WebUIGraphDumpImplTest : public GraphTestHarness {};
TEST_F(WebUIGraphDumpImplTest, Create) { TEST_F(WebUIGraphDumpImplTest, ChangeStream) {
GraphImpl graph; GraphImpl graph;
MockMultiplePagesWithMultipleProcessesGraph mock_graph(&graph); MockMultiplePagesWithMultipleProcessesGraph mock_graph(&graph);
...@@ -30,30 +126,36 @@ TEST_F(WebUIGraphDumpImplTest, Create) { ...@@ -30,30 +126,36 @@ TEST_F(WebUIGraphDumpImplTest, Create) {
main_frame->OnNavigationCommitted(kExampleUrl, /* same_document */ false); main_frame->OnNavigationCommitted(kExampleUrl, /* same_document */ false);
WebUIGraphDumpImpl impl(&graph); WebUIGraphDumpImpl impl(&graph);
// Create a mojo proxy to the impl.
mojom::WebUIGraphDumpPtr impl_proxy;
impl.Bind(mojo::MakeRequest(&impl_proxy), base::OnceClosure());
mojom::WebUIGraphPtr returned_graph; TestChangeStream change_stream;
WebUIGraphDumpImpl::GetCurrentGraphCallback callback = impl_proxy->SubscribeToChanges(change_stream.GetProxy());
base::BindLambdaForTesting([&returned_graph](mojom::WebUIGraphPtr graph) {
returned_graph = std::move(graph);
});
impl.GetCurrentGraph(std::move(callback));
task_env().RunUntilIdle(); task_env().RunUntilIdle();
ASSERT_NE(nullptr, returned_graph.get()); // Validate that the initial graph state dump is complete.
EXPECT_EQ(2u, returned_graph->pages.size()); EXPECT_EQ(0u, change_stream.num_changes());
for (const auto& page : returned_graph->pages) { EXPECT_EQ(7u, change_stream.id_set().size());
EXPECT_NE(0u, page->id);
EXPECT_NE(0u, page->main_frame_id); EXPECT_EQ(2u, change_stream.process_map().size());
for (const auto& kv : change_stream.process_map()) {
EXPECT_NE(0u, kv.second->id);
} }
EXPECT_EQ(3u, returned_graph->frames.size()); EXPECT_EQ(3u, change_stream.frame_map().size());
// Count the top-level frames as we go. // Count the top-level frames as we go.
size_t top_level_frames = 0; size_t top_level_frames = 0;
for (const auto& frame : returned_graph->frames) { for (const auto& kv : change_stream.frame_map()) {
const auto& frame = kv.second;
if (frame->parent_frame_id == 0) { if (frame->parent_frame_id == 0) {
++top_level_frames; ++top_level_frames;
// Top level frames should have a page ID.
EXPECT_NE(0u, frame->page_id);
// The page's main frame should have an URL. // The page's main frame should have an URL.
if (frame->id == NodeBase::GetSerializationId(main_frame)) if (frame->id == NodeBase::GetSerializationId(main_frame))
EXPECT_EQ(kExampleUrl, frame->url); EXPECT_EQ(kExampleUrl, frame->url);
...@@ -61,15 +163,34 @@ TEST_F(WebUIGraphDumpImplTest, Create) { ...@@ -61,15 +163,34 @@ TEST_F(WebUIGraphDumpImplTest, Create) {
EXPECT_NE(0u, frame->id); EXPECT_NE(0u, frame->id);
EXPECT_NE(0u, frame->process_id); EXPECT_NE(0u, frame->process_id);
} }
// Make sure we have one top-level frame per page. // Make sure we have one top-level frame per page.
EXPECT_EQ(returned_graph->pages.size(), top_level_frames); EXPECT_EQ(change_stream.page_map().size(), top_level_frames);
EXPECT_EQ(2u, returned_graph->processes.size()); EXPECT_EQ(2u, change_stream.page_map().size());
for (const auto& page : returned_graph->pages) { for (const auto& kv : change_stream.page_map()) {
const auto& page = kv.second;
EXPECT_NE(0u, page->id); EXPECT_NE(0u, page->id);
EXPECT_NE(0u, page->main_frame_id);
EXPECT_EQ(kExampleUrl, page->main_frame_url); EXPECT_EQ(kExampleUrl, page->main_frame_url);
} }
// Test change notifications.
const GURL kAnotherURL("http://www.google.com/");
mock_graph.page->OnMainFrameNavigationCommitted(now, 1, kAnotherURL);
size_t child_frame_id =
NodeBase::GetSerializationId(mock_graph.child_frame.get());
mock_graph.child_frame.reset();
task_env().RunUntilIdle();
EXPECT_EQ(1u, change_stream.num_changes());
EXPECT_FALSE(base::ContainsKey(change_stream.id_set(), child_frame_id));
const auto main_page_it = change_stream.page_map().find(
NodeBase::GetSerializationId(mock_graph.page.get()));
ASSERT_TRUE(main_page_it != change_stream.page_map().end());
EXPECT_EQ(kAnotherURL, main_page_it->second->main_frame_url);
} }
} // namespace performance_manager } // namespace performance_manager
...@@ -130,11 +130,6 @@ class PageNode extends GraphNode { ...@@ -130,11 +130,6 @@ class PageNode extends GraphNode {
manyBodyStrength() { manyBodyStrength() {
return -600; return -600;
} }
/** override */
linkTargets() {
return [this.page.mainFrameId];
}
} }
class FrameNode extends GraphNode { class FrameNode extends GraphNode {
...@@ -163,7 +158,7 @@ class FrameNode extends GraphNode { ...@@ -163,7 +158,7 @@ class FrameNode extends GraphNode {
/** override */ /** override */
linkTargets() { linkTargets() {
return [this.frame.parentFrameId, this.frame.processId]; return [this.frame.parentFrameId, this.frame.processId, this.frame.pageId];
} }
} }
...@@ -233,6 +228,9 @@ function bounding_force(graph_height) { ...@@ -233,6 +228,9 @@ function bounding_force(graph_height) {
return force; return force;
} }
/**
* @implements {performanceManager.mojom.WebUIGraphChangeStreamInterface}
*/
class Graph { class Graph {
/** /**
* TODO(siggi): This should be SVGElement, but closure doesn't have externs * TODO(siggi): This should be SVGElement, but closure doesn't have externs
...@@ -310,12 +308,104 @@ class Graph { ...@@ -310,12 +308,104 @@ class Graph {
this.nodeGroup_ = svg.append('g').attr('class', 'nodes'); this.nodeGroup_ = svg.append('g').attr('class', 'nodes');
} }
/**
* @param {!performanceManager.mojom.WebUIFrameInfo} frame
*/
frameCreated(frame) {
this.addNode_(new FrameNode(frame));
}
/**
* @param {!performanceManager.mojom.WebUIPageInfo} page
*/
pageCreated(page) {
this.addNode_(new PageNode(page));
}
/**
* @param {!performanceManager.mojom.WebUIProcessInfo} process
*/
processCreated(process) {
this.addNode_(new ProcessNode(process));
}
/**
* @param {!performanceManager.mojom.WebUIFrameInfo} frame
*/
frameChanged(frame) {
const frameNode = /** @type {!FrameNode} */ (this.nodes_.get(frame.id));
frameNode.frame = frame;
}
/**
* @param {!performanceManager.mojom.WebUIPageInfo} page
*/
pageChanged(page) {
const pageNode = /** @type {!PageNode} */ (this.nodes_.get(page.id));
pageNode.page = page;
}
/**
* @param {!performanceManager.mojom.WebUIProcessInfo} process
*/
processChanged(process) {
const processNode =
/** @type {!ProcessNode} */ (this.nodes_.get(process.id));
processNode.process = process;
}
/**
* @param {!number} nodeId
*/
nodeDeleted(nodeId) {
const node = this.nodes_.get(nodeId);
// Filter away any links to or from the deleted node.
this.links_ =
this.links_.filter(link => link.source != node && link.target != node);
// And remove the node.
this.nodes_.delete(nodeId);
}
/** /**
* @param {!Event} event A graph update event posted from the WebUI. * @param {!Event} event A graph update event posted from the WebUI.
* @private * @private
*/ */
onMessage_(event) { onMessage_(event) {
this.onGraphDump_(event.data); const type = /** @type {string} */ (event.data[0]);
const data = /** @type {Object|number} */ (event.data[1]);
switch (type) {
case 'frameCreated':
this.frameCreated(
/** @type {!performanceManager.mojom.WebUIFrameInfo} */ (data));
break;
case 'pageCreated':
this.pageCreated(
/** @type {!performanceManager.mojom.WebUIPageInfo} */ (data));
break;
case 'processCreated':
this.processCreated(
/** @type {!performanceManager.mojom.WebUIProcessInfo} */ (data));
break;
case 'frameChanged':
this.frameChanged(
/** @type {!performanceManager.mojom.WebUIFrameInfo} */ (data));
break;
case 'pageChanged':
this.pageChanged(
/** @type {!performanceManager.mojom.WebUIPageInfo} */ (data));
break;
case 'processChanged':
this.processChanged(
/** @type {!performanceManager.mojom.WebUIProcessInfo} */ (data));
break;
case 'nodeDeleted':
this.nodeDeleted(/** @type {number} */ (data));
break;
}
this.render_();
} }
/** @private */ /** @private */
...@@ -390,66 +480,6 @@ class Graph { ...@@ -390,66 +480,6 @@ class Graph {
.attr('y2', d => d.target.y); .attr('y2', d => d.target.y);
} }
/**
* @param {!Map<number, !GraphNode>} oldNodes
* @param {performanceManager.mojom.WebUIPageInfo} page
* @private
*/
addOrUpdatePage_(oldNodes, page) {
if (!page) {
return;
}
let node = /** @type {?PageNode} */ (oldNodes.get(page.id));
if (node) {
node.page = page;
} else {
node = new PageNode(page);
node.setInitialPosition(this.width_, this.height_);
}
this.nodes_.set(page.id, node);
}
/**
* @param {!Map<number, !GraphNode>} oldNodes
* @param {performanceManager.mojom.WebUIFrameInfo} frame
* @private
*/
addOrUpdateFrame_(oldNodes, frame) {
if (!frame) {
return;
}
let node = /** @type {?FrameNode} */ (oldNodes.get(frame.id));
if (node) {
node.frame = frame;
} else {
node = new FrameNode(frame);
node.setInitialPosition(this.width_, this.height_);
}
this.nodes_.set(frame.id, node);
}
/**
* @param {!Map<number, !GraphNode>} oldNodes
* @param {performanceManager.mojom.WebUIProcessInfo} process
* @private
*/
addOrUpdateProcess_(oldNodes, process) {
if (!process) {
return;
}
let node = /** @type {?ProcessNode} */ (oldNodes.get(process.id));
if (node) {
node.process = process;
} else {
node = new ProcessNode(process);
node.setInitialPosition(this.width_, this.height_);
}
this.nodes_.set(process.id, node);
}
/** /**
* @param {!GraphNode} source * @param {!GraphNode} source
* @param {number} dst_id * @param {number} dst_id
...@@ -463,41 +493,21 @@ class Graph { ...@@ -463,41 +493,21 @@ class Graph {
} }
/** /**
* @param {performanceManager.mojom.WebUIGraph} graph An updated graph from * Adds a new node to the graph, populates its links and gives it an initial
* the WebUI. * position.
*
* @param {!GraphNode} node
* @private * @private
*/ */
onGraphDump_(graph) { addNode_(node) {
// Keep a copy of the current node list, as the new node list will copy this.nodes_.set(node.id, node);
// existing nodes into it.
const oldNodes = this.nodes_;
this.nodes_ = new Map();
for (const page of graph.pages) {
this.addOrUpdatePage_(oldNodes, page);
}
for (const frame of graph.frames) {
this.addOrUpdateFrame_(oldNodes, frame);
}
for (const process of graph.processes) {
this.addOrUpdateProcess_(oldNodes, process);
}
const linkTargets = node.linkTargets();
// Recompute the links, there's no benefit to maintaining the identity for (const linkTarget of linkTargets) {
// of the previous links. this.maybeAddLink_(node, linkTarget);
// TODO(siggi): I'm not sure this is true in general. Edges might cache
// their individual strengths, as a case in point.
this.links_ = [];
const newNodes = this.nodes_.values();
for (const node of newNodes) {
const linkTargets = node.linkTargets();
for (const linkTarget of linkTargets) {
this.maybeAddLink_(node, linkTarget);
}
} }
// TODO(siggi): this is a good place to do initial positioning of new nodes. node.setInitialPosition(this.width_, this.height_);
this.render_();
} }
/** /**
......
...@@ -2,6 +2,79 @@ ...@@ -2,6 +2,79 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
cr.define('graph_tab', function() {
'use strict';
/**
* @implements {performanceManager.mojom.WebUIGraphChangeStreamInterface}
*/
class WebUIGraphChangeStreamImpl {
constructor(contentWindow) {
this.contentWindow_ = contentWindow;
}
/**
* @param {string} type
* @param {Object|number} data
*/
postMessage_(type, data) {
this.contentWindow_.postMessage([type, data], '*');
}
/**
* @param {!performanceManager.mojom.WebUIFrameInfo} frame
*/
frameCreated(frame) {
this.postMessage_('frameCreated', frame);
}
/**
* @param {!performanceManager.mojom.WebUIPageInfo} page
*/
pageCreated(page) {
this.postMessage_('pageCreated', page);
}
/**
* @param {!performanceManager.mojom.WebUIProcessInfo} process
*/
processCreated(process) {
this.postMessage_('processCreated', process);
}
/**
* @param {!performanceManager.mojom.WebUIFrameInfo} frame
*/
frameChanged(frame) {
this.postMessage_('frameChanged', frame);
}
/**
* @param {!performanceManager.mojom.WebUIPageInfo} page
*/
pageChanged(page) {
this.postMessage_('pageChanged', page);
}
/**
* @param {!performanceManager.mojom.WebUIProcessInfo} process
*/
processChanged(process) {
this.postMessage_('processChanged', process);
}
/**
* @param {!number} nodeId
*/
nodeDeleted(nodeId) {
this.postMessage_('nodeDeleted', nodeId);
}
}
return {
WebUIGraphChangeStreamImpl: WebUIGraphChangeStreamImpl,
};
});
Polymer({ Polymer({
is: 'graph-tab', is: 'graph-tab',
...@@ -12,8 +85,12 @@ Polymer({ ...@@ -12,8 +85,12 @@ Polymer({
*/ */
graphDump_: null, graphDump_: null,
/** @private {number} The current update timer if any. */ /**
updateTimer_: 0, * The graph change listener.
*
* @private {performanceManager.mojom.WebUIGraphChangeStreamInterface}
*/
changeListener_: null,
/** @override */ /** @override */
ready: function() { ready: function() {
...@@ -22,25 +99,19 @@ Polymer({ ...@@ -22,25 +99,19 @@ Polymer({
/** @override */ /** @override */
detached: function() { detached: function() {
// Clear the update timer to avoid memory leaks. // TODO(siggi): Is there a way to tear down the binding explicitly?
if (this.updateTimer_) { this.graphDump_ = null;
clearInterval(this.updateTimer_); this.changeListener_ = null;
this.updateTimer_ = 0;
}
}, },
/** @private */ /** @private */
onWebViewReady_: function() { onWebViewReady_: function() {
// Set up regular updates. this.changeListener_ =
this.updateTimer_ = setInterval(() => { new graph_tab.WebUIGraphChangeStreamImpl(this.$.webView.contentWindow);
this.graphDump_.getCurrentGraph().then(response => { const client = new performanceManager.mojom
this.onGraphDump_(response.graph); .WebUIGraphChangeStream(this.changeListener_)
}); .createProxy();
}, 1000); // Subscribe for graph updates.
}, this.graphDump_.subscribeToChanges(client);
/** @private */
onGraphDump_: function(graph) {
this.$.webView.contentWindow.postMessage(graph, '*');
}, },
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment