aboutsummaryrefslogtreecommitdiff
path: root/src/zen/frontend/html/stats.js
blob: 741ad7ef912d9a041303f4d3daaaa92ee5314685 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// Copyright Epic Games, Inc. All Rights Reserved.
// Sortable stats table view.

const US_PER_MS = 1000;

function escapeHtml(s) {
	return String(s).replace(/[&<>"']/g, (c) => ({
		"&": "&amp;",
		"<": "&lt;",
		">": "&gt;",
		"\"": "&quot;",
		"'": "&#39;",
	}[c]));
}

export class StatsView {
	constructor(tbody, headerRow, model, onSelect) {
		this.tbody = tbody;
		this.headerRow = headerRow;
		this.stats = model.scopeStats.slice();
		this.onSelect = onSelect;
		this.sortKey = "count";
		this.sortAsc = false;
		this.selectedName = null;

		for (const th of headerRow.querySelectorAll("th[data-sort]")) {
			th.addEventListener("click", () => this.handleSort(th.dataset.sort));
		}
		this.render();
	}

	handleSort(key) {
		if (this.sortKey === key) {
			this.sortAsc = !this.sortAsc;
		} else {
			this.sortKey = key;
			this.sortAsc = key === "name";
		}
		this.render();
	}

	selectByName(name) {
		this.selectedName = name;
		for (const tr of this.tbody.querySelectorAll("tr")) {
			tr.classList.toggle("selected", tr.dataset.name === name);
			if (tr.dataset.name === name) {
				tr.scrollIntoView({ block: "nearest" });
			}
		}
	}

	render() {
		const key = this.sortKey;
		const asc = this.sortAsc;
		this.stats.sort((a, b) => {
			const av = a[key];
			const bv = b[key];
			if (typeof av === "string") {
				return asc ? av.localeCompare(bv) : bv.localeCompare(av);
			}
			return asc ? av - bv : bv - av;
		});

		for (const th of this.headerRow.querySelectorAll("th[data-sort]")) {
			th.classList.toggle("sorted", th.dataset.sort === key);
			th.classList.toggle("asc", th.dataset.sort === key && asc);
		}

		const rows = [];
		for (const stat of this.stats) {
			const selected = stat.name === this.selectedName ? " class=\"selected\"" : "";
			rows.push(
				`<tr data-name="${escapeHtml(stat.name)}"${selected}>` +
				`<td>${escapeHtml(stat.name)}</td>` +
				`<td class="num">${stat.count.toLocaleString()}</td>` +
				`<td class="num">${(stat.min_us / US_PER_MS).toFixed(3)}</td>` +
				`<td class="num">${(stat.mean_us / US_PER_MS).toFixed(3)}</td>` +
				`<td class="num">${(stat.max_us / US_PER_MS).toFixed(3)}</td>` +
				`<td class="num">${(stat.stdev_us / US_PER_MS).toFixed(3)}</td>` +
				`</tr>`,
			);
		}
		this.tbody.innerHTML = rows.join("");

		for (const tr of this.tbody.querySelectorAll("tr")) {
			tr.addEventListener("click", () => {
				const name = tr.dataset.name;
				this.selectByName(name);
				if (this.onSelect) {
					this.onSelect(name);
				}
			});
		}
	}
}