Skip to content

Commit

Permalink
Add converter to Chromium GlobalDumpGraph
Browse files Browse the repository at this point in the history
The GlobalDumpGraphConverter is converting between the Perfetto type
GlobalNodeGraph and Chromium GlobalDumpGraph. Using this class it
is possible to use produced by Perfetto GraphProcessor graphs in
Chromium. See crbug.com/1095982 for more details.

Bug: 1095982
Change-Id: I8bda59287a07d3759ca6da1037ecb6bfbad6439f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2299745
Commit-Queue: ssid <ssid@chromium.org>
Reviewed-by: Eric Seckler <eseckler@chromium.org>
Reviewed-by: ssid <ssid@chromium.org>
Cr-Commit-Position: refs/heads/master@{#805239}
  • Loading branch information
Maciej Malinowski authored and Commit Bot committed Sep 9, 2020
1 parent 70b84c7 commit 4217726
Show file tree
Hide file tree
Showing 4 changed files with 363 additions and 0 deletions.
4 changes: 4 additions & 0 deletions services/resource_coordinator/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ source_set("lib") {
"memory_instrumentation/aggregate_metrics_processor.h",
"memory_instrumentation/coordinator_impl.cc",
"memory_instrumentation/coordinator_impl.h",
"memory_instrumentation/global_dump_graph_converter.cc",
"memory_instrumentation/global_dump_graph_converter.h",
"memory_instrumentation/graph.cc",
"memory_instrumentation/graph.h",
"memory_instrumentation/graph_processor.cc",
Expand All @@ -36,6 +38,7 @@ source_set("lib") {
"//services/metrics/public/cpp:ukm_builders",
"//services/metrics/public/mojom",
"//services/resource_coordinator/public/cpp:resource_coordinator_cpp",
"//third_party/perfetto:libperfetto",
]
}

Expand All @@ -44,6 +47,7 @@ source_set("tests") {

sources = [
"memory_instrumentation/coordinator_impl_unittest.cc",
"memory_instrumentation/global_dump_graph_converter_unittest.cc",
"memory_instrumentation/graph_processor_unittest.cc",
"memory_instrumentation/graph_unittest.cc",
"memory_instrumentation/memory_dump_map_converter_unittest.cc",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright 2020 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/global_dump_graph_converter.h"

#include <list>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/trace_event/process_memory_dump.h"
#include "third_party/perfetto/include/perfetto/ext/trace_processor/importers/memory_tracker/graph_processor.h"

namespace memory_instrumentation {

using perfetto::trace_processor::GlobalNodeGraph;

GlobalDumpGraphConverter::GlobalDumpGraphConverter() = default;
GlobalDumpGraphConverter::~GlobalDumpGraphConverter() = default;

std::unique_ptr<GlobalDumpGraph> GlobalDumpGraphConverter::Convert(
const GlobalNodeGraph& input) const {
NodePointerPerfettoToChromeMap pointer_map;
auto output = std::make_unique<GlobalDumpGraph>();

CopyAndConvertProcessDumps(input, output.get(), &pointer_map);

CopyAndConvertSharedMemoryGraph(input, output.get(), &pointer_map);

CopyAndConvertEdges(input, output.get(), &pointer_map);

return output;
}

void GlobalDumpGraphConverter::CopyAndConvertProcessDumps(
const GlobalNodeGraph& input,
GlobalDumpGraph* output,
NodePointerPerfettoToChromeMap* pointer_map) const {
for (const auto& input_entry : input.process_node_graphs()) {
const base::ProcessId process_id = input_entry.first;
const GlobalNodeGraph::Process* input_process = input_entry.second.get();
if (input_process == nullptr)
continue;

GlobalDumpGraph::Process* output_process =
output->CreateGraphForProcess(process_id);

CopyAndConvertNodeTree(input_process->root(), output_process, {},
pointer_map);
}
}

void GlobalDumpGraphConverter::CopyAndConvertSharedMemoryGraph(
const GlobalNodeGraph& input,
GlobalDumpGraph* output,
NodePointerPerfettoToChromeMap* pointer_map) const {
CopyAndConvertNodeTree(input.shared_memory_graph()->root(),
output->shared_memory_graph(), {}, pointer_map);
}

void GlobalDumpGraphConverter::CopyAndConvertNodeTree(
const GlobalNodeGraph::Node* input,
GlobalDumpGraph::Process* output,
const std::string& node_path,
NodePointerPerfettoToChromeMap* pointer_map) const {
DCHECK(input);

for (const auto& entry : input->const_children()) {
const std::string path = node_path + "/" + entry.first;
const GlobalNodeGraph::Node* raw_child = entry.second;

GlobalDumpGraph::Node* child =
output->CreateNode(ConvertMemoryAllocatorDumpGuid(raw_child->id()),
path, raw_child->is_weak());

CopyNodeMembers(*raw_child, child);

CopyAndConvertNodeTree(raw_child, output, path, pointer_map);
pointer_map->emplace(raw_child, child);
}
}

base::trace_event::MemoryAllocatorDumpGuid
GlobalDumpGraphConverter::ConvertMemoryAllocatorDumpGuid(
const perfetto::trace_processor::MemoryAllocatorNodeId& input) const {
return base::trace_event::MemoryAllocatorDumpGuid(input.ToUint64());
}

void GlobalDumpGraphConverter::CopyNodeMembers(
const GlobalNodeGraph::Node& input,
GlobalDumpGraph::Node* output) const {
for (const auto& item : input.const_entries()) {
const std::string& name = item.first;
const GlobalNodeGraph::Node::Entry& entry = item.second;

if (entry.type == GlobalNodeGraph::Node::Entry::kUInt64) {
output->AddEntry(name, ConvertScalarUnits(entry.units),
entry.value_uint64);
} else {
output->AddEntry(name, entry.value_string);
}
}

output->set_weak(input.is_weak());
output->set_explicit(input.is_explicit());
output->add_not_owned_sub_size(input.not_owned_sub_size());
output->add_not_owning_sub_size(input.not_owning_sub_size());
output->set_owned_coefficient(input.owned_coefficient());
output->set_owning_coefficient(input.owning_coefficient());
output->set_cumulative_owned_coefficient(
input.cumulative_owned_coefficient());
output->set_cumulative_owning_coefficient(
input.cumulative_owning_coefficient());
}

void GlobalDumpGraphConverter::CopyAndConvertEdges(
const GlobalNodeGraph& input,
GlobalDumpGraph* output,
NodePointerPerfettoToChromeMap* pointer_map) const {
for (const auto& input_edge : input.edges()) {
CopyAndConvertEdge(input_edge, output, pointer_map);
}
}

void GlobalDumpGraphConverter::CopyAndConvertEdge(
const GlobalNodeGraph::Edge& input,
GlobalDumpGraph* output,
const NodePointerPerfettoToChromeMap* pointer_map) const {
DCHECK(input.source());
DCHECK(input.target());

GlobalDumpGraph::Node* source = pointer_map->at(input.source());
GlobalDumpGraph::Node* target = pointer_map->at(input.target());

DCHECK(source);
DCHECK(target);

output->AddNodeOwnershipEdge(source, target, input.priority());
}

GlobalDumpGraph::Node::Entry::ScalarUnits
GlobalDumpGraphConverter::ConvertScalarUnits(
GlobalNodeGraph::Node::Entry::ScalarUnits input) const {
using PerfettoScalarUnits = GlobalNodeGraph::Node::Entry::ScalarUnits;
using ChromeScalarUnits = GlobalDumpGraph::Node::Entry::ScalarUnits;
switch (input) {
case PerfettoScalarUnits::kObjects:
return ChromeScalarUnits::kObjects;
case PerfettoScalarUnits::kBytes:
return ChromeScalarUnits::kBytes;
}

NOTREACHED();
}

} // namespace memory_instrumentation
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2020 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.

#ifndef SERVICES_RESOURCE_COORDINATOR_MEMORY_INSTRUMENTATION_GLOBAL_DUMP_GRAPH_CONVERTER_H_
#define SERVICES_RESOURCE_COORDINATOR_MEMORY_INSTRUMENTATION_GLOBAL_DUMP_GRAPH_CONVERTER_H_

#include <list>
#include <map>
#include <memory>
#include <vector>

#include "services/resource_coordinator/memory_instrumentation/graph.h"
#include "third_party/perfetto/include/perfetto/ext/trace_processor/importers/memory_tracker/graph_processor.h"

namespace memory_instrumentation {

// Converts the Perfetto GlobalNodeGraph to the corresponding defined in
// Chromium type GlobalDumpGraph.
//
// Example usage:
//
// {
// perfetto::trace_processor::GlobalNodeGraph graph;
//
// GlobalDumpGraphConverter converter;
// std::unique_ptr<GlobalDumpGraph> dumpGraph = converter.Convert(graph);
// }
class GlobalDumpGraphConverter {
public:
GlobalDumpGraphConverter();
~GlobalDumpGraphConverter();
GlobalDumpGraphConverter(const GlobalDumpGraphConverter&) = delete;
void operator=(const GlobalDumpGraphConverter&) = delete;

std::unique_ptr<GlobalDumpGraph> Convert(
const perfetto::trace_processor::GlobalNodeGraph& input) const;

private:
// Map is used during conversion from Perfetto GlobalNodeGraph to Chromium
// GlobalDumpGraph. It simplifies finding matching nodes during conversion of
// graph edges.
using NodePointerPerfettoToChromeMap =
std::map<const perfetto::trace_processor::GlobalNodeGraph::Node*,
GlobalDumpGraph::Node*>;

void CopyAndConvertProcessDumps(
const perfetto::trace_processor::GlobalNodeGraph& input,
GlobalDumpGraph* output,
NodePointerPerfettoToChromeMap* pointer_map) const;

void CopyAndConvertSharedMemoryGraph(
const perfetto::trace_processor::GlobalNodeGraph& input,
GlobalDumpGraph* output,
NodePointerPerfettoToChromeMap* pointer_map) const;

void CopyAndConvertNodeTree(
const perfetto::trace_processor::GlobalNodeGraph::Node* input,
GlobalDumpGraph::Process* output,
const std::string& node_path,
NodePointerPerfettoToChromeMap* pointer_map) const;

base::trace_event::MemoryAllocatorDumpGuid ConvertMemoryAllocatorDumpGuid(
const perfetto::trace_processor::MemoryAllocatorNodeId& input) const;

void CopyNodeMembers(
const perfetto::trace_processor::GlobalNodeGraph::Node& input,
GlobalDumpGraph::Node* output) const;

void CopyAndConvertEdges(
const perfetto::trace_processor::GlobalNodeGraph& input,
GlobalDumpGraph* output,
NodePointerPerfettoToChromeMap* pointer_map) const;

void CopyAndConvertEdge(
const perfetto::trace_processor::GlobalNodeGraph::Edge& input,
GlobalDumpGraph* output,
const NodePointerPerfettoToChromeMap* pointer_map) const;

GlobalDumpGraph::Node::Entry::ScalarUnits ConvertScalarUnits(
const perfetto::trace_processor::GlobalNodeGraph::Node::Entry::ScalarUnits
input) const;
};

} // namespace memory_instrumentation

#endif // SERVICES_RESOURCE_COORDINATOR_MEMORY_INSTRUMENTATION_GLOBAL_DUMP_GRAPH_CONVERTER_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2020 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/global_dump_graph_converter.h"

#include "testing/gtest/include/gtest/gtest.h"

namespace memory_instrumentation {

using base::trace_event::MemoryAllocatorDumpGuid;
using perfetto::trace_processor::GlobalNodeGraph;

TEST(GlobalDumpGraphConverterTest, Convert) {
auto input = std::make_unique<perfetto::trace_processor::GlobalNodeGraph>();
perfetto::trace_processor::GlobalNodeGraph::Node* owner = nullptr;
perfetto::trace_processor::GlobalNodeGraph::Node* owned = nullptr;

// Adding first process with one node
{
GlobalNodeGraph::Process* process = input->CreateGraphForProcess(1);
perfetto::trace_processor::MemoryAllocatorNodeId node_id{1};
owner = process->CreateNode(node_id, "test1", false);
owner->AddEntry(
"first",
perfetto::trace_processor::GlobalNodeGraph::Node::Entry::kObjects, 123);
owner->AddEntry("second", "string");
owner->set_weak(true);
owner->set_explicit(true);
owner->add_not_owned_sub_size(100);
owner->add_not_owning_sub_size(200);
owner->set_owned_coefficient(3);
owner->set_owning_coefficient(5);
owner->set_cumulative_owned_coefficient(7);
owner->set_cumulative_owning_coefficient(9);
}

// Adding second process with one node
{
GlobalNodeGraph::Process* process = input->CreateGraphForProcess(2);
perfetto::trace_processor::MemoryAllocatorNodeId node_id{2};
owned = process->CreateNode(node_id, "test2", false);
}

// Creating Edge between two previously created nodes
input->AddNodeOwnershipEdge(owner, owned, 99);

// Create shared memory graph
{
GlobalNodeGraph::Process* process = input->shared_memory_graph();
perfetto::trace_processor::MemoryAllocatorNodeId node_id{3};
process->CreateNode(node_id, "test3", false);
}

// Convert GlobalDumpGraph to Perfetto GlobalDumpGrap.
GlobalDumpGraphConverter converter;
const std::unique_ptr<GlobalDumpGraph> output = converter.Convert(*input);

ASSERT_EQ(output->process_dump_graphs().size(), 2uL);
ASSERT_NE(output->process_dump_graphs().find(1),
output->process_dump_graphs().end());

{
const GlobalDumpGraph::Process& process1 =
*output->process_dump_graphs().at(1);
ASSERT_EQ(process1.root()->const_children().size(), 1uL);
const GlobalDumpGraph::Node* node1 = process1.root()->GetChild("test1");
ASSERT_NE(node1, nullptr);
ASSERT_EQ(node1->is_weak(), true);
ASSERT_EQ(node1->is_explicit(), true);
ASSERT_EQ(node1->not_owned_sub_size(), 100uL);
ASSERT_EQ(node1->not_owning_sub_size(), 200uL);
ASSERT_EQ(node1->owned_coefficient(), 3);
ASSERT_EQ(node1->owning_coefficient(), 5);
ASSERT_EQ(node1->cumulative_owned_coefficient(), 7);
ASSERT_EQ(node1->cumulative_owning_coefficient(), 9);
ASSERT_EQ(node1->const_entries().size(), 2uL);
ASSERT_NE(node1->const_entries().find("first"),
node1->const_entries().end());
ASSERT_EQ(node1->const_entries().at("first").type,
GlobalDumpGraph::Node::Entry::kUInt64);
ASSERT_EQ(node1->const_entries().at("first").units,
GlobalDumpGraph::Node::Entry::kObjects);
ASSERT_EQ(node1->const_entries().at("first").value_uint64, 123uL);
ASSERT_NE(node1->const_entries().find("second"),
node1->const_entries().end());
ASSERT_EQ(node1->const_entries().at("second").type,
GlobalDumpGraph::Node::Entry::kString);
ASSERT_EQ(node1->const_entries().at("second").value_string, "string");

const GlobalDumpGraph::Process& process2 =
*output->process_dump_graphs().at(2);
ASSERT_EQ(process2.root()->const_children().size(), 1uL);
const GlobalDumpGraph::Node* node2 = process2.root()->GetChild("test2");
ASSERT_NE(node2, nullptr);

const auto edges_count =
std::distance(std::begin(output->edges()), std::end(output->edges()));
ASSERT_EQ(edges_count, 1L);

const GlobalDumpGraph::Edge edge = *output->edges().begin();
ASSERT_EQ(edge.source(), node1);
ASSERT_EQ(edge.target(), node2);
ASSERT_EQ(edge.priority(), 99);

ASSERT_EQ(output->shared_memory_graph()->root()->const_children().size(),
1uL);
const GlobalDumpGraph::Node* node3 =
output->shared_memory_graph()->root()->GetChild("test3");
ASSERT_NE(node3, nullptr);
}
}

} // namespace memory_instrumentation

0 comments on commit 4217726

Please sign in to comment.