Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions POLICY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@
changes without a destination-frame flash. Use accessible, polished segmented
selectors with a sliding highlight and independent accents for each chart.
- Every plot point supports mouse hover with exact bytes/platform/version.
Clicking a point opens an accessible popup with the full **fbuild bloat**
Clicking a point opens an accessible popup for that graph’s region only
(Flash or RAM), backed by the full **fbuild bloat**
symbol report for that exact platform, FastLED revision and measurement run.
Keyboard users can focus a chart, navigate its points with arrow keys and
open the selected report with Enter or Space.
- Split each popup into Flash and RAM sections, independently sorted by symbol
size. Show the top five in each section, with a separate More button to reveal
all remaining symbols and an option to collapse back to the top five.
- Sort the selected region’s symbols by size. Show the top 10 on the first page,
then 50 per subsequent page, with Previous/Next controls and visible ranges.
Render only the current page; never expand hundreds of rows at once.
- Show a prominent live-site link in the README. Embed a real overview
screenshot as a noninteractive PNG, linked to the interactive Pages site.
Regenerate that screenshot whenever the daily data is published.
Regenerate that screenshot whenever the daily data is published. Also generate
an actual-site preview PNG for Open Graph and social link previews.
- Every master label must include a secondary measurement-date label converted
to the viewer's local time zone and locale, including chart axes
and report headings. Store timestamps in UTC and convert on display.
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

The image is a noninteractive screenshot of the actual Chart.js overview. Click
it for the full interactive site: hover points for bytes and click them to open
the exact platform/version fbuild bloat report.
the selected Flash or RAM symbol report for that platform/version. Reports show
the top 10 symbols, then pages of 50. [Site preview image](docs/assets/preview.png)
is also generated from the actual dashboard for social link previews.

[Benchmark policy and decisions](POLICY.md)

Expand Down
4 changes: 2 additions & 2 deletions docs/app.js

Large diffs are not rendered by default.

Binary file added docs/assets/preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 10 additions & 2 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="FastLED flash and RAM history across Blink, APA102 SPI, and Rainbow on four platforms.">
<meta property="og:title" content="FastLED · Memory history">
<meta property="og:description" content="Explore flash and RAM by sketch, platform, and release, with linked symbol reports.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://fastled.github.io/dashboard/">
<meta property="og:image" content="https://fastled.github.io/dashboard/assets/preview.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://fastled.github.io/dashboard/assets/preview.png">
<title>FastLED · Memory history</title>
<link rel="stylesheet" href="style.css?v=80b56eddf046">
<link rel="stylesheet" href="style.css?v=1cedb58cefc9">
</head>
<body>
<main>
Expand Down Expand Up @@ -32,5 +40,5 @@
<footer><p>Recomputed daily · Latest seven releases + master · <a href="data/latest.json">Download JSON ↗</a></p><p>Flash and RAM are fbuild’s board-aware ELF measurements (static RAM; runtime heap and stack excluded). Historical values may change with fbuild updates. Provenance and daily snapshots are saved with the results.</p></footer>
</main>
<dialog id="bloat-modal" aria-labelledby="bloat-title"><div class="modal-head"><h2 id="bloat-title">Bloat report</h2><button id="close-modal" aria-label="Close bloat report">×</button></div><p id="bloat-meta"></p><div id="bloat-report"></div></dialog>
<script type="module" src="app.js?v=c8c160c54656"></script>
<script type="module" src="app.js?v=b6eefdb051c2"></script>
</body></html>
2 changes: 1 addition & 1 deletion docs/style.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions scripts/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ try {
});
await page.goto(server.url, { waitUntil: "networkidle" });
await page.waitForSelector("#blink-flash canvas");
const firstSection = await page
.locator(".sketch-section")
.first()
.boundingBox();
if (!firstSection) throw new Error("Missing overview section");
await page.screenshot({
path: "docs/assets/preview.png",
fullPage: true,
clip: {
x: 0,
y: 0,
width: 1440,
height: Math.ceil(firstSection.y + firstSection.height + 20),
},
});
await page.screenshot({
path: "docs/assets/dashboard.png",
fullPage: true,
Expand Down
3 changes: 2 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ function mountSection(root: HTMLElement, sketch: Sketch, data: Dashboard) {
const p = elements[0];
openBloat(
(chart.data.datasets[p.datasetIndex] as RowDataset).rows[p.index],
metric,
);
}
},
Expand Down Expand Up @@ -236,7 +237,7 @@ function mountSection(root: HTMLElement, sketch: Sketch, data: Dashboard) {
);
} else if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
void openBloat(rows[selected]);
void openBloat(rows[selected], metric);
}
});
canvas.addEventListener("pointermove", (event) => {
Expand Down
84 changes: 55 additions & 29 deletions src/bloat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,26 @@ element("close-modal").addEventListener("click", () => modal.close());
modal.addEventListener("click", (event) => {
if (event.target === modal) modal.close();
});
export async function openBloat(row: Success | undefined) {
export async function openBloat(
row: Success | undefined,
region: "flash" | "ram",
) {
if (!row) return;
const request = ++reportRequest;
const platform = platforms.find((p) => p.id === row.board);
const title = element("bloat-title");
const sketchName = { blink: "Blink", spi: "APA102", rainbow: "Rainbow" }[
row.sketch
];
title.textContent = `${sketchName} · ${platform?.name} · ${row.version} · fbuild bloat`;
title.textContent = `${sketchName} · ${platform?.name} · ${row.version} · ${region === "flash" ? "Flash" : "RAM"} bloat`;
if (row.version === "master") {
const date = document.createElement("span");
date.className = "measurement-date";
date.textContent = localDate(row.measured_at);
title.append(date);
}
element("bloat-meta").textContent =
`SHA ${row.sha.slice(0, 10)} · fbuild ${row.fbuild} · flash ${bytes(row.flash)} · static RAM ${bytes(row.ram)} · measured ${localTimestamp(row.measured_at)}`;
`SHA ${row.sha.slice(0, 10)} · fbuild ${row.fbuild} · ${region === "flash" ? "flash" : "static RAM"} ${bytes(row[region])} · measured ${localTimestamp(row.measured_at)}`;
const container = element("bloat-report");
container.textContent = "Loading symbol report…";
if (!modal.open) modal.showModal();
Expand All @@ -48,16 +51,17 @@ export async function openBloat(row: Success | undefined) {
if (request !== reportRequest) return;
container.replaceChildren();
const summary = document.createElement("p");
summary.textContent = `Allocated image: ${bytes(report.image_flash)} · attributed flash: ${bytes(report.total_flash)} · attributed RAM: ${bytes(report.total_ram)}. Attributed totals may overlap and differ from board totals.`;
summary.textContent =
region === "flash"
? `Allocated image: ${bytes(report.image_flash)} · attributed flash: ${bytes(report.total_flash)}. Attributed totals may overlap and differ from board totals.`
: `Attributed RAM: ${bytes(report.total_ram)}. Static RAM excludes runtime heap and stack; attributed totals may differ from board totals.`;
container.append(summary);
const download = document.createElement("a");
download.href = row.bloat_report;
download.textContent = "Download full fbuild bloat JSON ↗";
container.append(download);
for (const [region, title] of [
["flash", "Flash"],
["ram", "RAM"],
] as const) {
{
const title = region === "flash" ? "Flash" : "RAM";
const symbols = report.symbols
.filter((s) => s.size > 0 && s.region === region)
.sort((a, b) => b.size - a.size);
Expand All @@ -69,7 +73,7 @@ export async function openBloat(row: Success | undefined) {
const note = document.createElement("p");
note.className = "note";
note.textContent = symbols.length
? "Largest symbols first. Showing the top five."
? "Largest symbols first. Showing the top 10."
: "No attributed symbols in this region.";
section.append(note);
const table = document.createElement("table");
Expand All @@ -88,9 +92,17 @@ export async function openBloat(row: Success | undefined) {
head.append(tr);
table.append(head);
const body = document.createElement("tbody");
const renderRows = (expanded: boolean) => {
let page = 0;
const pageCount =
symbols.length <= 10 ? 1 : 1 + Math.ceil((symbols.length - 10) / 50);
const renderRows = () => {
const fragment = document.createDocumentFragment();
(expanded ? symbols : symbols.slice(0, 5)).forEach((symbol) => {
const start = page === 0 ? 0 : 10 + (page - 1) * 50;
const end = Math.min(symbols.length, start + (page === 0 ? 10 : 50));
note.textContent = symbols.length
? `Largest symbols first · ${start + 1}–${end} of ${symbols.length.toLocaleString()} · Page ${page + 1} of ${pageCount}`
: "No attributed symbols in this region.";
symbols.slice(start, end).forEach((symbol) => {
const tr = document.createElement("tr");
[
symbol.region,
Expand All @@ -106,27 +118,41 @@ export async function openBloat(row: Success | undefined) {
});
body.replaceChildren(fragment);
};
renderRows(false);
renderRows();
table.append(body);
if (symbols.length) section.append(table);
if (symbols.length > 5) {
const more = document.createElement("button");
more.className = "bloat-more";
more.textContent = `More (${(symbols.length - 5).toLocaleString("en-US")} remaining)`;
more.setAttribute("aria-expanded", "false");
more.setAttribute("aria-controls", table.id);
more.addEventListener("click", () => {
const expanded = more.getAttribute("aria-expanded") !== "true";
renderRows(expanded);
more.setAttribute("aria-expanded", String(expanded));
more.textContent = expanded
? "Show top five"
: `More (${(symbols.length - 5).toLocaleString("en-US")} remaining)`;
note.textContent = expanded
? "Largest symbols first. Showing all symbols."
: "Largest symbols first. Showing the top five.";
if (pageCount > 1) {
const navigation = document.createElement("nav");
navigation.className = "bloat-pagination";
navigation.setAttribute("aria-label", `${title} symbol pages`);
const previous = document.createElement("button");
const next = document.createElement("button");
previous.textContent = "Previous";
next.textContent = "Next 50";
for (const button of [previous, next])
button.setAttribute("aria-controls", table.id);
const update = () => {
renderRows();
previous.disabled = page === 0;
next.disabled = page === pageCount - 1;
next.textContent =
page === pageCount - 1
? "Next"
: `Next ${Math.min(50, symbols.length - (page === 0 ? 10 : 10 + page * 50))}`;
};
previous.addEventListener("click", () => {
page--;
update();
});
section.append(more);
next.addEventListener("click", () => {
page++;
update();
});
note.setAttribute("role", "status");
note.setAttribute("aria-live", "polite");
navigation.append(previous, next);
section.append(navigation);
update();
}
container.append(section);
}
Expand Down
Loading
Loading