blob: c88e9e0beb4d295b74ab69a83bcf7057382a5d0d [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 "services/resource_coordinator/memory_instrumentation/graph.h"
#include "base/callback.h"
#include "base/strings/string_tokenizer.h"
namespace memory_instrumentation {
namespace {
using base::trace_event::MemoryAllocatorDumpGuid;
using Edge = GlobalDumpGraph::Edge;
using PostOrderIterator = GlobalDumpGraph::PostOrderIterator;
using PreOrderIterator = GlobalDumpGraph::PreOrderIterator;
using Process = GlobalDumpGraph::Process;
using Node = GlobalDumpGraph::Node;
} // namespace
GlobalDumpGraph::GlobalDumpGraph()
: shared_memory_graph_(
std::make_unique<Process>(base::kNullProcessId, this)) {}
GlobalDumpGraph::~GlobalDumpGraph() {}
Process* GlobalDumpGraph::CreateGraphForProcess(base::ProcessId process_id) {
auto id_to_dump_iterator = process_dump_graphs_.emplace(
process_id, std::make_unique<Process>(process_id, this));
DCHECK(id_to_dump_iterator.second); // Check for duplicate pids
return id_to_dump_iterator.first->second.get();
}
void GlobalDumpGraph::AddNodeOwnershipEdge(Node* owner,
Node* owned,
int importance) {
// The owner node should not already be associated with an edge.
DCHECK(!owner->owns_edge());
all_edges_.emplace_front(owner, owned, importance);
Edge* edge = &*all_edges_.begin();
owner->SetOwnsEdge(edge);
owned->AddOwnedByEdge(edge);
}
Node* GlobalDumpGraph::CreateNode(Process* process_graph, Node* parent) {
all_nodes_.emplace_front(process_graph, parent);
return &*all_nodes_.begin();
}
PreOrderIterator GlobalDumpGraph::VisitInDepthFirstPreOrder() {
std::vector<Node*> roots;
for (auto it = process_dump_graphs_.rbegin();
it != process_dump_graphs_.rend(); it++) {
roots.push_back(it->second->root());
}
roots.push_back(shared_memory_graph_->root());
return PreOrderIterator(std::move(roots));
}
PostOrderIterator GlobalDumpGraph::VisitInDepthFirstPostOrder() {
std::vector<Node*> roots;
for (auto it = process_dump_graphs_.rbegin();
it != process_dump_graphs_.rend(); it++) {
roots.push_back(it->second->root());
}
roots.push_back(shared_memory_graph_->root());
return PostOrderIterator(std::move(roots));
}
Process::Process(base::ProcessId pid, GlobalDumpGraph* global_graph)
: pid_(pid),
global_graph_(global_graph),
root_(global_graph->CreateNode(this, nullptr)) {}
Process::~Process() {}
Node* Process::CreateNode(MemoryAllocatorDumpGuid guid,
base::StringPiece path,
bool weak) {
DCHECK(!path.empty());
std::string path_string = path.as_string();
base::StringTokenizer tokenizer(path_string, "/");
// Perform a tree traversal, creating the nodes if they do not
// already exist on the path to the child.
Node* current = root_;
while (tokenizer.GetNext()) {
base::StringPiece key = tokenizer.token_piece();
Node* parent = current;
current = current->GetChild(key);
if (!current) {
current = global_graph_->CreateNode(this, parent);
parent->InsertChild(key, current);
}
}
// The final node should have the weakness specified by the
// argument and also be considered explicit.
current->set_weak(weak);
current->set_explicit(true);
// The final node should also have the associated |guid|.
current->set_guid(guid);
// Add to the global guid map as well if it exists.
if (!guid.empty())
global_graph_->nodes_by_guid_.emplace(guid, current);
return current;
}
Node* Process::FindNode(base::StringPiece path) {
DCHECK(!path.empty());
std::string path_string = path.as_string();
base::StringTokenizer tokenizer(path_string, "/");
Node* current = root_;
while (tokenizer.GetNext()) {
base::StringPiece key = tokenizer.token_piece();
current = current->GetChild(key);
if (!current)
return nullptr;
}
return current;
}
Node::Node(Process* dump_graph, Node* parent)
: dump_graph_(dump_graph), parent_(parent), owns_edge_(nullptr) {}
Node::~Node() {}
Node* Node::GetChild(base::StringPiece name) {
DCHECK(!name.empty());
DCHECK_EQ(std::string::npos, name.find('/'));
auto child = children_.find(name.as_string());
return child == children_.end() ? nullptr : child->second;
}
void Node::InsertChild(base::StringPiece name, Node* node) {
DCHECK(!name.empty());
DCHECK_EQ(std::string::npos, name.find('/'));
children_.emplace(name.as_string(), node);
}
Node* Node::CreateChild(base::StringPiece name) {
Node* new_child = dump_graph_->global_graph()->CreateNode(dump_graph_, this);
InsertChild(name, new_child);
return new_child;
}
bool Node::IsDescendentOf(const Node& possible_parent) const {
const Node* current = this;
while (current != nullptr) {
if (current == &possible_parent)
return true;
current = current->parent();
}
return false;
}
void Node::AddOwnedByEdge(Edge* edge) {
owned_by_edges_.push_back(edge);
}
void Node::SetOwnsEdge(Edge* owns_edge) {
owns_edge_ = owns_edge;
}
void Node::AddEntry(std::string name,
Node::Entry::ScalarUnits units,
uint64_t value) {
entries_.emplace(name, Node::Entry(units, value));
}
void Node::AddEntry(std::string name, std::string value) {
entries_.emplace(name, Node::Entry(value));
}
Node::Entry::Entry(Entry::ScalarUnits units, uint64_t value)
: type(Node::Entry::Type::kUInt64), units(units), value_uint64(value) {}
Node::Entry::Entry(std::string value)
: type(Node::Entry::Type::kString),
units(Node::Entry::ScalarUnits::kObjects),
value_string(value),
value_uint64(0) {}
Edge::Edge(Node* source, Node* target, int priority)
: source_(source), target_(target), priority_(priority) {}
PreOrderIterator::PreOrderIterator(std::vector<Node*> roots)
: to_visit_(std::move(roots)) {}
PreOrderIterator::PreOrderIterator(PreOrderIterator&& other) = default;
PreOrderIterator::~PreOrderIterator() {}
// Yields the next node in the DFS post-order traversal.
Node* PreOrderIterator::next() {
while (!to_visit_.empty()) {
// Retain a pointer to the node at the top and remove it from stack.
Node* node = to_visit_.back();
to_visit_.pop_back();
// If the node has already been visited, don't visit it again.
if (visited_.count(node) != 0)
continue;
// If we haven't visited the node which this node owns then wait for that.
if (node->owns_edge() && visited_.count(node->owns_edge()->target()) == 0)
continue;
// If we haven't visited the node's parent then wait for that.
if (node->parent() && visited_.count(node->parent()) == 0)
continue;
// Visit all children of this node.
for (auto it = node->children()->rbegin(); it != node->children()->rend();
it++) {
to_visit_.push_back(it->second);
}
// Visit all owners of this node.
for (auto it = node->owned_by_edges()->rbegin();
it != node->owned_by_edges()->rend(); it++) {
to_visit_.push_back((*it)->source());
}
// Add this node to the visited set.
visited_.insert(node);
return node;
}
return nullptr;
}
PostOrderIterator::PostOrderIterator(std::vector<Node*> roots)
: to_visit_(std::move(roots)) {}
PostOrderIterator::PostOrderIterator(PostOrderIterator&& other) = default;
PostOrderIterator::~PostOrderIterator() = default;
// Yields the next node in the DFS post-order traversal.
Node* PostOrderIterator::next() {
while (!to_visit_.empty()) {
// Retain a pointer to the node at the top and remove it from stack.
Node* node = to_visit_.back();
to_visit_.pop_back();
// If the node has already been visited, don't visit it again.
if (visited_.count(node) != 0)
continue;
// If the node is at the top of the path, we have already looked
// at its children and owners.
if (!path_.empty() && path_.back() == node) {
// Mark the current node as visited so we don't visit again.
visited_.insert(node);
// The current node is no longer on the path.
path_.pop_back();
return node;
}
// If the node is not at the front, it should also certainly not be
// anywhere else in the path. If it is, there is a cycle in the graph.
DCHECK(std::find(path_.begin(), path_.end(), node) == path_.end());
path_.push_back(node);
// Add this node back to the queue of nodes to visit.
to_visit_.push_back(node);
// Visit all children of this node.
for (auto it = node->children()->rbegin(); it != node->children()->rend();
it++) {
to_visit_.push_back(it->second);
}
// Visit all owners of this node.
for (auto it = node->owned_by_edges()->rbegin();
it != node->owned_by_edges()->rend(); it++) {
to_visit_.push_back((*it)->source());
}
}
return nullptr;
}
} // namespace memory_instrumentation