Commit b4abaad4 authored by Tiger Oakes's avatar Tiger Oakes Committed by Commit Bot

SuperSize: Adds highlights for certain flags

Adds flags to the data.ndjson file, and exposed the information in the
UI using custom highlighting options. The UI will additionally list the
flags in the symbol infocard.

Bug: 847599
Change-Id: If2c7b7f5147296cf13e0c0ef9c36d6748820e42f
Reviewed-on: https://chromium-review.googlesource.com/1148795
Commit-Queue: Tiger Oakes <tigero@google.com>
Reviewed-by: default avatarSam Maier <smaier@chromium.org>
Reviewed-by: default avatarEric Stevenson <estevenson@chromium.org>
Cr-Commit-Position: refs/heads/master@{#577923}
parent 020df651
...@@ -27,6 +27,7 @@ _COMPACT_SYMBOL_NAME_KEY = 'n' ...@@ -27,6 +27,7 @@ _COMPACT_SYMBOL_NAME_KEY = 'n'
_COMPACT_SYMBOL_BYTE_SIZE_KEY = 'b' _COMPACT_SYMBOL_BYTE_SIZE_KEY = 'b'
_COMPACT_SYMBOL_TYPE_KEY = 't' _COMPACT_SYMBOL_TYPE_KEY = 't'
_COMPACT_SYMBOL_COUNT_KEY = 'u' _COMPACT_SYMBOL_COUNT_KEY = 'u'
_COMPACT_SYMBOL_FLAGS_KEY = 'f'
_SMALL_SYMBOL_DESCRIPTIONS = { _SMALL_SYMBOL_DESCRIPTIONS = {
'b': 'Other small uninitialized data', 'b': 'Other small uninitialized data',
...@@ -34,7 +35,6 @@ _SMALL_SYMBOL_DESCRIPTIONS = { ...@@ -34,7 +35,6 @@ _SMALL_SYMBOL_DESCRIPTIONS = {
'r': 'Other small readonly data', 'r': 'Other small readonly data',
't': 'Other small code', 't': 'Other small code',
'v': 'Other small vtable entries', 'v': 'Other small vtable entries',
'*': 'Other small generated symbols',
'x': 'Other small dex non-method entries', 'x': 'Other small dex non-method entries',
'm': 'Other small dex methods', 'm': 'Other small dex methods',
'p': 'Other small locale pak entries', 'p': 'Other small locale pak entries',
...@@ -49,8 +49,6 @@ def _GetSymbolType(symbol): ...@@ -49,8 +49,6 @@ def _GetSymbolType(symbol):
symbol_type = symbol.section symbol_type = symbol.section
if symbol.name.endswith('[vtable]'): if symbol.name.endswith('[vtable]'):
symbol_type = _SYMBOL_TYPE_VTABLE symbol_type = _SYMBOL_TYPE_VTABLE
elif symbol.name.endswith(']'):
symbol_type = _SYMBOL_TYPE_GENERATED
if symbol_type not in _SMALL_SYMBOL_DESCRIPTIONS: if symbol_type not in _SMALL_SYMBOL_DESCRIPTIONS:
symbol_type = _SYMBOL_TYPE_OTHER symbol_type = _SYMBOL_TYPE_OTHER
return symbol_type return symbol_type
...@@ -147,6 +145,8 @@ def _MakeTreeViewList(symbols, include_all_symbols): ...@@ -147,6 +145,8 @@ def _MakeTreeViewList(symbols, include_all_symbols):
# so this data is only included for methods. # so this data is only included for methods.
if is_dex_method and symbol_count != 1: if is_dex_method and symbol_count != 1:
symbol_entry[_COMPACT_SYMBOL_COUNT_KEY] = symbol_count symbol_entry[_COMPACT_SYMBOL_COUNT_KEY] = symbol_count
if symbol.flags:
symbol_entry[_COMPACT_SYMBOL_FLAGS_KEY] = symbol.flags
file_node[_COMPACT_FILE_SYMBOLS_KEY].append(symbol_entry) file_node[_COMPACT_FILE_SYMBOLS_KEY].append(symbol_entry)
for symbol in extra_symbols: for symbol in extra_symbols:
......
...@@ -138,6 +138,9 @@ FLAG_CLONE = 64 ...@@ -138,6 +138,9 @@ FLAG_CLONE = 64
# which was removed by name normalization. Occurs when an AFDO profile is # which was removed by name normalization. Occurs when an AFDO profile is
# supplied to the linker. # supplied to the linker.
FLAG_HOT = 128 FLAG_HOT = 128
# Relevant for .text symbols. If a method has this flag, then it was run
# according to the code coverage.
FLAG_COVERED = 256
DIFF_STATUS_UNCHANGED = 0 DIFF_STATUS_UNCHANGED = 0
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
align-items: center; align-items: center;
} }
.appbar-progress { .appbar-progress {
box-sizing: border-box;
display: block; display: block;
width: 100%; width: 100%;
height: 4px; height: 4px;
...@@ -140,6 +141,7 @@ ...@@ -140,6 +141,7 @@
.icon { .icon {
display: block; display: block;
margin-right: 6px; margin-right: 6px;
flex: none;
} }
/** Tree nodes */ /** Tree nodes */
...@@ -179,13 +181,13 @@ ...@@ -179,13 +181,13 @@
.symbol-name { .symbol-name {
font-weight: 500; font-weight: 500;
word-break: break-word; word-break: break-word;
flex: 1;
} }
.count, .count,
.size, .size,
.percent { .percent {
margin-left: 16px; margin-left: auto;
padding-left: 16px;
text-align: right; text-align: right;
color: #5f6368; color: #5f6368;
white-space: nowrap; white-space: nowrap;
...@@ -200,6 +202,16 @@ ...@@ -200,6 +202,16 @@
text-decoration: line-through; text-decoration: line-through;
} }
.highlight {
background: #feefc3;
}
.highlight-positive {
background: #ceead6;
}
.highlight-negative {
background: #fad2cf;
}
.diff .size-header::after { .diff .size-header::after {
content: " diff"; content: " diff";
} }
...@@ -220,6 +232,7 @@ ...@@ -220,6 +232,7 @@
<body> <body>
<div class="scrim toggle-options" hidden></div> <div class="scrim toggle-options" hidden></div>
<!-- App Toolbar -->
<header class="appbar"> <header class="appbar">
<div class="appbar-inner"> <div class="appbar-inner">
<h1 class="headline">Super Size Tiger View</h1> <h1 class="headline">Super Size Tiger View</h1>
...@@ -234,7 +247,8 @@ ...@@ -234,7 +247,8 @@
</svg> </svg>
<span class="label-text">Upload data</span> <span class="label-text">Upload data</span>
</label> </label>
<a href="https://chromium.googlesource.com/chromium/src/+/master/tools/binary_size/html_report_faq.md" class="icon-button" title="FAQ" id="faq"> <a href="https://chromium.googlesource.com/chromium/src/+/master/tools/binary_size/html_report_faq.md" class="icon-button"
title="FAQ" id="faq">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#5f6368"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#5f6368">
<path d="M11,18h2v-2h-2V18z M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z M12,20c-4.41,0-8-3.59-8-8 <path d="M11,18h2v-2h-2V18z M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z M12,20c-4.41,0-8-3.59-8-8
s3.59-8,8-8s8,3.59,8,8S16.41,20,12,20z M12,6c-2.21,0-4,1.79-4,4h2c0-1.1,0.9-2,2-2s2,0.9,2,2c0,2-3,1.75-3,5h2c0-2.25,3-2.5,3-5 s3.59-8,8-8s8,3.59,8,8S16.41,20,12,20z M12,6c-2.21,0-4,1.79-4,4h2c0-1.1,0.9-2,2-2s2,0.9,2,2c0,2-3,1.75-3,5h2c0-2.25,3-2.5,3-5
...@@ -264,7 +278,7 @@ ...@@ -264,7 +278,7 @@
</div> </div>
<progress value="0" id="progress" class="appbar-progress"></progress> <progress value="0" id="progress" class="appbar-progress"></progress>
</header> </header>
<link href="options.css" rel="stylesheet"> <!-- Options side panel -->
<form id="options" class="options" method="GET"> <form id="options" class="options" method="GET">
<header class="form-bar"> <header class="form-bar">
<button type="button" class="icon-button toggle-options" title="Close"> <button type="button" class="icon-button toggle-options" title="Close">
...@@ -319,9 +333,9 @@ ...@@ -319,9 +333,9 @@
<p class="input-error" id="include-error"></p> <p class="input-error" id="include-error"></p>
</div> </div>
<div class="input-wrapper"> <div class="input-wrapper">
<input class="input-regex" type="text" id="excluderegex" name="exclude" placeholder="\(.+\)" aria-describedby="exclude-error"> <input class="input-regex" type="text" id="excluderegex" name="exclude" placeholder="\(.+\)" aria-describedby="exclude-error">
<label class="input-label" for="excluderegex"> <label class="input-label" for="excluderegex">
Symbols must exclude Symbols must exclude
</label> </label>
<p class="input-error" id="exclude-error"></p> <p class="input-error" id="exclude-error"></p>
</div> </div>
...@@ -359,12 +373,6 @@ ...@@ -359,12 +373,6 @@
Vtable entries Vtable entries
</label> </label>
</div> </div>
<div class="checkbox-wrapper">
<input type="checkbox" id="filtergen" name="type" value="*" checked>
<label class="checkbox-label" for="filtergen">
Generated Symbols (typeinfo, thunks, etc)
</label>
</div>
<div class="checkbox-wrapper"> <div class="checkbox-wrapper">
<input type="checkbox" id="filterdexnon" name="type" value="x" checked> <input type="checkbox" id="filterdexnon" name="type" value="x" checked>
<label class="checkbox-label" for="filterdexnon"> <label class="checkbox-label" for="filterdexnon">
...@@ -398,8 +406,34 @@ ...@@ -398,8 +406,34 @@
<button type="button" class="text-button" id="type-all">Select all</button> <button type="button" class="text-button" id="type-all">Select all</button>
<button type="button" class="text-button" id="type-none">Select none</button> <button type="button" class="text-button" id="type-none">Select none</button>
</fieldset> </fieldset>
<fieldset id="highlight-container">
<legend class="subhead">Highlight symbols</legend>
<div class="radio-wrapper">
<input type="radio" id="clearhighlight" name="highlight" value="clear" checked>
<label class="radio-label" for="clearhighlight">None</label>
</div>
<div class="radio-wrapper">
<input type="radio" id="hothighlight" name="highlight" value="hot">
<label class="radio-label" for="hothighlight">Hot code</label>
</div>
<div class="radio-wrapper">
<input type="radio" id="generatedhighlight" name="highlight" value="generated">
<label class="radio-label" for="generatedhighlight">Generated files</label>
</div>
<div class="radio-wrapper">
<input type="radio" id="coveragehightlight" name="highlight" value="coverage">
<label class="radio-label" for="coveragehightlight">Code coverage</label>
</div>
</fieldset>
<div class="checkbox-wrapper">
<input type="checkbox" id="generatedfilter" name="generated_filter" value="on">
<label class="checkbox-label" for="generatedfilter">Show only generated files</label>
</div>
</form> </form>
<div class="symbols"> <div class="symbols">
<!-- Icons for symbols are stored here and cloned. -->
<div hidden id="icons"> <div hidden id="icons">
<svg class="icon foldericon" height="24" width="24" fill="#5f6368"> <svg class="icon foldericon" height="24" width="24" fill="#5f6368">
<title>Directory</title> <title>Directory</title>
...@@ -449,11 +483,6 @@ ...@@ -449,11 +483,6 @@
<path d="M20,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h15c1.1,0,2-0.9,2-2V5C22,3.9,21.1,3,20,3z M20,5v3H5V5H20z M15,19h-5v-9h5V19z <path d="M20,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h15c1.1,0,2-0.9,2-2V5C22,3.9,21.1,3,20,3z M20,5v3H5V5H20z M15,19h-5v-9h5V19z
M5,10h3v9H5V10z M17,19v-9h3v9H17z" /> M5,10h3v9H5V10z M17,19v-9h3v9H17z" />
</svg> </svg>
<svg class="icon generatedicon" height="24" width="24" fill="#f439a0">
<title>Generated symbol</title>
<path d="M6 2a2 2 0 0 0-2 2v16c0 1.1.9 2 2 2h12a2 2 0 0 0 2-2V8l-6-6H6zm0 2h7v5h5v11H6V4zm9.5 8l-.6 1.4-1.4.6 1.4.6.6 1.4.6-1.4 1.4-.6-1.4-.6-.6-1.4zm-6 1l-1 2-2 1 2 1 1 2 1-2 2-1-2-1-1-2z"
/>
</svg>
<svg class="icon dexicon" height="24" width="24" fill="#ea4335"> <svg class="icon dexicon" height="24" width="24" fill="#ea4335">
<title>Dex non-method entry</title> <title>Dex non-method entry</title>
<path d="M6.6 1.44l-.82.83 2.1 2.1A6.96 6.96 0 0 0 5 10v1h14v-1a6.96 6.96 0 0 0-2.88-5.63l2.1-2.1-.82-.83-2.3 2.31a6.81 6.81 0 0 0-6.19 0L6.6 1.44zM9 7a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1zm6 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1zM5 12v4.27C5 20 8.17 23 12 23s7-3 7-6.73V12H5zm2 2h10v2.27c0 2.6-2.2 4.73-5 4.73s-5-2.13-5-4.73V14z" <path d="M6.6 1.44l-.82.83 2.1 2.1A6.96 6.96 0 0 0 5 10v1h14v-1a6.96 6.96 0 0 0-2.88-5.63l2.1-2.1-.82-.83-2.3 2.31a6.81 6.81 0 0 0-6.19 0L6.6 1.44zM9 7a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1zm6 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1zM5 12v4.27C5 20 8.17 23 12 23s7-3 7-6.73V12H5zm2 2h10v2.27c0 2.6-2.2 4.73-5 4.73s-5-2.13-5-4.73V14z"
...@@ -480,6 +509,7 @@ ...@@ -480,6 +509,7 @@
/> />
</svg> </svg>
</div> </div>
<!-- Template for trees and leaves -->
<template id="treenode-container"> <template id="treenode-container">
<li role="treeitem" aria-expanded="false" aria-describedby="infocard-container"> <li role="treeitem" aria-expanded="false" aria-describedby="infocard-container">
<a class="node" href="#" tabindex="-1" role="presentation"> <a class="node" href="#" tabindex="-1" role="presentation">
...@@ -497,6 +527,7 @@ ...@@ -497,6 +527,7 @@
</span> </span>
</li> </li>
</template> </template>
<!-- Tree view -->
<main class="tree-container"> <main class="tree-container">
<header class="tree-header"> <header class="tree-header">
<span class="subtitle">Name</span> <span class="subtitle">Name</span>
...@@ -504,6 +535,7 @@ ...@@ -504,6 +535,7 @@
</header> </header>
<ul id="symboltree" class="tree" role="tree" aria-labelledby="headline"></ul> <ul id="symboltree" class="tree" role="tree" aria-labelledby="headline"></ul>
</main> </main>
<!-- Symbol and container breakdown cards -->
<link href="infocard.css" rel="stylesheet"> <link href="infocard.css" rel="stylesheet">
<footer class="infocards"> <footer class="infocards">
<div class="infocard infocard-container open" id="infocard-container" hidden> <div class="infocard infocard-container open" id="infocard-container" hidden>
...@@ -514,11 +546,12 @@ ...@@ -514,11 +546,12 @@
<header class="header-info"> <header class="header-info">
<h4 class="subhead size-info"></h4> <h4 class="subhead size-info"></h4>
<p class="body-2 path-info"></p> <p class="body-2 path-info"></p>
<p class="caption type-info"></p> <p class="caption type-info type-info-container"></p>
</header> </header>
<table class="type-breakdown-info"> <table class="type-breakdown-info">
<thead> <thead>
<tr> <tr>
<th class="subhead-2">Icon</th>
<th class="subhead-2">Type</th> <th class="subhead-2">Type</th>
<th class="subhead-2 count">Count</th> <th class="subhead-2 count">Count</th>
<th class="subhead-2 size">Total size</th> <th class="subhead-2 size">Total size</th>
...@@ -527,66 +560,118 @@ ...@@ -527,66 +560,118 @@
</thead> </thead>
<tbody> <tbody>
<tr class="bss-info"> <tr class="bss-info">
<td>
<svg class="icon" viewBox="0 0 24 24" height="18" width="18" fill="#a142f4">
<path d="M6 2a2 2 0 0 0-2 2v16c0 1.1.9 2 2 2h12a2 2 0 0 0 2-2V8l-6-6H6zm0 2h7v5h5v11H6V4zm4 6v4h2v-4h-2zm0 6v2h2v-2h-2z"
/>
</svg>
</td>
<th scope="row">.bss</th> <th scope="row">.bss</th>
<td class="count"></td> <td class="count"></td>
<td class="size"></td> <td class="size"></td>
<td class="percent"></td> <td class="percent"></td>
</tr> </tr>
<tr class="data-info"> <tr class="data-info">
<td>
<svg class="icon" viewBox="0 0 24 24" height="18" width="18" fill="#fa7b17">
<path d="M6 2a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6H6m0 2h7v5h5v11H6V4m2 8v2h8v-2H8m0 4v2h5v-2z" />
</svg>
</td>
<th scope="row">.data and .data.*</th> <th scope="row">.data and .data.*</th>
<td class="count"></td> <td class="count"></td>
<td class="size"></td> <td class="size"></td>
<td class="percent"></td> <td class="percent"></td>
</tr> </tr>
<tr class="rodata-info"> <tr class="rodata-info">
<td>
<svg class="icon" viewBox="0 0 24 24" height="18" width="18" fill="#24c1e0">
<path d="M6 2a2 2 0 0 0-2 2v16c0 1.1.9 2 2 2h12a2 2 0 0 0 2-2V8l-6-6H6zm0 2h7v5h5v11H6V4zm5.9 8c-2 0-3.7 1.2-4.4 3a4.7 4.7 0 0 0 8.8 0c-.7-1.8-2.4-3-4.4-3zm0 1a2 2 0 0 1 2 2 2 2 0 0 1-2 2 2 2 0 0 1-2-2 2 2 0 0 1 2-2zm0 .8a1.2 1.2 0 0 0-1.2 1.2 1.2 1.2 0 0 0 1.2 1.2 1.2 1.2 0 0 0 1.2-1.2 1.2 1.2 0 0 0-1.2-1.2z"
/>
</svg>
</td>
<th scope="row">.rodata</th> <th scope="row">.rodata</th>
<td class="count"></td> <td class="count"></td>
<td class="size"></td> <td class="size"></td>
<td class="percent"></td> <td class="percent"></td>
</tr> </tr>
<tr class="text-info"> <tr class="text-info">
<td>
<svg class="icon" viewBox="0 0 24 24" height="18" width="18" fill="#1a73e8">
<path d="M9.4,16.6L4.8,12l4.6-4.6L8,6l-6,6l6,6L9.4,16.6z M14.6,16.6l4.6-4.6l-4.6-4.6L16,6l6,6l-6,6L14.6,16.6z" />
</svg>
</td>
<th scope="row">.text</th> <th scope="row">.text</th>
<td class="count"></td> <td class="count"></td>
<td class="size"></td> <td class="size"></td>
<td class="percent"></td> <td class="percent"></td>
</tr> </tr>
<tr class="vtable-info"> <tr class="vtable-info">
<td>
<svg class="icon" viewBox="0 0 24 24" height="18" width="18" fill="#fbbc04">
<path d="M20,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h15c1.1,0,2-0.9,2-2V5C22,3.9,21.1,3,20,3z M20,5v3H5V5H20z M15,19h-5v-9h5V19z
M5,10h3v9H5V10z M17,19v-9h3v9H17z" />
</svg>
</td>
<th scope="row">Vtable entry</th> <th scope="row">Vtable entry</th>
<td class="count"></td> <td class="count"></td>
<td class="size"></td> <td class="size"></td>
<td class="percent"></td> <td class="percent"></td>
</tr> </tr>
<tr class="gen-info">
<th scope="row">Generated symbols</th>
<td class="count"></td>
<td class="size"></td>
<td class="percent"></td>
</tr>
<tr class="dexnon-info"> <tr class="dexnon-info">
<td>
<svg class="icon" viewBox="0 0 24 24" height="18" width="18" fill="#ea4335">
<path d="M6.6 1.44l-.82.83 2.1 2.1A6.96 6.96 0 0 0 5 10v1h14v-1a6.96 6.96 0 0 0-2.88-5.63l2.1-2.1-.82-.83-2.3 2.31a6.81 6.81 0 0 0-6.19 0L6.6 1.44zM9 7a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1zm6 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1zM5 12v4.27C5 20 8.17 23 12 23s7-3 7-6.73V12H5zm2 2h10v2.27c0 2.6-2.2 4.73-5 4.73s-5-2.13-5-4.73V14z"
/>
</svg>
</td>
<th scope="row">Dex non-method entries</th> <th scope="row">Dex non-method entries</th>
<td class="count"></td> <td class="count"></td>
<td class="size"></td> <td class="size"></td>
<td class="percent"></td> <td class="percent"></td>
</tr> </tr>
<tr class="dex-info"> <tr class="dex-info">
<td>
<svg class="icon" viewBox="0 0 24 24" height="18" width="18" fill="#a50e0e">
<path d="M6.6 1.44l-.82.83 2.1 2.1A6.96 6.96 0 0 0 5 10v1h14v-1a6.96 6.96 0 0 0-2.88-5.63l2.1-2.1-.82-.83-2.3 2.31a6.81 6.81 0 0 0-6.19 0L6.6 1.44zM9 7a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1zm6 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1zM5 12v4.27C5 20 8.17 23 12 23s7-3 7-6.73V12H5zm2 2h10v2.27c0 2.6-2.2 4.73-5 4.73s-5-2.13-5-4.73V14z"
/>
</svg>
</td>
<th scope="row">Dex methods</th> <th scope="row">Dex methods</th>
<td class="count"></td> <td class="count"></td>
<td class="size"></td> <td class="size"></td>
<td class="percent"></td> <td class="percent"></td>
</tr> </tr>
<tr class="pak-info"> <tr class="pak-info">
<td>
<svg class="icon" viewBox="0 0 24 24" height="18" width="18" fill="#34a853">
<path d="M5 3a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5zm0 2h5v2h2V5h7v14H5V5zm7 2v2h2V7h-2zm0 2h-2v2h2V9zm0 2v2h2v-2h-2zm0 2h-2v2h2v-2zm0 2v2h2v-2h-2z"
/>
</svg>
</td>
<th scope="row">Locale pak entries</th> <th scope="row">Locale pak entries</th>
<td class="count"></td> <td class="count"></td>
<td class="size"></td> <td class="size"></td>
<td class="percent"></td> <td class="percent"></td>
</tr> </tr>
<tr class="paknon-info"> <tr class="paknon-info">
<td>
<svg class="icon" viewBox="0 0 24 24" height="18" width="18" fill="#0d652d">
<path d="M5 3a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5zm0 2h5v2h2V5h7v14H5V5zm7 2v2h2V7h-2zm0 2h-2v2h2V9zm0 2v2h2v-2h-2zm0 2h-2v2h2v-2zm0 2v2h2v-2h-2z"
/>
</svg>
</td>
<th scope="row">Non-locale pak entries</th> <th scope="row">Non-locale pak entries</th>
<td class="count"></td> <td class="count"></td>
<td class="size"></td> <td class="size"></td>
<td class="percent"></td> <td class="percent"></td>
</tr> </tr>
<tr class="other-info"> <tr class="other-info">
<td>
<svg class="icon" viewBox="0 0 24 24" height="18" width="18" fill="#5f6368">
<path d="M10.88 2l-.85 1.36L4 13h6v-2H7.6l3.28-5.23L12.28 8h2.35l-3.75-6zM12 10v10h10V10H12zm2 2h6v6h-6v-6zM2.21 15A5.52 5.52 0 0 0 10 21.4v-2.45A3.48 3.48 0 0 1 7.5 20a3.48 3.48 0 0 1-3.15-5H2.2z"
/>
</svg>
</td>
<th scope="row">Other entries</th> <th scope="row">Other entries</th>
<td class="count"></td> <td class="count"></td>
<td class="size"></td> <td class="size"></td>
...@@ -596,14 +681,17 @@ ...@@ -596,14 +681,17 @@
</table> </table>
</div> </div>
<div class="infocard infocard-symbol" id="infocard-symbol" hidden> <div class="infocard infocard-symbol" id="infocard-symbol" hidden>
<div class="icon-info"> <div class="icon-info">
<div></div> <div></div>
</div> </div>
<header class="header-info"> <header class="header-info">
<h4 class="subhead size-info"></h4> <h4 class="subhead size-info"></h4>
<p class="body-2 path-info"></p> <p class="body-2 path-info"></p>
</header> </header>
<p class="caption type-info"></p> <p class="caption type-info-container">
<span class="type-info"></span>
<span class="flags-info"></span>
</p>
</div> </div>
</footer> </footer>
</div> </div>
......
...@@ -14,6 +14,18 @@ ...@@ -14,6 +14,18 @@
const displayInfocard = (() => { const displayInfocard = (() => {
const _CANVAS_RADIUS = 40; const _CANVAS_RADIUS = 40;
const _FLAG_LABELS = new Map([
[_FLAGS.ANONYMOUS, 'anon'],
[_FLAGS.STARTUP, 'startup'],
[_FLAGS.UNLIKELY, 'unlikely'],
[_FLAGS.REL, 'rel'],
[_FLAGS.REL_LOCAL, 'rel.loc'],
[_FLAGS.GENERATED_SOURCE, 'gen'],
[_FLAGS.CLONE, 'clone'],
[_FLAGS.HOT, 'hot'],
[_FLAGS.COVERAGE, 'covered'],
]);
class Infocard { class Infocard {
/** /**
* @param {string} id * @param {string} id
...@@ -26,7 +38,7 @@ const displayInfocard = (() => { ...@@ -26,7 +38,7 @@ const displayInfocard = (() => {
this._pathInfo = this._infocard.querySelector('.path-info'); this._pathInfo = this._infocard.querySelector('.path-info');
/** @type {HTMLDivElement} */ /** @type {HTMLDivElement} */
this._iconInfo = this._infocard.querySelector('.icon-info'); this._iconInfo = this._infocard.querySelector('.icon-info');
/** @type {HTMLParagraphElement} */ /** @type {HTMLSpanElement} */
this._typeInfo = this._infocard.querySelector('.type-info'); this._typeInfo = this._infocard.querySelector('.type-info');
/** /**
...@@ -136,6 +148,12 @@ const displayInfocard = (() => { ...@@ -136,6 +148,12 @@ const displayInfocard = (() => {
} }
class SymbolInfocard extends Infocard { class SymbolInfocard extends Infocard {
constructor(id) {
super(id);
/** @type {HTMLSpanElement} */
this._flagsInfo = this._infocard.querySelector('.flags-info');
}
/** /**
* @param {SVGSVGElement} icon Icon to display * @param {SVGSVGElement} icon Icon to display
*/ */
...@@ -144,6 +162,31 @@ const displayInfocard = (() => { ...@@ -144,6 +162,31 @@ const displayInfocard = (() => {
super._setTypeContent(icon); super._setTypeContent(icon);
this._iconInfo.style.backgroundColor = color; this._iconInfo.style.backgroundColor = color;
} }
/**
* Updates the DOM for the info card.
* @param {TreeNode} node
*/
_updateInfocard(node) {
super._updateInfocard(node);
this._flagsInfo.textContent = this._flagsString(node);
}
/**
* Returns a string representing the flags in the node.
* @param {TreeNode} symbolNode
*/
_flagsString(symbolNode) {
if (!symbolNode.flags) {
return '';
}
const flagsString = Array.from(_FLAG_LABELS)
.filter(([flag]) => hasFlag(flag, symbolNode))
.map(([, part]) => part)
.join(',');
return `{${flagsString}}`;
}
} }
class ContainerInfocard extends Infocard { class ContainerInfocard extends Infocard {
...@@ -162,7 +205,6 @@ const displayInfocard = (() => { ...@@ -162,7 +205,6 @@ const displayInfocard = (() => {
r: this._tableBody.querySelector('.rodata-info'), r: this._tableBody.querySelector('.rodata-info'),
t: this._tableBody.querySelector('.text-info'), t: this._tableBody.querySelector('.text-info'),
v: this._tableBody.querySelector('.vtable-info'), v: this._tableBody.querySelector('.vtable-info'),
'*': this._tableBody.querySelector('.gen-info'),
x: this._tableBody.querySelector('.dexnon-info'), x: this._tableBody.querySelector('.dexnon-info'),
m: this._tableBody.querySelector('.dex-info'), m: this._tableBody.querySelector('.dex-info'),
p: this._tableBody.querySelector('.pak-info'), p: this._tableBody.querySelector('.pak-info'),
...@@ -272,7 +314,7 @@ const displayInfocard = (() => { ...@@ -272,7 +314,7 @@ const displayInfocard = (() => {
* @param {TreeNode} containerNode * @param {TreeNode} containerNode
*/ */
_updateInfocard(containerNode) { _updateInfocard(containerNode) {
const extraRows = {...this._infoRows}; const extraRows = Object.assign({}, this._infoRows);
const statsEntries = Object.entries(containerNode.childStats).sort( const statsEntries = Object.entries(containerNode.childStats).sort(
(a, b) => b[1].size - a[1].size (a, b) => b[1].size - a[1].size
); );
......
...@@ -78,7 +78,7 @@ ...@@ -78,7 +78,7 @@
.symbol-name-info { .symbol-name-info {
font-weight: 500; font-weight: 500;
} }
.type-info { .type-info-container {
grid-area: type; grid-area: type;
margin-bottom: 0; margin-bottom: 0;
} }
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
* @prop {number} size Byte size of this node and its children. * @prop {number} size Byte size of this node and its children.
* @prop {string} type Type of this node. If this node has children, the string * @prop {string} type Type of this node. If this node has children, the string
* may have a second character to denote the most common child. * may have a second character to denote the most common child.
* @prop {number} flags
* @prop {{[type: string]: {size:number,count:number}}} childStats Stats about * @prop {{[type: string]: {size:number,count:number}}} childStats Stats about
* this node's descendants, organized by symbol type. * this node's descendants, organized by symbol type.
*/ */
...@@ -57,6 +58,20 @@ const _KEYS = Object.freeze({ ...@@ -57,6 +58,20 @@ const _KEYS = Object.freeze({
SIZE: /** @type {'b'} */ ('b'), SIZE: /** @type {'b'} */ ('b'),
TYPE: /** @type {'t'} */ ('t'), TYPE: /** @type {'t'} */ ('t'),
COUNT: /** @type {'u'} */ ('u'), COUNT: /** @type {'u'} */ ('u'),
FLAGS: /** @type {'f'} */ ('f'),
});
/** Abberivated keys used by FileEntrys in the JSON data file. */
const _FLAGS = Object.freeze({
ANONYMOUS: 2 ** 0,
STARTUP: 2 ** 1,
UNLIKELY: 2 ** 2,
REL: 2 ** 3,
REL_LOCAL: 2 ** 4,
GENERATED_SOURCE: 2 ** 5,
CLONE: 2 ** 6,
HOT: 2 ** 7,
COVERAGE: 2 ** 8,
}); });
/** /**
...@@ -69,8 +84,6 @@ const _BYTE_UNITS = Object.freeze({ ...@@ -69,8 +84,6 @@ const _BYTE_UNITS = Object.freeze({
KiB: 1024 ** 1, KiB: 1024 ** 1,
B: 1024 ** 0, B: 1024 ** 0,
}); });
/** Set of all byte units */
const _BYTE_UNITS_SET = new Set(Object.keys(_BYTE_UNITS));
/** /**
* Special types used by containers, such as folders and files. * Special types used by containers, such as folders and files.
...@@ -83,13 +96,15 @@ const _CONTAINER_TYPES = { ...@@ -83,13 +96,15 @@ const _CONTAINER_TYPES = {
}; };
const _CONTAINER_TYPE_SET = new Set(Object.values(_CONTAINER_TYPES)); const _CONTAINER_TYPE_SET = new Set(Object.values(_CONTAINER_TYPES));
/** Type for a code/.text symbol */
const _CODE_SYMBOL_TYPE = 't';
/** Type for a dex method symbol */ /** Type for a dex method symbol */
const _DEX_METHOD_SYMBOL_TYPE = 'm'; const _DEX_METHOD_SYMBOL_TYPE = 'm';
/** Type for an 'other' symbol */ /** Type for an 'other' symbol */
const _OTHER_SYMBOL_TYPE = 'o'; const _OTHER_SYMBOL_TYPE = 'o';
/** Set of all known symbol types. Container types are not included. */ /** Set of all known symbol types. Container types are not included. */
const _SYMBOL_TYPE_SET = new Set('bdrtv*xmpP' + _OTHER_SYMBOL_TYPE); const _SYMBOL_TYPE_SET = new Set('bdrtvxmpP' + _OTHER_SYMBOL_TYPE);
/** Name used by a directory created to hold symbols with no name. */ /** Name used by a directory created to hold symbols with no name. */
const _NO_NAME = '(No path)'; const _NO_NAME = '(No path)';
...@@ -133,9 +148,18 @@ function* types(typesList) { ...@@ -133,9 +148,18 @@ function* types(typesList) {
function debounce(func, wait) { function debounce(func, wait) {
/** @type {number} */ /** @type {number} */
let timeoutId; let timeoutId;
function debounced (...args) { function debounced(...args) {
clearTimeout(timeoutId); clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), wait); timeoutId = setTimeout(() => func(...args), wait);
}; }
return /** @type {any} */ (debounced); return /** @type {any} */ (debounced);
} }
/**
* Returns tree if a symbol has a certain bit flag
* @param {number} flag Bit flag from `_FLAGS`
* @param {TreeNode} symbolNode
*/
function hasFlag(flag, symbolNode) {
return (symbolNode.flags & flag) === flag;
}
...@@ -68,44 +68,11 @@ function _initState() { ...@@ -68,44 +68,11 @@ function _initState() {
const state = Object.freeze({ const state = Object.freeze({
/** /**
* Returns a string from the current query string state. * Returns a string from the current query string state.
* Can optionally restrict valid values for the query.
* Values not present in the query will return null, or the default
* value if supplied.
* @param {string} key * @param {string} key
* @param {object} [options]
* @param {string} [options.default] Default to use if key is not present
* in the state
* @param {Set<string>} [options.valid] If provided, values must be in this
* set to be returned. Invalid values will return null or `defaultValue`.
* @returns {string | null} * @returns {string | null}
*/ */
get(key, options = {}) { get(key) {
const [val = null] = state.getAll(key, { return _filterParams.get(key);
default: options.default ? [options.default] : null,
valid: options.valid,
});
return val;
},
/**
* Returns all string values for a key from the current query string state.
* Can optionally provide default values used if there are no values.
* @param {string} key
* @param {object} [options]
* @param {string[]} [options.default] Default to use if key is not present
* in the state.
* @param {Set<string>} [options.valid] If provided, values must be in this
* set to be returned. Invalid values will be omitted.
* @returns {string[]}
*/
getAll(key, options = {}) {
let vals = _filterParams.getAll(key);
if (options.valid != null) {
vals = vals.filter(val => options.valid.has(val));
}
if (options.default != null && vals.length === 0) {
vals = options.default;
}
return vals;
}, },
/** /**
* Checks if a key is present in the query string state. * Checks if a key is present in the query string state.
...@@ -140,7 +107,7 @@ function _initState() { ...@@ -140,7 +107,7 @@ function _initState() {
}); });
// Update form inputs to reflect the state from URL. // Update form inputs to reflect the state from URL.
for (const element of form.elements) { for (const element of Array.from(form.elements)) {
if (element.name) { if (element.name) {
const input = /** @type {HTMLInputElement} */ (element); const input = /** @type {HTMLInputElement} */ (element);
const values = _filterParams.getAll(input.name); const values = _filterParams.getAll(input.name);
...@@ -370,12 +337,15 @@ function _makeSizeTextGetter() { ...@@ -370,12 +337,15 @@ function _makeSizeTextGetter() {
}; };
} else { } else {
const bytes = node.size; const bytes = node.size;
const unit = state.get('byteunit', { let unit = state.get('byteunit');
default: 'MiB', let suffix = _BYTE_UNITS[unit];
valid: _BYTE_UNITS_SET, if (suffix == null) {
}); unit = 'MiB';
suffix = _BYTE_UNITS.MiB;
}
// Format the bytes as a number with 2 digits after the decimal point // Format the bytes as a number with 2 digits after the decimal point
const text = (bytes / _BYTE_UNITS[unit]).toLocaleString(_LOCALE, { const text = (bytes / suffix).toLocaleString(_LOCALE, {
minimumFractionDigits: 2, minimumFractionDigits: 2,
maximumFractionDigits: 2, maximumFractionDigits: 2,
}); });
......
...@@ -24,6 +24,9 @@ const newTreeElement = (() => { ...@@ -24,6 +24,9 @@ const newTreeElement = (() => {
const _treeTemplate = document.getElementById('treenode-container'); const _treeTemplate = document.getElementById('treenode-container');
const _symbolTree = document.getElementById('symboltree'); const _symbolTree = document.getElementById('symboltree');
const _highlightRadio = document.getElementById('highlight-container');
/** @type {HTMLSelectElement} */
const _byteunitSelect = form.elements.namedItem('byteunit');
/** /**
* @type {HTMLCollectionOf<HTMLAnchorElement | HTMLSpanElement>} * @type {HTMLCollectionOf<HTMLAnchorElement | HTMLSpanElement>}
...@@ -44,6 +47,57 @@ const newTreeElement = (() => { ...@@ -44,6 +47,57 @@ const newTreeElement = (() => {
*/ */
const _uiNodeData = new WeakMap(); const _uiNodeData = new WeakMap();
/**
* @type {{[mode:string]: (nameEl: HTMLSpanElement, node: TreeNode) => void}}
*/
const _highlightActions = {
hot(symbolName, symbolNode) {
if (hasFlag(_FLAGS.HOT, symbolNode)) {
symbolName.classList.add('highlight');
}
},
generated(symbolName, symbolNode) {
if (hasFlag(_FLAGS.GENERATED_SOURCE, symbolNode)) {
symbolName.classList.add('highlight');
}
},
coverage(symbolName, symbolNode) {
if (symbolNode.type === _CODE_SYMBOL_TYPE) {
if (hasFlag(_FLAGS.COVERAGE, symbolNode)) {
symbolName.classList.add('highlight-positive');
} else {
symbolName.classList.add('highlight-negative');
}
}
},
reset(symbolName) {
symbolName.classList.remove('highlight');
symbolName.classList.remove('highlight-positive');
symbolName.classList.remove('highlight-negative');
},
};
/**
* Applies highlights to the tree element based on certain flags and state.
* @param {HTMLSpanElement} symbolNameElement
* @param {TreeNode} symbolNode
*/
function _highlightSymbolName(symbolNameElement, symbolNode) {
const isDexMethod = symbolNode.type === _DEX_METHOD_SYMBOL_TYPE;
if (isDexMethod && state.has('method_count')) {
const {count = 0} = symbolNode.childStats[_DEX_METHOD_SYMBOL_TYPE] || {};
if (count < 0) {
symbolNameElement.classList.add('removed');
}
}
const hightlightFunc = _highlightActions[state.get('highlight')];
_highlightActions.reset(symbolNameElement, symbolNode);
if (hightlightFunc) {
hightlightFunc(symbolNameElement, symbolNode);
}
}
/** /**
* Sets focus to a new tree element while updating the element that last had * Sets focus to a new tree element while updating the element that last had
* focus. The tabindex property is used to avoid needing to tab through every * focus. The tabindex property is used to avoid needing to tab through every
...@@ -51,6 +105,7 @@ const newTreeElement = (() => { ...@@ -51,6 +105,7 @@ const newTreeElement = (() => {
* @param {number | HTMLElement} el Index of tree node in `_liveNodeList` * @param {number | HTMLElement} el Index of tree node in `_liveNodeList`
*/ */
function _focusTreeElement(el) { function _focusTreeElement(el) {
/** @type {HTMLElement} */
const lastFocused = document.activeElement; const lastFocused = document.activeElement;
if (_uiNodeData.has(lastFocused)) { if (_uiNodeData.has(lastFocused)) {
// Update DOM // Update DOM
...@@ -86,7 +141,9 @@ const newTreeElement = (() => { ...@@ -86,7 +141,9 @@ const newTreeElement = (() => {
let data = _uiNodeData.get(link); let data = _uiNodeData.get(link);
if (data == null || data.children == null) { if (data == null || data.children == null) {
const idPath = link.querySelector('.symbol-name').title; /** @type {HTMLSpanElement} */
const symbolName = link.querySelector('.symbol-name');
const idPath = symbolName.title;
data = await worker.openNode(idPath); data = await worker.openNode(idPath);
_uiNodeData.set(link, data); _uiNodeData.set(link, data);
} }
...@@ -95,7 +152,9 @@ const newTreeElement = (() => { ...@@ -95,7 +152,9 @@ const newTreeElement = (() => {
if (newElements.length === 1) { if (newElements.length === 1) {
// Open the inner element if it only has a single child. // Open the inner element if it only has a single child.
// Ensures nodes like "java"->"com"->"google" are opened all at once. // Ensures nodes like "java"->"com"->"google" are opened all at once.
newElements[0].querySelector('.node').click(); /** @type {HTMLAnchorElement | HTMLSpanElement} */
const link = newElements[0].querySelector('.node');
link.click();
} }
const newElementsFragment = dom.createFragment(newElements); const newElementsFragment = dom.createFragment(newElements);
...@@ -185,7 +244,9 @@ const newTreeElement = (() => { ...@@ -185,7 +244,9 @@ const newTreeElement = (() => {
const groupList = link.parentElement.parentElement; const groupList = link.parentElement.parentElement;
if (groupList.getAttribute('role') === 'group') { if (groupList.getAttribute('role') === 'group') {
event.preventDefault(); event.preventDefault();
_focusTreeElement(groupList.previousElementSibling); /** @type {HTMLAnchorElement} */
const parentLink = groupList.previousElementSibling;
_focusTreeElement(parentLink);
} }
} }
} }
...@@ -292,13 +353,7 @@ const newTreeElement = (() => { ...@@ -292,13 +353,7 @@ const newTreeElement = (() => {
_ZERO_WIDTH_SPACE _ZERO_WIDTH_SPACE
); );
symbolName.title = data.idPath; symbolName.title = data.idPath;
_highlightSymbolName(symbolName, data);
if (state.has('method_count') && type === _DEX_METHOD_SYMBOL_TYPE) {
const {count = 0} = data.childStats[type] || {};
if (count < 0) {
symbolName.classList.add('removed');
}
}
// Set the byte size and hover text // Set the byte size and hover text
_setSize(element.querySelector('.size'), data); _setSize(element.querySelector('.size'), data);
...@@ -312,7 +367,7 @@ const newTreeElement = (() => { ...@@ -312,7 +367,7 @@ const newTreeElement = (() => {
} }
// When the `byteunit` state changes, update all .size elements in the page // When the `byteunit` state changes, update all .size elements in the page
form.elements.namedItem('byteunit').addEventListener('change', event => { _byteunitSelect.addEventListener('change', event => {
event.stopPropagation(); event.stopPropagation();
state.set(event.currentTarget.name, event.currentTarget.value); state.set(event.currentTarget.name, event.currentTarget.value);
// Update existing size elements with the new unit // Update existing size elements with the new unit
...@@ -321,6 +376,19 @@ const newTreeElement = (() => { ...@@ -321,6 +376,19 @@ const newTreeElement = (() => {
if (data) _setSize(sizeElement, data); if (data) _setSize(sizeElement, data);
} }
}); });
_highlightRadio.addEventListener('change', event => {
event.stopPropagation();
state.set(event.target.name, event.target.value);
// Update existing symbol elements
for (const link of _liveNodeList) {
const data = _uiNodeData.get(link);
if (data.children && data.children.length === 0) {
/** @type {HTMLSpanElement} */
const symbolName = link.querySelector('.symbol-name');
_highlightSymbolName(symbolName, data);
}
}
});
_symbolTree.addEventListener('keydown', _handleKeyNavigation); _symbolTree.addEventListener('keydown', _handleKeyNavigation);
_symbolTree.addEventListener('focusin', event => { _symbolTree.addEventListener('focusin', event => {
...@@ -337,7 +405,7 @@ const newTreeElement = (() => { ...@@ -337,7 +405,7 @@ const newTreeElement = (() => {
} }
}); });
return newTreeElement return newTreeElement;
})(); })();
{ {
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
* @prop {string} t Single character string to indicate the symbol type. * @prop {string} t Single character string to indicate the symbol type.
* @prop {number} [u] Count value indicating how many symbols this entry * @prop {number} [u] Count value indicating how many symbols this entry
* represents. Negative value when removed in a diff. * represents. Negative value when removed in a diff.
* @prop {number} [f] Bit flags, defaults to 0.
*/ */
/** /**
* @typedef {object} FileEntry JSON object representing a single file and its * @typedef {object} FileEntry JSON object representing a single file and its
...@@ -82,7 +83,14 @@ function _compareFunc(a, b) { ...@@ -82,7 +83,14 @@ function _compareFunc(a, b) {
* @returns {TreeNode} * @returns {TreeNode}
*/ */
function createNode(options) { function createNode(options) {
const {idPath, type, shortNameIndex, size = 0, childStats = {}} = options; const {
idPath,
type,
shortNameIndex,
size = 0,
flags = 0,
childStats = {},
} = options;
return { return {
children: [], children: [],
parent: null, parent: null,
...@@ -91,6 +99,7 @@ function createNode(options) { ...@@ -91,6 +99,7 @@ function createNode(options) {
shortNameIndex, shortNameIndex,
size, size,
type, type,
flags,
}; };
} }
...@@ -143,13 +152,14 @@ class TreeBuilder { ...@@ -143,13 +152,14 @@ class TreeBuilder {
const additionalSize = node.size; const additionalSize = node.size;
const additionalStats = Object.entries(node.childStats); const additionalStats = Object.entries(node.childStats);
const additionalFlags = node.flags;
// Update the size and childStats of all ancestors // Update the size and childStats of all ancestors
while (node.parent != null) { while (node.parent != null) {
const {parent} = node; const {parent} = node;
const [containerType, lastBiggestType] = parent.type; const [containerType, lastBiggestType] = parent.type;
let {size: lastBiggestSize = 0} = let {size: lastBiggestSize = 0} =
parent.childStats[lastBiggestType] || {}; parent.childStats[lastBiggestType] || {};
for (const [type, stat] of additionalStats) { for (const [type, stat] of additionalStats) {
const parentStat = parent.childStats[type] || {size: 0, count: 0}; const parentStat = parent.childStats[type] || {size: 0, count: 0};
...@@ -165,6 +175,7 @@ class TreeBuilder { ...@@ -165,6 +175,7 @@ class TreeBuilder {
} }
parent.size += additionalSize; parent.size += additionalSize;
parent.flags |= additionalFlags;
node = parent; node = parent;
} }
} }
...@@ -257,15 +268,16 @@ class TreeBuilder { ...@@ -257,15 +268,16 @@ class TreeBuilder {
// If there is 1 child, include it so the UI doesn't need to make a // If there is 1 child, include it so the UI doesn't need to make a
// roundtrip in order to expand the chain. // roundtrip in order to expand the chain.
children = node.children children = node.children
.map(n => TreeBuilder.formatNode(n, childDepth)) .map(n => TreeBuilder.formatNode(n, childDepth))
.sort(_compareFunc); .sort(_compareFunc);
} }
return TreeBuilder._joinDexMethodClasses({ return TreeBuilder._joinDexMethodClasses(
...node, Object.assign({}, node, {
children, children,
parent: null, parent: null,
}); })
);
} }
/** /**
...@@ -344,7 +356,8 @@ class TreeBuilder { ...@@ -344,7 +356,8 @@ class TreeBuilder {
idPath: `${idPath}:${symbol[_KEYS.SYMBOL_NAME]}`, idPath: `${idPath}:${symbol[_KEYS.SYMBOL_NAME]}`,
shortNameIndex: idPath.length + 1, shortNameIndex: idPath.length + 1,
size, size,
type: symbol[_KEYS.TYPE], type,
flags: symbol[_KEYS.FLAGS] || 0,
childStats: {[type]: {size, count}}, childStats: {[type]: {size, count}},
}); });
...@@ -548,6 +561,7 @@ function parseOptions(options) { ...@@ -548,6 +561,7 @@ function parseOptions(options) {
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 filterGeneratedFiles = params.has('generated_filter');
let minSymbolSize = Number(params.get('min_size')); let minSymbolSize = Number(params.get('min_size'));
if (Number.isNaN(minSymbolSize)) { if (Number.isNaN(minSymbolSize)) {
...@@ -569,19 +583,28 @@ function parseOptions(options) { ...@@ -569,19 +583,28 @@ function parseOptions(options) {
} }
} }
/** @type {Array<(symbolNode: TreeNode) => boolean>} */ /**
* @type {Array<(symbolNode: TreeNode) => boolean>} List of functions that
* check each symbol. If any returns false, the symbol will not be used.
*/
const filters = []; const filters = [];
/** Ensure symbol size is past the minimum */ // Ensure symbol size is past the minimum
if (minSymbolSize > 0) { if (minSymbolSize > 0) {
filters.push(s => Math.abs(s.size) >= minSymbolSize); filters.push(s => Math.abs(s.size) >= minSymbolSize);
} }
/** Ensure the symbol size wasn't filtered out */ // Ensure the symbol size wasn't filtered out
if (typeFilter.size < _SYMBOL_TYPE_SET.size) { if (typeFilter.size < _SYMBOL_TYPE_SET.size) {
filters.push(s => typeFilter.has(s.type)); filters.push(s => typeFilter.has(s.type));
} }
// Only show generated files
if (filterGeneratedFiles) {
filters.push(s => hasFlag(_FLAGS.GENERATED_SOURCE, s));
}
// Search symbol names using regex
if (includeRegex) { if (includeRegex) {
try { try {
const regex = new RegExp(includeRegex); const regex = new RegExp(includeRegex);
...@@ -692,7 +715,7 @@ async function buildTree(options, onProgress) { ...@@ -692,7 +715,7 @@ async function buildTree(options, onProgress) {
const currentTime = Date.now(); const currentTime = Date.now();
if (currentTime - lastBatchSent > 500) { if (currentTime - lastBatchSent > 500) {
postToUi(); postToUi();
await Promise.resolve(); // Pause loop to check for worker messages await Promise.resolve(); // Pause loop to check for worker messages
lastBatchSent = currentTime; lastBatchSent = currentTime;
} }
} }
......
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