blob: 3e20a8568f92979f1c34ad24f406afd80047538f [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/profiling/json_exporter.h"
#include <map>
#include "base/strings/string_number_conversions.h"
namespace profiling {
namespace {
// Maps strings to integers for the JSON string table.
using StringTable = std::map<std::string, size_t>;
struct BacktraceNode {
BacktraceNode(size_t sid, size_t p) : string_id(sid), parent(p) {}
static constexpr size_t kNoParent = static_cast<size_t>(-1);
size_t string_id;
size_t parent; // kNoParent indicates no parent.
};
// Used as a map kep to uniquify an allocation with a given size and stack.
// Since backtraces are uniquified, this does pointer comparisons on the
// backtrace to give a stable ordering, even if that ordering has no
// intrinsic meaning.
struct UniqueAlloc {
UniqueAlloc(const Backtrace* bt, size_t sz) : backtrace(bt), size(sz) {}
bool operator<(const UniqueAlloc& other) const {
return std::tie(backtrace, size) < std::tie(other.backtrace, other.size);
}
const Backtrace* backtrace;
size_t size;
};
using UniqueAllocCount = std::map<UniqueAlloc, int>;
// Trace events support different "types" of allocations which various
// subsystems annotate stuff with for other types of tracing. For our purposes
// we only have one type, and use this ID.
constexpr int kTypeId = 0;
// Writes a dummy process name entry given a PID. When we have more information
// on a process it can be filled in here. But for now the tracing tools expect
// this entry since everything is associated with a PID.
void WriteProcessName(int pid, std::ostream& out) {
out << "{ \"pid\":" << pid << ", \"ph\":\"M\", \"name\":\"process_name\", "
<< "\"args\":{\"name\":\"Browser process\"}}";
}
// Writes the dictionary keys to preceed a "heaps_v2" trace argument. This is
// "v2" heap dump format.
void WriteHeapsV2Header(int pid, std::ostream& out) {
out << "{ \"pid\":" << pid << ",";
out << "\"ph\":\"v\",";
out << "\"name\":\"periodic_interval\",";
out << "\"args\":{";
out << "\"dumps\":{";
out << "\"level_of_detail\":\"detailed\",";
out << "\"heaps_v2\": {\n";
}
// Closes the dictionaries from the WriteHeapsV2Header function above.
void WriteHeapsV2Footer(std::ostream& out) {
out << "}}}}"; // heaps_v2, dumps, args, event
}
// Inserts or retrieves the ID for a string in the string table.
size_t AddOrGetString(std::string str, StringTable* string_table) {
auto result = string_table->emplace(std::move(str), string_table->size());
// "result.first" is an iterator into the map.
return result.first->second;
}
// Returns the index into nodes of the node to reference for this stack. That
// node will reference its parent node, etc. to allow the full stack to
// be represented.
size_t AppendBacktraceStrings(const Backtrace& backtrace,
std::vector<BacktraceNode>* nodes,
StringTable* string_table) {
int parent = -1;
for (const Address& addr : backtrace.addrs()) {
size_t sid =
AddOrGetString("pc:" + base::Uint64ToString(addr.value), string_table);
nodes->emplace_back(sid, parent);
parent = nodes->size() - 1;
}
return nodes->size() - 1; // Last item is the end of this stack.
}
// Writes the string table which looks like:
// "strings":[
// {"id":123,string:"This is the string"},
// ...
// ]
void WriteStrings(const StringTable& string_table, std::ostream& out) {
out << "\"strings\":[";
bool first_time = true;
for (const auto& string_pair : string_table) {
if (!first_time)
out << ",\n";
else
first_time = false;
out << "{\"id\":" << string_pair.second;
// TODO(brettw) when we have real symbols this will need escaping.
out << ",\"string\":\"" << string_pair.first << "\"}";
}
out << "]";
}
// Writes the nodes array in the maps section. These are all the backtrace
// entries and are indexed by the allocator nodes arra.
// "nodes":[
// {"id":1, "name_sid":123, "parent":17},
// ...
// ]
void WriteMapNodes(const std::vector<BacktraceNode>& nodes, std::ostream& out) {
out << "\"nodes\":[";
for (size_t i = 0; i < nodes.size(); i++) {
if (i != 0)
out << ",\n";
out << "{\"id\":" << i;
out << ",\"name_sid\":" << nodes[i].string_id;
if (nodes[i].parent != BacktraceNode::kNoParent)
out << ",\"parent\":" << nodes[i].parent;
out << "}";
}
out << "]";
}
// Writes the number of matching allocations array which looks like:
// "counts":[1, 1, 2 ]
void WriteCounts(const UniqueAllocCount& alloc_counts, std::ostream& out) {
out << "\"counts\":[";
bool first_time = true;
for (const auto& cur : alloc_counts) {
if (!first_time)
out << ",\n";
else
first_time = false;
out << cur.second;
}
out << "]";
}
// Writes the sizes of each allocation which looks like:
// "sizes":[32, 64, 12]
void WriteSizes(const UniqueAllocCount& alloc_counts, std::ostream& out) {
out << "\"sizes\":[";
bool first_time = true;
for (const auto& cur : alloc_counts) {
if (!first_time)
out << ",\n";
else
first_time = false;
out << cur.first.size;
}
out << "]";
}
// Writes the types array of integers which looks like:
// "types":[ 0, 0, 0, ]
void WriteTypes(const UniqueAllocCount& alloc_counts, std::ostream& out) {
out << "\"types\":[";
for (size_t i = 0; i < alloc_counts.size(); i++) {
if (i != 0)
out << ",";
out << kTypeId;
}
out << "]";
}
// Writes the nodes array which indexes for each allocation into the maps nodes
// array written above. It looks like:
// "nodes":[1, 5, 10]
void WriteAllocatorNodes(const UniqueAllocCount& alloc_counts,
const std::map<const Backtrace*, size_t>& backtraces,
std::ostream& out) {
out << "\"nodes\":[";
bool first_time = true;
for (const auto& cur : alloc_counts) {
if (!first_time)
out << ",\n";
else
first_time = false;
auto found = backtraces.find(cur.first.backtrace);
out << found->second;
}
out << "]";
}
} // namespace
void ExportAllocationEventSetToJSON(int pid,
const BacktraceStorage* backtrace_storage,
const AllocationEventSet& event_set,
std::ostream& out) {
out << "{ \"traceEvents\": [";
WriteProcessName(pid, out);
out << ",\n";
WriteHeapsV2Header(pid, out);
StringTable string_table;
// We hardcode one type, "[unknown]".
size_t type_string_id = AddOrGetString("[unknown]", &string_table);
// Find all backtraces referenced by the set. The backtrace storage will
// contain more stacks than we want to write out (it will refer to all
// processes, while we're only writing one). So do those only on demand.
//
// The map maps backtrace keys to node IDs (computed below).
std::map<const Backtrace*, size_t> backtraces;
for (const auto& event : event_set)
backtraces.emplace(event.backtrace(), 0);
// Write each backtrace, converting the string for the stack entry to string
// IDs. The backtrace -> node ID will be filled in at this time.
//
// As a future optimization, compute when a stack is a superset of another
// one and share the common nodes.
std::vector<BacktraceNode> nodes;
nodes.reserve(backtraces.size() * 10); // Guesstimate for end size.
for (auto& bt : backtraces)
bt.second = AppendBacktraceStrings(*bt.first, &nodes, &string_table);
// Maps section.
out << "\"maps\": {\n";
WriteStrings(string_table, out);
out << ",\n";
WriteMapNodes(nodes, out);
out << ",\n";
out << "\"types\":[{\"id\":" << kTypeId << ",\"name_sid\":" << type_string_id
<< "}]";
out << "},\n"; // End of maps section.
// Aggregate allocations. Allocations of the same size and stack get grouped.
UniqueAllocCount alloc_counts;
for (const auto& alloc : event_set) {
UniqueAlloc unique_alloc(alloc.backtrace(), alloc.size());
alloc_counts[unique_alloc]++;
}
// Allocators section.
out << "\"allocators\":{\"malloc\":{\n";
WriteCounts(alloc_counts, out);
out << ",\n";
WriteSizes(alloc_counts, out);
out << ",\n";
WriteTypes(alloc_counts, out);
out << ",\n";
WriteAllocatorNodes(alloc_counts, backtraces, out);
out << "}}\n"; // End of allocators section.
WriteHeapsV2Footer(out);
out << "]}\n";
}
} // namespace profiling