blob: f6cbd1f2821a69f7ac6a1b40b21a6daa6245a06a [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/webui/discards/graph_dump_impl.h"
#include <memory>
#include <utility>
#include "base/base64.h"
#include "base/bind.h"
#include "base/json/json_string_value_serializer.h"
#include "base/macros.h"
#include "base/task/cancelable_task_tracker.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/discards/discards.mojom.h"
#include "components/favicon/core/favicon_service.h"
#include "components/favicon_base/favicon_callback.h"
#include "components/performance_manager/public/graph/graph.h"
#include "components/performance_manager/public/graph/node_data_describer.h"
#include "components/performance_manager/public/graph/node_data_describer_registry.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/public/web_contents_proxy.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
namespace {
// Best effort convert |value| to a string.
std::string ToJSON(const base::Value& value) {
std::string result;
JSONStringValueSerializer serializer(&result);
if (serializer.Serialize(value))
return result;
return std::string();
}
} // namespace
class DiscardsGraphDumpImpl::FaviconRequestHelper {
public:
FaviconRequestHelper(base::WeakPtr<DiscardsGraphDumpImpl> graph_dump,
scoped_refptr<base::SequencedTaskRunner> task_runner);
void RequestFavicon(GURL page_url,
performance_manager::WebContentsProxy contents_proxy,
int64_t serialization_id);
void FaviconDataAvailable(int64_t serialization_id,
const favicon_base::FaviconRawBitmapResult& result);
private:
std::unique_ptr<base::CancelableTaskTracker> cancelable_task_tracker_;
base::WeakPtr<DiscardsGraphDumpImpl> graph_dump_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(FaviconRequestHelper);
};
DiscardsGraphDumpImpl::FaviconRequestHelper::FaviconRequestHelper(
base::WeakPtr<DiscardsGraphDumpImpl> graph_dump,
scoped_refptr<base::SequencedTaskRunner> task_runner)
: graph_dump_(graph_dump), task_runner_(task_runner) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
void DiscardsGraphDumpImpl::FaviconRequestHelper::RequestFavicon(
GURL page_url,
performance_manager::WebContentsProxy contents_proxy,
int64_t serialization_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
content::WebContents* web_contents = contents_proxy.Get();
if (!web_contents)
return;
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
if (!profile)
return;
favicon::FaviconService* favicon_service =
FaviconServiceFactory::GetForProfile(profile,
ServiceAccessType::EXPLICIT_ACCESS);
if (!favicon_service)
return;
if (!cancelable_task_tracker_)
cancelable_task_tracker_ = std::make_unique<base::CancelableTaskTracker>();
constexpr size_t kIconSize = 16;
constexpr bool kFallbackToHost = true;
// It's safe to pass this unretained here, as the tasks are cancelled
// on deletion of the cancelable task tracker.
favicon_service->GetRawFaviconForPageURL(
page_url, {favicon_base::IconType::kFavicon}, kIconSize, kFallbackToHost,
base::BindOnce(&FaviconRequestHelper::FaviconDataAvailable,
base::Unretained(this), serialization_id),
cancelable_task_tracker_.get());
}
void DiscardsGraphDumpImpl::FaviconRequestHelper::FaviconDataAvailable(
int64_t serialization_id,
const favicon_base::FaviconRawBitmapResult& result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!result.is_valid())
return;
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&DiscardsGraphDumpImpl::SendFaviconNotification,
graph_dump_, serialization_id, result.bitmap_data));
}
DiscardsGraphDumpImpl::DiscardsGraphDumpImpl() {}
DiscardsGraphDumpImpl::~DiscardsGraphDumpImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!graph_);
DCHECK(!change_subscriber_);
DCHECK(!favicon_request_helper_);
}
// static
void DiscardsGraphDumpImpl::CreateAndBind(
mojo::PendingReceiver<discards::mojom::GraphDump> receiver,
performance_manager::Graph* graph) {
std::unique_ptr<DiscardsGraphDumpImpl> dump =
std::make_unique<DiscardsGraphDumpImpl>();
dump->BindWithGraph(graph, std::move(receiver));
graph->PassToGraph(std::move(dump));
}
void DiscardsGraphDumpImpl::BindWithGraph(
performance_manager::Graph* graph,
mojo::PendingReceiver<discards::mojom::GraphDump> receiver) {
receiver_.Bind(std::move(receiver));
receiver_.set_disconnect_handler(base::BindOnce(
&DiscardsGraphDumpImpl::OnConnectionError, base::Unretained(this)));
}
int64_t DiscardsGraphDumpImpl::GetNodeIdForTesting(
const performance_manager::Node* node) {
return GetNodeId(node);
}
namespace {
template <typename FunctionType>
void ForFrameAndOffspring(const performance_manager::FrameNode* parent_frame,
FunctionType on_frame) {
on_frame(parent_frame);
for (const performance_manager::FrameNode* child_frame :
parent_frame->GetChildFrameNodes())
ForFrameAndOffspring(child_frame, on_frame);
}
} // namespace
void DiscardsGraphDumpImpl::SubscribeToChanges(
mojo::PendingRemote<discards::mojom::GraphChangeStream> change_subscriber) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
change_subscriber_.Bind(std::move(change_subscriber));
// Give all existing nodes an ID.
for (const performance_manager::FrameNode* frame_node :
graph_->GetAllFrameNodes()) {
AddNode(frame_node);
}
for (const performance_manager::PageNode* page_node :
graph_->GetAllPageNodes()) {
AddNode(page_node);
}
for (const performance_manager::ProcessNode* process_node :
graph_->GetAllProcessNodes()) {
AddNode(process_node);
}
for (const performance_manager::WorkerNode* worker_node :
graph_->GetAllWorkerNodes()) {
AddNode(worker_node);
}
// Send creation notifications for all existing nodes.
SendNotificationToAllNodes(/* created = */ true);
// It is entirely possible for there to be circular link references between
// nodes that already existed at the point this object was created (the loop
// was closed after the two nodes themselves were created). We don't have the
// exact order of historical events that led to the current graph state, so we
// simply fire off a node changed notification for all nodes after the node
// creation. This ensures that all targets exist the second time through, and
// any loops are closed. Afterwards any newly created loops will be properly
// maintained as node creation/destruction/link events will be fed to the
// graph in the proper order.
SendNotificationToAllNodes(/* created = */ false);
// Subscribe to subsequent notifications.
graph_->AddFrameNodeObserver(this);
graph_->AddPageNodeObserver(this);
graph_->AddProcessNodeObserver(this);
graph_->AddWorkerNodeObserver(this);
}
void DiscardsGraphDumpImpl::RequestNodeDescriptions(
const std::vector<int64_t>& node_ids,
RequestNodeDescriptionsCallback callback) {
base::flat_map<int64_t, std::string> descriptions;
performance_manager::NodeDataDescriberRegistry* describer_registry =
graph_->GetNodeDataDescriberRegistry();
for (int64_t node_id : node_ids) {
auto it = nodes_by_id_.find(NodeId::FromUnsafeValue(node_id));
// The requested node may have been removed by the time the request arrives,
// in which case no description is returned for that node ID.
if (it != nodes_by_id_.end()) {
descriptions[node_id] =
ToJSON(describer_registry->DescribeNodeData(it->second));
}
}
std::move(callback).Run(descriptions);
}
void DiscardsGraphDumpImpl::OnPassedToGraph(performance_manager::Graph* graph) {
DCHECK(!graph_);
graph_ = graph;
}
void DiscardsGraphDumpImpl::OnTakenFromGraph(
performance_manager::Graph* graph) {
DCHECK_EQ(graph_, graph);
if (change_subscriber_) {
graph_->RemoveFrameNodeObserver(this);
graph_->RemovePageNodeObserver(this);
graph_->RemoveProcessNodeObserver(this);
graph_->RemoveWorkerNodeObserver(this);
}
change_subscriber_.reset();
// The favicon helper must be deleted on the UI thread.
if (favicon_request_helper_) {
content::GetUIThreadTaskRunner({})->DeleteSoon(
FROM_HERE, std::move(favicon_request_helper_));
}
graph_ = nullptr;
}
void DiscardsGraphDumpImpl::OnFrameNodeAdded(
const performance_manager::FrameNode* frame_node) {
AddNode(frame_node);
SendFrameNotification(frame_node, true);
StartFrameFaviconRequest(frame_node);
}
void DiscardsGraphDumpImpl::OnBeforeFrameNodeRemoved(
const performance_manager::FrameNode* frame_node) {
SendDeletionNotification(frame_node);
RemoveNode(frame_node);
}
void DiscardsGraphDumpImpl::OnURLChanged(
const performance_manager::FrameNode* frame_node,
const GURL& previous_value) {
SendFrameNotification(frame_node, false);
StartFrameFaviconRequest(frame_node);
}
void DiscardsGraphDumpImpl::OnPageNodeAdded(
const performance_manager::PageNode* page_node) {
AddNode(page_node);
SendPageNotification(page_node, true);
StartPageFaviconRequest(page_node);
}
void DiscardsGraphDumpImpl::OnBeforePageNodeRemoved(
const performance_manager::PageNode* page_node) {
SendDeletionNotification(page_node);
RemoveNode(page_node);
}
void DiscardsGraphDumpImpl::OnEmbedderFrameNodeChanged(
const performance_manager::PageNode* page_node,
const performance_manager::FrameNode*,
EmbeddingType) {
DCHECK(HasNode(page_node));
SendPageNotification(page_node, false);
}
void DiscardsGraphDumpImpl::OnFaviconUpdated(
const performance_manager::PageNode* page_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
StartPageFaviconRequest(page_node);
}
void DiscardsGraphDumpImpl::OnMainFrameUrlChanged(
const performance_manager::PageNode* page_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SendPageNotification(page_node, false);
}
void DiscardsGraphDumpImpl::OnProcessNodeAdded(
const performance_manager::ProcessNode* process_node) {
AddNode(process_node);
SendProcessNotification(process_node, true);
}
void DiscardsGraphDumpImpl::OnProcessLifetimeChange(
const performance_manager::ProcessNode* process_node) {
SendProcessNotification(process_node, false);
}
void DiscardsGraphDumpImpl::OnBeforeProcessNodeRemoved(
const performance_manager::ProcessNode* process_node) {
SendDeletionNotification(process_node);
RemoveNode(process_node);
}
void DiscardsGraphDumpImpl::OnWorkerNodeAdded(
const performance_manager::WorkerNode* worker_node) {
AddNode(worker_node);
SendWorkerNotification(worker_node, true);
}
void DiscardsGraphDumpImpl::OnBeforeWorkerNodeRemoved(
const performance_manager::WorkerNode* worker_node) {
SendDeletionNotification(worker_node);
RemoveNode(worker_node);
}
void DiscardsGraphDumpImpl::OnFinalResponseURLDetermined(
const performance_manager::WorkerNode* worker_node) {
SendWorkerNotification(worker_node, false);
}
void DiscardsGraphDumpImpl::OnClientFrameAdded(
const performance_manager::WorkerNode* worker_node,
const performance_manager::FrameNode* client_frame_node) {
SendWorkerNotification(worker_node, false);
}
void DiscardsGraphDumpImpl::OnBeforeClientFrameRemoved(
const performance_manager::WorkerNode* worker_node,
const performance_manager::FrameNode* client_frame_node) {
SendWorkerNotification(worker_node, false);
}
void DiscardsGraphDumpImpl::OnClientWorkerAdded(
const performance_manager::WorkerNode* worker_node,
const performance_manager::WorkerNode* client_worker_node) {
SendWorkerNotification(worker_node, false);
}
void DiscardsGraphDumpImpl::OnBeforeClientWorkerRemoved(
const performance_manager::WorkerNode* worker_node,
const performance_manager::WorkerNode* client_worker_node) {
SendWorkerNotification(worker_node, false);
}
void DiscardsGraphDumpImpl::AddNode(const performance_manager::Node* node) {
DCHECK(node_ids_.find(node) == node_ids_.end());
NodeId new_id = node_id_generator_.GenerateNextId();
node_ids_.insert(std::make_pair(node, new_id));
nodes_by_id_.insert(std::make_pair(new_id, node));
}
void DiscardsGraphDumpImpl::RemoveNode(const performance_manager::Node* node) {
auto it = node_ids_.find(node);
DCHECK(it != node_ids_.end());
NodeId node_id = it->second;
node_ids_.erase(it);
size_t erased = nodes_by_id_.erase(node_id);
DCHECK_EQ(1u, erased);
}
bool DiscardsGraphDumpImpl::HasNode(
const performance_manager::Node* node) const {
return node_ids_.find(node) != node_ids_.end();
}
int64_t DiscardsGraphDumpImpl::GetNodeId(
const performance_manager::Node* node) const {
if (node == nullptr)
return 0;
auto it = node_ids_.find(node);
DCHECK(it != node_ids_.end());
return it->second.GetUnsafeValue();
}
DiscardsGraphDumpImpl::FaviconRequestHelper*
DiscardsGraphDumpImpl::EnsureFaviconRequestHelper() {
if (!favicon_request_helper_) {
favicon_request_helper_ = std::make_unique<FaviconRequestHelper>(
weak_factory_.GetWeakPtr(), base::SequencedTaskRunnerHandle::Get());
}
return favicon_request_helper_.get();
}
void DiscardsGraphDumpImpl::StartPageFaviconRequest(
const performance_manager::PageNode* page_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!page_node->GetMainFrameUrl().is_valid())
return;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&FaviconRequestHelper::RequestFavicon,
base::Unretained(EnsureFaviconRequestHelper()),
page_node->GetMainFrameUrl(),
page_node->GetContentsProxy(), GetNodeId(page_node)));
}
void DiscardsGraphDumpImpl::StartFrameFaviconRequest(
const performance_manager::FrameNode* frame_node) {
if (!frame_node->GetURL().is_valid())
return;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&FaviconRequestHelper::RequestFavicon,
base::Unretained(EnsureFaviconRequestHelper()),
frame_node->GetURL(),
frame_node->GetPageNode()->GetContentsProxy(),
GetNodeId(frame_node)));
}
void DiscardsGraphDumpImpl::SendNotificationToAllNodes(bool created) {
for (const performance_manager::ProcessNode* process_node :
graph_->GetAllProcessNodes())
SendProcessNotification(process_node, created);
for (const performance_manager::PageNode* page_node :
graph_->GetAllPageNodes()) {
SendPageNotification(page_node, created);
if (created)
StartPageFaviconRequest(page_node);
// Dispatch preorder frame notifications.
for (const performance_manager::FrameNode* main_frame_node :
page_node->GetMainFrameNodes()) {
ForFrameAndOffspring(
main_frame_node,
[this, created](const performance_manager::FrameNode* frame_node) {
this->SendFrameNotification(frame_node, created);
if (created)
this->StartFrameFaviconRequest(frame_node);
});
}
}
for (const performance_manager::WorkerNode* worker_node :
graph_->GetAllWorkerNodes()) {
SendWorkerNotification(worker_node, created);
}
}
void DiscardsGraphDumpImpl::SendFrameNotification(
const performance_manager::FrameNode* frame,
bool created) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(https://crbug.com/1028117): Add more frame properties.
discards::mojom::FrameInfoPtr frame_info = discards::mojom::FrameInfo::New();
frame_info->id = GetNodeId(frame);
auto* parent_frame = frame->GetParentFrameNode();
frame_info->parent_frame_id = GetNodeId(parent_frame);
auto* process = frame->GetProcessNode();
frame_info->process_id = GetNodeId(process);
auto* page = frame->GetPageNode();
frame_info->page_id = GetNodeId(page);
frame_info->url = frame->GetURL();
frame_info->description_json =
ToJSON(graph_->GetNodeDataDescriberRegistry()->DescribeNodeData(frame));
if (created) {
change_subscriber_->FrameCreated(std::move(frame_info));
} else {
change_subscriber_->FrameChanged(std::move(frame_info));
}
}
void DiscardsGraphDumpImpl::SendPageNotification(
const performance_manager::PageNode* page_node,
bool created) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(https://crbug.com/1028117): Add more page_node properties.
discards::mojom::PageInfoPtr page_info = discards::mojom::PageInfo::New();
page_info->id = GetNodeId(page_node);
page_info->main_frame_url = page_node->GetMainFrameUrl();
page_info->embedder_frame_id = GetNodeId(page_node->GetEmbedderFrameNode());
page_info->description_json = ToJSON(
graph_->GetNodeDataDescriberRegistry()->DescribeNodeData(page_node));
if (created)
change_subscriber_->PageCreated(std::move(page_info));
else
change_subscriber_->PageChanged(std::move(page_info));
}
void DiscardsGraphDumpImpl::SendProcessNotification(
const performance_manager::ProcessNode* process,
bool created) {
// TODO(https://crbug.com/1028117): Add more process properties.
discards::mojom::ProcessInfoPtr process_info =
discards::mojom::ProcessInfo::New();
process_info->id = GetNodeId(process);
process_info->pid = process->GetProcessId();
process_info->private_footprint_kb = process->GetPrivateFootprintKb();
process_info->description_json =
ToJSON(graph_->GetNodeDataDescriberRegistry()->DescribeNodeData(process));
if (created)
change_subscriber_->ProcessCreated(std::move(process_info));
else
change_subscriber_->ProcessChanged(std::move(process_info));
}
void DiscardsGraphDumpImpl::SendWorkerNotification(
const performance_manager::WorkerNode* worker,
bool created) {
// TODO(https://crbug.com/1028117): Add more process properties.
discards::mojom::WorkerInfoPtr worker_info =
discards::mojom::WorkerInfo::New();
worker_info->id = GetNodeId(worker);
worker_info->url = worker->GetURL();
worker_info->process_id = GetNodeId(worker->GetProcessNode());
for (const performance_manager::FrameNode* client_frame :
worker->GetClientFrames()) {
worker_info->client_frame_ids.push_back(GetNodeId(client_frame));
}
for (const performance_manager::WorkerNode* client_worker :
worker->GetClientWorkers()) {
worker_info->client_worker_ids.push_back(GetNodeId(client_worker));
}
for (const performance_manager::WorkerNode* child_worker :
worker->GetChildWorkers()) {
worker_info->child_worker_ids.push_back(GetNodeId(child_worker));
}
worker_info->description_json =
ToJSON(graph_->GetNodeDataDescriberRegistry()->DescribeNodeData(worker));
if (created)
change_subscriber_->WorkerCreated(std::move(worker_info));
else
change_subscriber_->WorkerChanged(std::move(worker_info));
}
void DiscardsGraphDumpImpl::SendDeletionNotification(
const performance_manager::Node* node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
change_subscriber_->NodeDeleted(GetNodeId(node));
}
void DiscardsGraphDumpImpl::SendFaviconNotification(
int64_t serialization_id,
scoped_refptr<base::RefCountedMemory> bitmap_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(0u, bitmap_data->size());
discards::mojom::FavIconInfoPtr icon_info =
discards::mojom::FavIconInfo::New();
icon_info->node_id = serialization_id;
base::Base64Encode(
base::StringPiece(reinterpret_cast<const char*>(bitmap_data->front()),
bitmap_data->size()),
&icon_info->icon_data);
change_subscriber_->FavIconDataAvailable(std::move(icon_info));
}
// static
void DiscardsGraphDumpImpl::OnConnectionError(DiscardsGraphDumpImpl* impl) {
std::unique_ptr<GraphOwned> owned_impl = impl->graph_->TakeFromGraph(impl);
}