You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/AssetMemoryAnalyzer/www/AssetMemoryViewer/index.html

269 lines
7.8 KiB
HTML

<html>
<head>
<title>Lumberyard Asset Memory Viewer</title>
<link href="../../External/tabulator-master/dist/css/tabulator.css" rel="stylesheet">
<script src="../../External/tabulator-master/dist/js/tabulator.js"></script>
<style>
#dropzone {
position: relative;
border: 2px dashed #0087F7;
border-radius: 5px;
width: 50%;
height: 240px;
margin: 0 auto;
cursor: pointer;
}
.dz-active {
background: #CCCCCC;
}
#dz-message {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: red;
pointer-events: none;
}
html, body{
font-family: Helvetica, sans-serif;
}
.clear-button {
width: 16px;
height: 16px;
border-radius: 4px;
background-color: rgba(8, 8, 8, 0.25);
margin: 4px;
text-align: center;
}
</style>
</head>
<body>
<div id="dropzone" ondrop="dropHandler(event);" ondragenter="dragEnterHandler(event);" ondragleave="dragLeaveHandler(event);" ondragover="event.preventDefault();" onclick="loadFileClicked(event);">
<div id="dz-message">
Drop your assetmem JSON file here, or click to browse to it.
</div>
<input id="file-input" type="file" name="file-input" style="display: none;" accept=".json,.txt" onchange="loadFileSelected(event);" />
<template id="filter-template">
<div style="width: 100%; display: flex;" onclick="event.stopPropagation();">
<select id="sel-not" onchange="UpdateFilter();">
<option></option>
<option>NOT</option>
</select>
<select id="sel-function" onchange="UpdateFilter();">
</select>
<input id="txt-filter" type="text" style="flex-grow: 100" onchange="UpdateFilter();">
</input>
<div onclick="ResetFilter();" class="clear-button">
&times;
</div>
</div>
</template>
</div>
<div id="main-table">
<div>
<script>
let mainTable = null;
let mainTableFilter = null;
function OverrideTableSort(table) {
// tabulator.js doesn't innately support recursive sorting on hierarchical tables.
// This function replaces the table's sort function with one that does recursive sorting.
table.modules.sort._sortItem = function (column, dir, sortList, i) {
let self = table.modules.sort;
let recursiveSort = function(rows) {
rows.sort(function (a, b) {
let result = self._sortRow(a, b, column, dir, params);
//if results match recurse through previous searchs to be sure
if (result === 0 && i) {
for (let j = i - 1; j >= 0; j--) {
result = self._sortRow(a, b, sortList[j].column, sortList[j].dir, params);
if (result !== 0) {
break;
}
}
}
return result;
});
rows.forEach(function(row) {
let sub = row.modules.dataTree;
if (sub !== undefined && sub.children instanceof Array) {
recursiveSort(sub.children);
}
});
};
let activeRows = self.table.rowManager.activeRows;
let params = typeof column.modules.sort.params === "function" ? column.modules.sort.params(column.getComponent(), dir) : column.modules.sort.params;
recursiveSort(activeRows);
};
};
function stringToNumber(s) {
return parseFloat(s.replace(/,/g, ""));
}
function stringColumnSum(values, data, calcParams) {
return values.reduce(function(total, val) { return total + stringToNumber(val); }, 0).toLocaleString();
}
function stringColumnSort(a, b) {
return stringToNumber(a) - stringToNumber(b);
}
function UpdateFilter() {
let shouldInvert = mainTableFilter.selNot.selectedIndex > 0;
let filterOption = mainTableFilter.selFunction.options[mainTableFilter.selFunction.selectedIndex];
let filterContent = mainTableFilter.txtFilter.value;
let shouldFilter = filterContent.length > 0;
if (filterOption.text == "regex") {
try {
filterContent = new RegExp(filterContent);
}
catch (error) {
shouldFilter = false;
}
}
let filterFunction = function(data, filterParams) {
if (!shouldFilter) {
return true;
}
return filterOption.fn(data.label, filterContent) ^ shouldInvert;
};
mainTable.setFilter(filterFunction);
}
function ResetFilter() {
mainTableFilter.txtFilter.value = "";
mainTableFilter.selNot.selectedIndex = 0;
mainTableFilter.selFunction.selectedIndex = 0;
UpdateFilter();
}
function AddLabelFilter(table) {
let filterFunctions = [
['contains', function(x, y) { return x.indexOf(y) != -1; }],
['equals', function(x, y) { return x == y; }],
['startswith', function(x, y) { return x.startsWith(y); }],
['endswith', function(x, y) { return x.endsWith(y); }],
['regex', function(x, y) { return x.search(y) != -1; }]
];
let contentDiv = table.columnManager.columns[0].contentElement;
let filterTemplate = document.querySelector("#filter-template");
let importFragment = document.importNode(filterTemplate.content, true);
mainTableFilter = importFragment.querySelector("div");
// Cache the fields we will be querying for filter updates
mainTableFilter.selNot = mainTableFilter.querySelector("#sel-not");
mainTableFilter.selFunction = mainTableFilter.querySelector("#sel-function");
mainTableFilter.txtFilter = mainTableFilter.querySelector("#txt-filter");
// Add the filter functions to the dropdown
filterFunctions.forEach(function(info) {
let option = new Option(info[0]);
option.fn = info[1];
mainTableFilter.selFunction.add(option);
});
contentDiv.appendChild(mainTableFilter);
}
function loadTable(data) {
let width = 120;
let root = data[0];
let displayItems = root['_children'];
mainTable = new Tabulator("#main-table", {
height: "100%",
data: displayItems,
layout: "fitColumns",
dataTree: "true",
columns: [
{ title: "Label", field: "label" },
{ title: "Heap Allocations", columns: [
{ title: "#", field: "heap.count", sorter: "number", width: width, bottomCalc: "sum" },
{ title: "kb", field: "heap.kb", sorter: stringColumnSort, width: width, bottomCalc: stringColumnSum }
]},
{ title: "VRAM Allocations", columns: [
{ title: "#", field: "vram.count", sorter: "number", width: width, bottomCalc: "sum" },
{ title: "kb", field: "vram.kb", sorter: stringColumnSort, width: width, bottomCalc: stringColumnSum }
]}
]
});
OverrideTableSort(mainTable);
AddLabelFilter(mainTable);
// Hide the dropzone
document.querySelector("#dropzone").style.display = "none";
}
function loadFile(file) {
let reader = new FileReader();
reader.onload = function(event) {
let data = JSON.parse(event.target.result);
loadTable(data);
};
reader.readAsText(file);
}
function dragEnterHandler(event) {
event.dropEffect = "copy";
event.target.classList.add("dz-active");
event.preventDefault();
}
function dragLeaveHandler(event) {
event.target.classList.remove("dz-active");
event.preventDefault();
}
function dropHandler(event) {
if (event.dataTransfer.files.length) {
loadFile(event.dataTransfer.files[0]);
}
event.target.classList.remove("dz-active");
event.preventDefault();
}
function loadFileClicked(event) {
document.querySelector("#file-input").click();
}
function loadFileSelected(event) {
if (event.target.files.length > 0) {
loadFile(event.target.files[0]);
}
}
</script>
</body>
</html>