Commit 56513a6b authored by Jasper Chapman-Black's avatar Jasper Chapman-Black Committed by Commit Bot

SuperSize: Caspian: Diff mode, add PSS measure

* Add DiffSizeInfo, with logic for matching up symbols from two
  loaded SizeInfos. This doesn't yet consider the new native and
  Java canonical name processing code, but the boilerplate is there.
* Add scaffolding in caspian_web.cc to support diffing.
* Add async waits in .js to avoid attempts to call WebAssembly
  code before module is loaded.

Bug: 1011921
Change-Id: Ia121e78fd5c8c2d7e07c53de345e53e9fa80d88d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1906109
Commit-Queue: Jasper Chapman-Black <jaspercb@chromium.org>
Reviewed-by: default avatarSamuel Huang <huangs@chromium.org>
Cr-Commit-Position: refs/heads/master@{#714517}
parent 1de10971
...@@ -72,7 +72,7 @@ if (is_wasm) { ...@@ -72,7 +72,7 @@ if (is_wasm) {
"-s", "-s",
"ALLOW_MEMORY_GROWTH=1", "ALLOW_MEMORY_GROWTH=1",
"-s", "-s",
"EXPORTED_FUNCTIONS=['_LoadSizeFile','_BuildTree','_Open','_malloc','_free']", "EXPORTED_FUNCTIONS=['_LoadSizeFile','_LoadBeforeSizeFile','_BuildTree','_Open','_malloc','_free']",
"-s", "-s",
"EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString']", "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString']",
] ]
......
...@@ -21,7 +21,9 @@ ...@@ -21,7 +21,9 @@
namespace caspian { namespace caspian {
namespace { namespace {
SizeInfo info; std::unique_ptr<SizeInfo> info;
std::unique_ptr<SizeInfo> before_info;
std::unique_ptr<DiffSizeInfo> diff_info;
std::unique_ptr<TreeBuilder> builder; std::unique_ptr<TreeBuilder> builder;
std::unique_ptr<Json::StreamWriter> writer; std::unique_ptr<Json::StreamWriter> writer;
...@@ -38,7 +40,15 @@ std::string JsonSerialize(const Json::Value& value) { ...@@ -38,7 +40,15 @@ std::string JsonSerialize(const Json::Value& value) {
extern "C" { extern "C" {
void LoadSizeFile(const char* compressed, size_t size) { void LoadSizeFile(const char* compressed, size_t size) {
ParseSizeInfo(compressed, size, &info); diff_info.reset(nullptr);
info = std::make_unique<SizeInfo>();
ParseSizeInfo(compressed, size, info.get());
}
void LoadBeforeSizeFile(const char* compressed, size_t size) {
diff_info.reset(nullptr);
before_info = std::make_unique<SizeInfo>();
ParseSizeInfo(compressed, size, before_info.get());
} }
void BuildTree(bool group_by_component, void BuildTree(bool group_by_component,
...@@ -49,14 +59,22 @@ void BuildTree(bool group_by_component, ...@@ -49,14 +59,22 @@ void BuildTree(bool group_by_component,
int match_flag) { int match_flag) {
std::vector<std::function<bool(const Symbol&)>> filters; std::vector<std::function<bool(const Symbol&)>> filters;
const bool diff_mode = info && before_info;
if (minimum_size_bytes > 0) { if (minimum_size_bytes > 0) {
filters.push_back([minimum_size_bytes](const Symbol& sym) -> bool { if (!diff_mode) {
return sym.pss() >= minimum_size_bytes; filters.push_back([minimum_size_bytes](const Symbol& sym) -> bool {
}); return sym.pss >= minimum_size_bytes;
});
} else {
filters.push_back([minimum_size_bytes](const Symbol& sym) -> bool {
return abs(sym.pss) >= minimum_size_bytes;
});
}
} }
// It's currently not useful to filter on more than one flag, so |match_flag| // It's currently not useful to filter on more than one flag, so
// can be assumed to be a power of two. // |match_flag| can be assumed to be a power of two.
if (match_flag) { if (match_flag) {
std::cout << "Filtering on flag matching " << match_flag << std::endl; std::cout << "Filtering on flag matching " << match_flag << std::endl;
filters.push_back([match_flag](const Symbol& sym) -> bool { filters.push_back([match_flag](const Symbol& sym) -> bool {
...@@ -71,8 +89,7 @@ void BuildTree(bool group_by_component, ...@@ -71,8 +89,7 @@ void BuildTree(bool group_by_component,
include_sections_map[static_cast<uint8_t>(*c)] = true; include_sections_map[static_cast<uint8_t>(*c)] = true;
} }
filters.push_back([&include_sections_map](const Symbol& sym) -> bool { filters.push_back([&include_sections_map](const Symbol& sym) -> bool {
return include_sections_map[static_cast<uint8_t>( return include_sections_map[static_cast<uint8_t>(sym.sectionId)];
info.ShortSectionName(sym.section_name))];
}); });
} }
...@@ -98,7 +115,18 @@ void BuildTree(bool group_by_component, ...@@ -98,7 +115,18 @@ void BuildTree(bool group_by_component,
} }
} }
builder.reset(new TreeBuilder(&info, group_by_component, filters)); // BuildTree() is called every time a new filter is applied in the HTML
// viewer, but if we already have a DiffSizeInfo we can skip regenerating it
// and let the TreeBuilder filter the symbols we care about.
if (diff_mode && !diff_info) {
diff_info.reset(new DiffSizeInfo(before_info.get(), info.get()));
}
BaseSizeInfo* rendered_info = info.get();
if (diff_mode) {
rendered_info = diff_info.get();
}
builder.reset(new TreeBuilder(rendered_info, group_by_component, filters));
builder->Build(); builder->Build();
} }
......
...@@ -14,20 +14,30 @@ ...@@ -14,20 +14,30 @@
#include "tools/binary_size/libsupersize/caspian/file_format.h" #include "tools/binary_size/libsupersize/caspian/file_format.h"
#include "tools/binary_size/libsupersize/caspian/model.h" #include "tools/binary_size/libsupersize/caspian/model.h"
int main(int argc, char* argv[]) { void ParseSizeInfoFromFile(const char* filename, caspian::SizeInfo* info) {
if (argc < 2) { std::ifstream ifs(filename, std::ifstream::in);
std::cerr << "Usage: cli (path to .size file)" << std::endl;
exit(1);
}
std::ifstream ifs(argv[1], std::ifstream::in);
if (!ifs.good()) { if (!ifs.good()) {
std::cerr << "Unable to open file: " << argv[1] << std::endl; std::cerr << "Unable to open file: " << filename << std::endl;
exit(1); exit(1);
} }
std::string compressed((std::istreambuf_iterator<char>(ifs)), std::string compressed((std::istreambuf_iterator<char>(ifs)),
std::istreambuf_iterator<char>()); std::istreambuf_iterator<char>());
caspian::ParseSizeInfo(compressed.data(), compressed.size(), info);
}
void Diff(const char* before_filename, const char* after_filename) {
caspian::SizeInfo before;
ParseSizeInfoFromFile(before_filename, &before);
caspian::SizeInfo after;
ParseSizeInfoFromFile(after_filename, &after);
caspian::DiffSizeInfo diff(&before, &after);
}
void Validate(const char* filename) {
caspian::SizeInfo info; caspian::SizeInfo info;
caspian::ParseSizeInfo(compressed.data(), compressed.size(), &info); ParseSizeInfoFromFile(filename, &info);
size_t max_aliases = 0; size_t max_aliases = 0;
for (auto& s : info.raw_symbols) { for (auto& s : info.raw_symbols) {
...@@ -44,5 +54,23 @@ int main(int argc, char* argv[]) { ...@@ -44,5 +54,23 @@ int main(int argc, char* argv[]) {
} }
} }
std::cout << "Largest number of aliases: " << max_aliases << std::endl; std::cout << "Largest number of aliases: " << max_aliases << std::endl;
}
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Must have exactly one of:" << std::endl;
std::cerr << " validate, diff" << std::endl;
std::cerr << "Usage: " << std::endl;
std::cerr << " cli validate <size file>" << std::endl;
std::cerr << " cli diff <before_file> <after_file>" << std::endl;
exit(1);
}
if (std::string_view(argv[1]) == "diff") {
Diff(argv[2], argv[3]);
} else if (std::string_view(argv[1]) == "validate") {
Validate(argv[1]);
} else {
exit(1);
}
return 0; return 0;
} }
...@@ -257,6 +257,8 @@ void ParseSizeInfo(const char* gzipped, ...@@ -257,6 +257,8 @@ void ParseSizeInfo(const char* gzipped,
// Construct raw symbols // Construct raw symbols
for (int section_idx = 0; section_idx < n_sections; section_idx++) { for (int section_idx = 0; section_idx < n_sections; section_idx++) {
const char* cur_section_name = info->section_names[section_idx]; const char* cur_section_name = info->section_names[section_idx];
caspian::SectionId cur_section_id =
info->ShortSectionName(cur_section_name);
const int cur_section_count = section_counts[section_idx]; const int cur_section_count = section_counts[section_idx];
const std::vector<int64_t>& cur_addresses = addresses[section_idx]; const std::vector<int64_t>& cur_addresses = addresses[section_idx];
const std::vector<int32_t>& cur_sizes = sizes[section_idx]; const std::vector<int32_t>& cur_sizes = sizes[section_idx];
...@@ -295,7 +297,7 @@ void ParseSizeInfo(const char* gzipped, ...@@ -295,7 +297,7 @@ void ParseSizeInfo(const char* gzipped,
} }
} }
} }
new_sym.section_name = cur_section_name; new_sym.sectionId = cur_section_id;
new_sym.address = cur_addresses[i]; new_sym.address = cur_addresses[i];
new_sym.size = cur_sizes[i]; new_sym.size = cur_sizes[i];
new_sym.object_path = info->object_paths[cur_path_indices[i]]; new_sym.object_path = info->object_paths[cur_path_indices[i]];
...@@ -319,6 +321,10 @@ void ParseSizeInfo(const char* gzipped, ...@@ -319,6 +321,10 @@ void ParseSizeInfo(const char* gzipped,
} }
} }
} }
for (caspian::Symbol& sym : info->raw_symbols) {
size_t alias_count = sym.aliases ? sym.aliases->size() : 1;
sym.pss = static_cast<float>(sym.size) / alias_count;
}
// If there are unparsed non-empty lines, something's gone wrong. // If there are unparsed non-empty lines, something's gone wrong.
CheckNoNonEmptyLinesRemain(rest); CheckNoNonEmptyLinesRemain(rest);
......
...@@ -5,15 +5,82 @@ ...@@ -5,15 +5,82 @@
#include "tools/binary_size/libsupersize/caspian/model.h" #include "tools/binary_size/libsupersize/caspian/model.h"
#include <algorithm> #include <algorithm>
#include <functional>
#include <iostream> #include <iostream>
#include <list>
#include <string> #include <string>
#include <unordered_map>
#include "tools/binary_size/libsupersize/caspian/file_format.h" #include "tools/binary_size/libsupersize/caspian/file_format.h"
namespace {
struct SymbolMatchIndex {
SymbolMatchIndex() {}
SymbolMatchIndex(caspian::SectionId id,
std::string_view name,
std::string_view path,
int size_without_padding)
: nonempty(true),
id(id),
name(name),
path(path),
size_without_padding(size_without_padding) {
this->name = name;
}
operator bool() const { return nonempty; }
bool operator==(const SymbolMatchIndex& other) const {
return id == other.id && name == other.name && path == other.path &&
size_without_padding == other.size_without_padding;
}
bool nonempty = false;
caspian::SectionId id;
std::string_view name;
std::string_view path;
int size_without_padding;
};
} // namespace
namespace std {
template <>
struct hash<SymbolMatchIndex> {
static constexpr size_t kPrime1 = 105929;
static constexpr size_t kPrime2 = 8543;
size_t operator()(const SymbolMatchIndex& k) const {
return ((kPrime1 * static_cast<size_t>(k.id)) ^
hash<string_view>()(k.name) ^ hash<string_view>()(k.path) ^
(kPrime2 * k.size_without_padding));
}
};
} // namespace std
namespace caspian { namespace caspian {
Symbol::Symbol() = default; Symbol::Symbol() = default;
Symbol::Symbol(const Symbol& other) = default; Symbol::Symbol(const Symbol& other) = default;
Symbol& Symbol::operator=(const Symbol& other) = default;
Symbol Symbol::DiffSymbolFrom(const Symbol* before_sym,
const Symbol* after_sym) {
Symbol ret;
if (after_sym) {
ret = *after_sym;
} else if (before_sym) {
ret = *before_sym;
}
if (before_sym && after_sym) {
ret.size = after_sym->size - before_sym->size;
ret.pss = after_sym->pss - before_sym->pss;
} else if (before_sym) {
ret.size = -before_sym->size;
ret.pss = -before_sym->pss;
}
return ret;
}
TreeNode::TreeNode() = default; TreeNode::TreeNode() = default;
TreeNode::~TreeNode() { TreeNode::~TreeNode() {
...@@ -23,6 +90,9 @@ TreeNode::~TreeNode() { ...@@ -23,6 +90,9 @@ TreeNode::~TreeNode() {
} }
} }
BaseSizeInfo::BaseSizeInfo() = default;
BaseSizeInfo::~BaseSizeInfo() = default;
SizeInfo::SizeInfo() = default; SizeInfo::SizeInfo() = default;
SizeInfo::~SizeInfo() = default; SizeInfo::~SizeInfo() = default;
...@@ -61,10 +131,100 @@ SectionId SizeInfo::ShortSectionName(const char* section_name) { ...@@ -61,10 +131,100 @@ SectionId SizeInfo::ShortSectionName(const char* section_name) {
return ret; return ret;
} }
namespace {
// Copied from /base/stl_util.h
template <class T, class Allocator, class Value>
void Erase(std::vector<T, Allocator>& container, const Value& value) {
container.erase(std::remove(container.begin(), container.end(), value),
container.end());
}
std::string_view GetIdPath(const Symbol& sym) {
return (sym.source_path && *sym.source_path) ? sym.source_path
: sym.object_path;
}
SymbolMatchIndex PerfectMatch(const Symbol& sym) {
return SymbolMatchIndex(sym.sectionId, sym.full_name, GetIdPath(sym),
sym.pss);
}
SymbolMatchIndex PerfectMatchExceptSize(const Symbol& sym) {
return SymbolMatchIndex(sym.sectionId, sym.full_name, GetIdPath(sym), 0.0f);
}
void MatchSymbols(std::function<SymbolMatchIndex(const Symbol&)> key_func,
std::vector<caspian::Symbol>* delta_symbols,
std::vector<const caspian::Symbol*>* unmatched_before,
std::vector<const caspian::Symbol*>* unmatched_after) {
// TODO(jaspercb): Accumulate added/dropped padding by section name.
std::unordered_map<SymbolMatchIndex,
std::list<std::reference_wrapper<const caspian::Symbol*>>>
before_symbols_by_key;
for (const caspian::Symbol*& before_sym : *unmatched_before) {
SymbolMatchIndex key = key_func(*before_sym);
if (key) {
before_symbols_by_key[key].emplace_back(before_sym);
}
}
for (const caspian::Symbol*& after_sym : *unmatched_after) {
SymbolMatchIndex key = key_func(*after_sym);
if (key) {
const auto& found = before_symbols_by_key.find(key);
if (found != before_symbols_by_key.end() && found->second.size()) {
const caspian::Symbol*& before_sym = found->second.front().get();
found->second.pop_front();
delta_symbols->push_back(Symbol::DiffSymbolFrom(before_sym, after_sym));
before_sym = nullptr;
after_sym = nullptr;
}
}
}
Erase(*unmatched_before, nullptr);
Erase(*unmatched_after, nullptr);
}
} // namespace
DiffSizeInfo::DiffSizeInfo(SizeInfo* before, SizeInfo* after)
: before(before), after(after) {
// TODO(jaspercb): It's okay to do a first pass where we match on the raw
// name (from the .size file), but anything left over after that first step
// (hopefully <2k objects) needs to consider the derived full_name. Should
// do this lazily for efficiency - name derivation is costly.
std::vector<const caspian::Symbol*> unmatched_before;
for (const caspian::Symbol& sym : before->raw_symbols) {
unmatched_before.push_back(&sym);
}
std::vector<const caspian::Symbol*> unmatched_after;
for (const caspian::Symbol& sym : after->raw_symbols) {
unmatched_after.push_back(&sym);
}
// Attempt several rounds to use increasingly loose matching on unmatched
// symbols. Any symbols still unmatched are tried in the next round.
for (const auto& key_function : {PerfectMatch, PerfectMatchExceptSize}) {
MatchSymbols(key_function, &raw_symbols, &unmatched_before,
&unmatched_after);
}
// Add removals or deletions for any unmatched symbols.
for (const caspian::Symbol* after_sym : unmatched_after) {
raw_symbols.push_back(Symbol::DiffSymbolFrom(nullptr, after_sym));
}
for (const caspian::Symbol* before_sym : unmatched_before) {
raw_symbols.push_back(Symbol::DiffSymbolFrom(before_sym, nullptr));
}
}
DiffSizeInfo::~DiffSizeInfo() {}
void TreeNode::WriteIntoJson(Json::Value* out, int depth) { void TreeNode::WriteIntoJson(Json::Value* out, int depth) {
(*out)["idPath"] = std::string(this->id_path); (*out)["idPath"] = std::string(this->id_path);
(*out)["shortNameIndex"] = this->short_name_index; (*out)["shortNameIndex"] = this->short_name_index;
// TODO: Put correct information.
std::string type; std::string type;
if (container_type != ContainerType::kSymbol) { if (container_type != ContainerType::kSymbol) {
type += static_cast<char>(container_type); type += static_cast<char>(container_type);
...@@ -84,7 +244,7 @@ void TreeNode::WriteIntoJson(Json::Value* out, int depth) { ...@@ -84,7 +244,7 @@ void TreeNode::WriteIntoJson(Json::Value* out, int depth) {
// TODO: Support additional compare functions. // TODO: Support additional compare functions.
auto compare_func = [](const TreeNode* const& l, auto compare_func = [](const TreeNode* const& l,
const TreeNode* const& r) -> bool { const TreeNode* const& r) -> bool {
return l->size > r->size; return abs(l->size) > abs(r->size);
}; };
std::sort(this->children.begin(), this->children.end(), compare_func); std::sort(this->children.begin(), this->children.end(), compare_func);
for (unsigned int i = 0; i < this->children.size(); i++) { for (unsigned int i = 0; i < this->children.size(); i++) {
...@@ -124,9 +284,9 @@ SectionId NodeStats::ComputeBiggestSection() const { ...@@ -124,9 +284,9 @@ SectionId NodeStats::ComputeBiggestSection() const {
SectionId ret = SectionId::kNone; SectionId ret = SectionId::kNone;
float max = 0.0f; float max = 0.0f;
for (auto& pair : child_stats) { for (auto& pair : child_stats) {
if (pair.second.size > max) { if (abs(pair.second.size) > max) {
ret = pair.first; ret = pair.first;
max = pair.second.size; max = abs(pair.second.size);
} }
} }
return ret; return ret;
......
...@@ -44,35 +44,38 @@ enum class SectionId : char { ...@@ -44,35 +44,38 @@ enum class SectionId : char {
struct Symbol { struct Symbol {
Symbol(); Symbol();
Symbol(const Symbol& other); Symbol(const Symbol& other);
Symbol& operator=(const Symbol& other);
float pss() const { static Symbol DiffSymbolFrom(const Symbol* before, const Symbol* after);
int alias_count = aliases ? aliases->size() : 1;
return static_cast<float>(size) / alias_count;
}
int32_t address = 0; int32_t address = 0;
int32_t size = 0; int32_t size = 0;
int32_t flags = 0; int32_t flags = 0;
int32_t padding = 0; int32_t padding = 0;
float pss = 0.0f;
SectionId sectionId = SectionId::kNone;
std::string_view full_name;
// Pointers into SizeInfo->raw_decompressed; // Pointers into SizeInfo->raw_decompressed;
const char* section_name = nullptr; const char* section_name = nullptr;
std::string_view full_name;
const char* object_path = nullptr; const char* object_path = nullptr;
const char* source_path = nullptr; const char* source_path = nullptr;
const char* component = nullptr; const char* component = nullptr;
std::vector<Symbol*>* aliases = nullptr; std::vector<Symbol*>* aliases = nullptr;
}; };
struct SizeInfo { struct BaseSizeInfo {
BaseSizeInfo();
~BaseSizeInfo();
std::vector<caspian::Symbol> raw_symbols;
Json::Value metadata;
};
struct SizeInfo : BaseSizeInfo {
SizeInfo(); SizeInfo();
~SizeInfo(); ~SizeInfo();
SizeInfo(const SizeInfo& other) = delete; SizeInfo(const SizeInfo& other) = delete;
SizeInfo& operator=(const SizeInfo& other) = delete; SizeInfo& operator=(const SizeInfo& other) = delete;
SectionId ShortSectionName(const char* section_name); SectionId ShortSectionName(const char* section_name);
std::vector<caspian::Symbol> raw_symbols;
Json::Value metadata;
// Entries in |raw_symbols| hold pointers to this data. // Entries in |raw_symbols| hold pointers to this data.
std::vector<const char*> object_paths; std::vector<const char*> object_paths;
std::vector<const char*> source_paths; std::vector<const char*> source_paths;
...@@ -84,6 +87,16 @@ struct SizeInfo { ...@@ -84,6 +87,16 @@ struct SizeInfo {
std::deque<std::vector<Symbol*>> alias_groups; std::deque<std::vector<Symbol*>> alias_groups;
}; };
struct DiffSizeInfo : BaseSizeInfo {
DiffSizeInfo(SizeInfo* before, SizeInfo* after);
~DiffSizeInfo();
DiffSizeInfo(const DiffSizeInfo&) = delete;
DiffSizeInfo& operator=(const DiffSizeInfo&) = delete;
SizeInfo* before = nullptr;
SizeInfo* after = nullptr;
};
struct Stat { struct Stat {
int32_t count = 0; int32_t count = 0;
float size = 0.0f; float size = 0.0f;
......
...@@ -41,7 +41,7 @@ std::string_view DirName(std::string_view path, char sep, char othersep) { ...@@ -41,7 +41,7 @@ std::string_view DirName(std::string_view path, char sep, char othersep) {
} // namespace } // namespace
TreeBuilder::TreeBuilder( TreeBuilder::TreeBuilder(
SizeInfo* size_info, BaseSizeInfo* size_info,
bool group_by_component, bool group_by_component,
std::vector<std::function<bool(const Symbol&)>> filters) std::vector<std::function<bool(const Symbol&)>> filters)
: size_info_(size_info), : size_info_(size_info),
...@@ -123,9 +123,8 @@ void TreeBuilder::AddFileEntry(const std::string_view source_path, ...@@ -123,9 +123,8 @@ void TreeBuilder::AddFileEntry(const std::string_view source_path,
TreeNode* symbol_node = new TreeNode(); TreeNode* symbol_node = new TreeNode();
symbol_node->container_type = ContainerType::kSymbol; symbol_node->container_type = ContainerType::kSymbol;
symbol_node->id_path = sym->full_name; symbol_node->id_path = sym->full_name;
symbol_node->size = sym->pss(); symbol_node->size = sym->pss;
symbol_node->node_stats = NodeStats( symbol_node->node_stats = NodeStats(sym->sectionId, 1, symbol_node->size);
size_info_->ShortSectionName(sym->section_name), 1, symbol_node->size);
AttachToParent(symbol_node, file_node); AttachToParent(symbol_node, file_node);
} }
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
namespace caspian { namespace caspian {
class TreeBuilder { class TreeBuilder {
public: public:
TreeBuilder(SizeInfo* size_info, TreeBuilder(BaseSizeInfo* size_info,
bool group_by_component, bool group_by_component,
std::vector<std::function<bool(const Symbol&)>> filters); std::vector<std::function<bool(const Symbol&)>> filters);
~TreeBuilder(); ~TreeBuilder();
...@@ -45,7 +45,7 @@ class TreeBuilder { ...@@ -45,7 +45,7 @@ class TreeBuilder {
// node. // node.
std::unordered_map<std::string_view, TreeNode*> _parents; std::unordered_map<std::string_view, TreeNode*> _parents;
SizeInfo* size_info_ = nullptr; BaseSizeInfo* size_info_ = nullptr;
// Contained TreeNode hold lightweight string_views to fields in SizeInfo. // Contained TreeNode hold lightweight string_views to fields in SizeInfo.
// If grouping by component, this isn't possible: TreeNode id_paths are not // If grouping by component, this isn't possible: TreeNode id_paths are not
// substrings of SizeInfo-owned strings. In that case, the strings are stored // substrings of SizeInfo-owned strings. In that case, the strings are stored
......
...@@ -16,6 +16,7 @@ class SupersizeHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler, ...@@ -16,6 +16,7 @@ class SupersizeHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler,
serve_from = None serve_from = None
# Path to data file # Path to data file
data_file_path = None data_file_path = None
before_file_path = None
#override #override
def translate_path(self, path): def translate_path(self, path):
...@@ -23,6 +24,8 @@ class SupersizeHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler, ...@@ -23,6 +24,8 @@ class SupersizeHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler,
relative_path = os.path.relpath(f, os.getcwd()) relative_path = os.path.relpath(f, os.getcwd())
if relative_path == 'data.ndjson': if relative_path == 'data.ndjson':
return SupersizeHTTPRequestHandler.data_file_path return SupersizeHTTPRequestHandler.data_file_path
if relative_path == 'before.size':
return SupersizeHTTPRequestHandler.before_file_path
else: else:
return os.path.join(SupersizeHTTPRequestHandler.serve_from, relative_path) return os.path.join(SupersizeHTTPRequestHandler.serve_from, relative_path)
...@@ -30,6 +33,13 @@ class SupersizeHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler, ...@@ -30,6 +33,13 @@ class SupersizeHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler,
def AddArguments(parser): def AddArguments(parser):
parser.add_argument('report_file', parser.add_argument('report_file',
help='Path to a custom html_report data file to load.') help='Path to a custom html_report data file to load.')
parser.add_argument(
'-b',
'--before_file',
type=str,
default='',
help=('Path to a "before" .size file to diff against. If present, '
'report_file should also be a .size file.'))
parser.add_argument('-p', '--port', type=int, default=8000, parser.add_argument('-p', '--port', type=int, default=8000,
help='Port for the HTTP server') help='Port for the HTTP server')
parser.add_argument('-a', '--address', default='localhost', parser.add_argument('-a', '--address', default='localhost',
...@@ -44,11 +54,13 @@ def Run(args, _parser): ...@@ -44,11 +54,13 @@ def Run(args, _parser):
SupersizeHTTPRequestHandler.serve_from = static_files SupersizeHTTPRequestHandler.serve_from = static_files
SupersizeHTTPRequestHandler.data_file_path = args.report_file SupersizeHTTPRequestHandler.data_file_path = args.report_file
SupersizeHTTPRequestHandler.before_file_path = args.before_file
SupersizeHTTPRequestHandler.extensions_map['.wasm'] = 'application/wasm' SupersizeHTTPRequestHandler.extensions_map['.wasm'] = 'application/wasm'
httpd = BaseHTTPServer.HTTPServer(server_addr, SupersizeHTTPRequestHandler) httpd = BaseHTTPServer.HTTPServer(server_addr, SupersizeHTTPRequestHandler)
sa = httpd.socket.getsockname() sa = httpd.socket.getsockname()
before_suffix = '&before_url=before.size&wasm=1' if args.before_file else ''
logging.warning( logging.warning(
'Server ready at http://%s:%d/viewer.html?load_url=data.ndjson', 'Server ready at http://%s:%d/viewer.html?load_url=data.ndjson' +
sa[0], sa[1]) before_suffix, sa[0], sa[1])
httpd.serve_forever() httpd.serve_forever()
...@@ -7,6 +7,13 @@ ...@@ -7,6 +7,13 @@
importScripts('./shared.js'); importScripts('./shared.js');
importScripts('./caspian_web.js'); importScripts('./caspian_web.js');
const LoadWasm = new Promise(function(resolve, reject) {
Module['onRuntimeInitialized'] = function() {
console.log('Loaded WebAssembly runtime');
resolve();
}
});
const _PATH_SEP = '/'; const _PATH_SEP = '/';
const _NAMES_TO_FLAGS = Object.freeze({ const _NAMES_TO_FLAGS = Object.freeze({
hot: _FLAGS.HOT, hot: _FLAGS.HOT,
...@@ -127,84 +134,66 @@ function mallocBuffer(buf) { ...@@ -127,84 +134,66 @@ function mallocBuffer(buf) {
return dataHeap; return dataHeap;
} }
var _Open = null; async function Open(name) {
function Open(name) { return LoadWasm.then(() => {
if (_Open === null) {
_Open = Module.cwrap('Open', 'number', ['string']); _Open = Module.cwrap('Open', 'number', ['string']);
} const stringPtr = _Open(name);
let stringPtr = _Open(name); // Something has gone wrong if we get back a string longer than 67MB.
// Something has gone wrong if we get back a string longer than 67MB. const ret = JSON.parse(Module.UTF8ToString(stringPtr, 2 ** 26));
let ret = JSON.parse(Module.UTF8ToString(stringPtr, 2 ** 26)); return ret;
console.log(ret); });
return ret;
} }
// Placeholder input name until supplied via setInput() // Placeholder input name until supplied via setInput()
const fetcher = new DataFetcher('data.size'); const fetcher = new DataFetcher('data.ndjson');
let beforeFetcher = null;
let sizeFileLoaded = false; let sizeFileLoaded = false;
async function loadSizeFile(isBefore, fetcher) {
const sizeBuffer = await fetcher.loadSizeBuffer();
const heapBuffer = mallocBuffer(sizeBuffer);
const LoadSizeFile = Module.cwrap(
isBefore ? 'LoadBeforeSizeFile' : 'LoadSizeFile', 'bool',
['number', 'number']);
const start_time = Date.now();
LoadSizeFile(heapBuffer.byteOffset, sizeBuffer.byteLength);
console.log(
'Loaded size file in ' + (Date.now() - start_time) / 1000.0 + ' seconds');
Module._free(heapBuffer.byteOffset);
}
async function buildTree( async function buildTree(
groupBy, includeRegex, excludeRegex, includeSections, minSymbolSize, groupBy, includeRegex, excludeRegex, includeSections, minSymbolSize,
flagToFilter, methodCountMode, onProgress) { flagToFilter, methodCountMode, onProgress) {
if (!sizeFileLoaded) { return await LoadWasm.then(async () => {
let sizeBuffer = await fetcher.loadSizeBuffer(); if (!sizeFileLoaded) {
let heapBuffer = mallocBuffer(sizeBuffer); const current = loadSizeFile(false, fetcher);
console.log('Passing ' + sizeBuffer.byteLength + ' bytes to WebAssembly'); const before =
let LoadSizeFile = beforeFetcher !== null ? loadSizeFile(true, beforeFetcher) : null;
Module.cwrap('LoadSizeFile', 'bool', ['number', 'number']); await current;
let start_time = Date.now(); await before;
LoadSizeFile(heapBuffer.byteOffset, sizeBuffer.byteLength); sizeFileLoaded = true;
}
const BuildTree = Module.cwrap(
'BuildTree', 'void',
['bool', 'string', 'string', 'string', 'number', 'number']);
const start_time = Date.now();
const groupByComponent = groupBy === 'component';
BuildTree(
groupByComponent, includeRegex, excludeRegex, includeSections,
minSymbolSize, flagToFilter);
console.log( console.log(
'Loaded size file in ' + (Date.now() - start_time) / 1000.0 + 'Constructed tree in ' + (Date.now() - start_time) / 1000.0 +
' seconds'); ' seconds');
Module._free(heapBuffer.byteOffset);
sizeFileLoaded = true;
}
/** const root = await Open('');
* Creates data to post to the UI thread. Defaults will be used for the root return {
* and percent values if not specified. root: root,
* @param {{root?:TreeNode,percent?:number,error?:Error}} data Default data percent: 1.0,
* values to post. diffMode: beforeFetcher !== null, // diff mode
*/
function createProgressMessage(data = {}) {
let {percent} = data;
if (percent == null) {
if (meta == null) {
percent = 0;
} else {
percent = Math.max(builder.rootNode.size / meta.total, 0.1);
}
}
const message = {
root: builder.formatNode(data.root || builder.rootNode),
percent,
diffMode: meta && meta.diff_mode,
}; };
if (data.error) { });
message.error = data.error.message;
}
return message;
}
let BuildTree = Module.cwrap(
'BuildTree', 'void',
['bool', 'string', 'string', 'string', 'number', 'number']);
let start_time = Date.now();
const groupByComponent = groupBy === 'component';
BuildTree(
groupByComponent, includeRegex, excludeRegex, includeSections,
minSymbolSize, flagToFilter);
console.log('Constructed tree in ' +
(Date.now() - start_time)/1000.0 + ' seconds');
return {
root: Open(''),
percent: 1.0,
diffMode: 0, // diff mode
};
} }
/** /**
...@@ -215,15 +204,8 @@ async function buildTree( ...@@ -215,15 +204,8 @@ async function buildTree(
function parseOptions(options) { function parseOptions(options) {
const params = new URLSearchParams(options); const params = new URLSearchParams(options);
const url = params.get('load_url');
const groupBy = params.get('group_by') || 'source_path'; const groupBy = params.get('group_by') || 'source_path';
const methodCountMode = params.has('method_count'); const methodCountMode = params.has('method_count');
const flagToFilter = _NAMES_TO_FLAGS[params.get('flag_filter')] || 0;
let minSymbolSize = Number(params.get('min_size'));
if (Number.isNaN(minSymbolSize)) {
minSymbolSize = 0;
}
const includeRegex = params.get('include'); const includeRegex = params.get('include');
const excludeRegex = params.get('exclude'); const excludeRegex = params.get('exclude');
...@@ -238,6 +220,15 @@ function parseOptions(options) { ...@@ -238,6 +220,15 @@ function parseOptions(options) {
includeSections = Array.from(includeSectionsSet.values()).join(''); includeSections = Array.from(includeSectionsSet.values()).join('');
} }
const minSymbolSize = Number(params.get('min_size'));
if (Number.isNaN(minSymbolSize)) {
minSymbolSize = 0;
}
const flagToFilter = _NAMES_TO_FLAGS[params.get('flag_filter')] || 0;
const url = params.get('load_url');
const beforeUrl = params.get('before_url');
return { return {
groupBy, groupBy,
includeRegex, includeRegex,
...@@ -246,6 +237,7 @@ function parseOptions(options) { ...@@ -246,6 +237,7 @@ function parseOptions(options) {
minSymbolSize, minSymbolSize,
flagToFilter, flagToFilter,
url, url,
beforeUrl,
}; };
} }
...@@ -260,6 +252,7 @@ const actions = { ...@@ -260,6 +252,7 @@ const actions = {
minSymbolSize, minSymbolSize,
flagToFilter, flagToFilter,
url, url,
beforeUrl,
} = parseOptions(options); } = parseOptions(options);
if (input === 'from-url://' && url) { if (input === 'from-url://' && url) {
// Display the data from the `load_url` query parameter // Display the data from the `load_url` query parameter
...@@ -270,6 +263,10 @@ const actions = { ...@@ -270,6 +263,10 @@ const actions = {
fetcher.setInput(input); fetcher.setInput(input);
} }
if (beforeUrl) {
beforeFetcher = new DataFetcher(beforeUrl);
}
return buildTree( return buildTree(
groupBy, includeRegex, excludeRegex, includeSections, minSymbolSize, groupBy, includeRegex, excludeRegex, includeSections, minSymbolSize,
flagToFilter, progress => { flagToFilter, progress => {
......
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