tiny.profiling.report.site
Defined in report.
API (7)
Actions
Public operations.
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Source
Source: src/profiling/report/root.zig:8
zig
pub const site = @import("site.zig");Source: src/profiling/report/site.zig
zig
const std = @import("std");const sys = @import("sys");const zen = @import("zen");const profiling = @import("../root.zig");const flame = @import("root.zig").flame;const model = @import("root.zig").model;const page = @import("root.zig").page;pub const style_name = "profile.css";pub const site_title = "tiny profiling";pub const Options = struct { profiling_dir: []const u8 = profiling.record.default_output_dir, output_dir: ?[]const u8 = null,};pub const Result = struct { runs: usize, pages: usize, output_dir: []const u8, index_path: []const u8,};pub const layout = "<!doctype html>\n" ++ "<html lang=\"en\">\n" ++ "<head>\n" ++ "<meta charset=\"utf-8\">\n" ++ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n" ++ "<title>{{title}} - {{site.title}}</title>\n" ++ "<meta name=\"description\" content=\"{{description}}\">\n" ++ "<link rel=\"stylesheet\" href=\"{{style.path}}\">\n" ++ "</head>\n" ++ "<body>\n" ++ "<main>\n" ++ "{{content}}\n" ++ "</main>\n" ++ "<script>\n" ++ "document.addEventListener('click', function (event) {\n" ++ " var th = event.target.closest('th[data-sort]');\n" ++ " if (!th) return;\n" ++ " var table = th.closest('table');\n" ++ " var body = table.tBodies[0];\n" ++ " if (!body) return;\n" ++ " var cells = Array.prototype.slice.call(th.parentNode.children);\n" ++ " var column = cells.indexOf(th);\n" ++ " var direction = th.getAttribute('data-dir') === 'desc' ? 1 : -1;\n" ++ " cells.forEach(function (cell) { cell.removeAttribute('data-dir'); });\n" ++ " th.setAttribute('data-dir', direction === 1 ? 'asc' : 'desc');\n" ++ " var rows = Array.prototype.slice.call(body.rows);\n" ++ " rows.sort(function (left, right) {\n" ++ " var a = left.cells[column], b = right.cells[column];\n" ++ " var av = a.getAttribute('data-v') || a.textContent.trim();\n" ++ " var bv = b.getAttribute('data-v') || b.textContent.trim();\n" ++ " var an = parseFloat(av), bn = parseFloat(bv);\n" ++ " if (isNaN(an) || isNaN(bn)) return av.localeCompare(bv) * direction;\n" ++ " return (an - bn) * direction;\n" ++ " });\n" ++ " rows.forEach(function (row) { body.appendChild(row); });\n" ++ "});\n" ++ "</script>\n" ++ "</body>\n" ++ "</html>\n";pub const style = ":root{color-scheme:light;font-family:Inter,ui-sans-serif,system-ui,sans-serif;line-height:1.5;background:#f7f7f4;color:#20231f;" ++ "--accent:#0b6bcb;--ok:#2f6f4e;--bad:#b3261e;--warn:#8a5a00;--muted:#6b7264;--edge:#d6d6ce;--panel:#fffefb}\n" ++ "body{margin:0}\n" ++ "main{max-width:1160px;margin:0 auto;padding:2.2rem 1.25rem 4rem}\n" ++ "h1{font-size:1.7rem;line-height:1.15;margin:.4rem 0 .6rem}\n" ++ "h2{font-size:1.15rem;margin:2rem 0 .4rem}\n" ++ "h3{font-size:.98rem;margin:1.2rem 0 .35rem}\n" ++ "a{color:var(--accent);text-decoration:none}\n" ++ "a:hover{text-decoration:underline}\n" ++ "code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.92em}\n" ++ ".crumbs{font-size:.85rem;color:var(--muted)}\n" ++ ".crumbs a{color:var(--muted)}\n" ++ ".lede{color:#3a4036;margin:.1rem 0 1rem}\n" ++ ".hint{font-size:.85rem;color:var(--muted);margin:.15rem 0 .6rem}\n" ++ ".muted{color:var(--muted)}\n" ++ ".verdict{padding:.6rem .8rem;border:1px solid var(--edge);background:var(--panel)}\n" ++ ".verdict.ok{border-color:#bcd9c6;background:#eef7f0}\n" ++ ".verdict.warn{border-color:#ecd9a8;background:#fdf6e3}\n" ++ ".meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,max-content));gap:.4rem 2rem;margin:.6rem 0 1rem;padding:.6rem .8rem;border:1px solid var(--edge);background:var(--panel)}\n" ++ ".meta div{min-width:0}\n" ++ ".meta dt{font-size:.72rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}\n" ++ ".meta dd{margin:0;font-variant-numeric:tabular-nums}\n" ++ "table.data{border-collapse:collapse;width:100%;margin:.4rem 0 1rem;font-size:.9rem;background:var(--panel)}\n" ++ "table.data th,table.data td{border:1px solid var(--edge);padding:.35rem .55rem;text-align:left;vertical-align:middle}\n" ++ "table.data thead th{background:#efefe9;font-weight:600;white-space:nowrap}\n" ++ "table.data th[data-sort]{cursor:pointer;user-select:none}\n" ++ "table.data th[data-sort]:after{content:' \\2195';color:#b0b4a8;font-size:.75em}\n" ++ "table.data th[data-dir=desc]:after{content:' \\2193';color:var(--accent)}\n" ++ "table.data th[data-dir=asc]:after{content:' \\2191';color:var(--accent)}\n" ++ "td.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n" ++ "td.label{max-width:26rem;overflow-wrap:anywhere}\n" ++ "td.fail{color:var(--bad);font-weight:700}\n" ++ ".bar-cell{position:relative;min-width:7rem}\n" ++ ".cellbar{position:absolute;left:0;top:0;bottom:0;background:rgba(11,107,203,.14)}\n" ++ ".cellval{position:relative}\n" ++ ".badge{display:inline-block;padding:.05rem .45rem;border-radius:99px;font-size:.72rem;font-weight:600;vertical-align:middle}\n" ++ ".badge-passed{background:#e3f2e7;color:var(--ok)}\n" ++ ".badge-failed{background:#fbe4e2;color:var(--bad)}\n" ++ ".badge-dirty{background:#fdf0d4;color:var(--warn)}\n" ++ ".delta-up{color:var(--bad);font-weight:600}\n" ++ ".delta-down{color:var(--ok);font-weight:600}\n" ++ ".delta-flat{color:var(--muted)}\n" ++ ".cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:.8rem}\n" ++ ".card{border:1px solid var(--edge);background:var(--panel);padding:.7rem .8rem}\n" ++ ".card h3{margin:0 0 .1rem;font-size:.95rem}\n" ++ ".card-meta{margin:.05rem 0 .45rem;font-size:.8rem;color:var(--muted)}\n" ++ ".trend{display:flex;align-items:center;gap:.5rem;margin:.15rem 0}\n" ++ ".trend-label{font-size:.7rem;color:var(--muted);width:2.2rem;text-align:right;text-transform:uppercase;letter-spacing:.05em}\n" ++ "svg{display:block;max-width:100%}\n" ++ "td svg{display:inline-block;vertical-align:middle}\n" ++ ".chart-line{fill:none;stroke:var(--accent);stroke-width:1.6}\n" ++ ".chart-band{fill:rgba(11,107,203,.13);stroke:none}\n" ++ ".chart-dot{fill:var(--accent)}\n" ++ ".chart-dot:hover{fill:#063e78}\n" ++ ".chart-sample{fill:rgba(32,35,31,.42)}\n" ++ ".chart-median{stroke:var(--bad);stroke-width:1.4}\n" ++ ".chart-grid{stroke:#e4e4dc;stroke-width:1}\n" ++ ".chart-tick,.chart-empty{fill:var(--muted);font-size:9.5px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n" ++ "details.log{margin:.8rem 0}\n" ++ "details.log summary{cursor:pointer;color:var(--muted)}\n" ++ "details.log pre{overflow:auto;max-height:26rem;padding:.8rem;background:#1f241f;color:#f4f6ef;font-size:.8rem}\n" ++ "svg.flame{width:100%;height:auto;background:var(--panel);border:1px solid var(--edge)}\n" ++ ".ff{cursor:pointer}\n" ++ ".ff rect{stroke:#f7f7f4;stroke-width:.4}\n" ++ ".ff text{font:11px ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;fill:#1c1e1a;pointer-events:none}\n";pub fn generate(allocator: std.mem.Allocator, options: Options) !Result { const site = try model.load(allocator, options.profiling_dir); const output_dir = options.output_dir orelse try std.fs.path.join(allocator, &.{ options.profiling_dir, "site" }); sys.fs.deleteTree(output_dir) catch {}; try sys.fs.createDirPath(output_dir); const style_path = try std.fs.path.join(allocator, &.{ output_dir, style_name }); try sys.fs.writeFile(style_path, style); var pages: usize = 0; const index_path = try std.fs.path.join(allocator, &.{ output_dir, "index.html" }); try writePage(allocator, index_path, style_name, .{ .title = "profiling", .description = "Monorepo profiling runs, trends, and recorded analyses.", .content = try page.index(allocator, site), }); pages += 1; for (site.runs) |*entry| { const run_dir = try std.fs.path.join(allocator, &.{ output_dir, "runs", entry.dir }); const workloads_dir = try std.fs.path.join(allocator, &.{ run_dir, "workloads" }); try sys.fs.createDirPath(workloads_dir); const memory_report = try profiling.report.memory.load(allocator, .{ .input = entry.root, .top = 25 }); const has_memory = memory_report.totals.traced_workloads > 0; const run_path = try std.fs.path.join(allocator, &.{ run_dir, "index.html" }); try writePage(allocator, run_path, "../../" ++ style_name, .{ .title = entry.manifest.run_id, .description = "Profiling run workloads and recorded analysis.", .content = try page.run(allocator, entry, has_memory), }); pages += 1; if (has_memory) { const memory_path = try std.fs.path.join(allocator, &.{ run_dir, page.memory_file }); const memory_title = try std.fmt.allocPrint(allocator, "{s} memory", .{entry.manifest.run_id}); try writePage(allocator, memory_path, "../../" ++ style_name, .{ .title = memory_title, .description = "Memory accountability: traces reconciled against RSS.", .content = try page.memoryPage(allocator, entry, memory_report), }); pages += 1; } for (entry.run.workloads) |*workload_row| { const flame_sources = try flame.sources(allocator, workload_row); const file = try page.workloadFile(allocator, workload_row.name); const workload_path = try std.fs.path.join(allocator, &.{ workloads_dir, file }); const title = try std.fmt.allocPrint(allocator, "{s} - {s}", .{ workload_row.name, entry.manifest.run_id }); try writePage(allocator, workload_path, "../../../" ++ style_name, .{ .title = title, .description = "Workload metrics, distributions, and logs.", .content = try page.workload(allocator, site, entry, workload_row, flame_sources), }); pages += 1; if (flame_sources.any()) { const flame_file = try page.flameFile(allocator, workload_row.name); const flame_path = try std.fs.path.join(allocator, &.{ workloads_dir, flame_file }); const flame_title = try std.fmt.allocPrint(allocator, "{s} flames - {s}", .{ workload_row.name, entry.manifest.run_id }); try writePage(allocator, flame_path, "../../../" ++ style_name, .{ .title = flame_title, .description = "On-CPU and off-CPU flame graphs.", .content = try page.flamePage(allocator, site, entry, workload_row, flame_sources), }); pages += 1; } } } return .{ .runs = site.runs.len, .pages = pages, .output_dir = output_dir, .index_path = index_path, };}const PageContent = struct { title: []const u8, description: []const u8, content: []const u8,};fn writePage(allocator: std.mem.Allocator, path: []const u8, style_rel: []const u8, content: PageContent) !void { const theme = zen.Theme{ .site_title = site_title, .layout = layout, .style_path = style_rel, .style = style, }; const theme_page = zen.Page{ .title = content.title, .description = content.description, .content = content.content, }; const plan = try zen.ThemeRenderPlan.inspect( theme, theme_page, zen.default_theme_render_limits, ); var storage = try zen.ThemeRenderStorage.init(allocator, plan.exactLimits()); defer storage.deinit(allocator); storage.activate(); const html = try zen.renderPage(&storage, theme, theme_page); defer storage.reset(); try sys.fs.writeFile(path, html);}test "site generates a navigable static tree" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const profiling_dir = ".zig-cache/profile-web-site-test"; const output_dir = ".zig-cache/profile-web-site-test-out"; defer sys.fs.deleteTree(profiling_dir) catch {}; defer sys.fs.deleteTree(output_dir) catch {}; try model.writeTestFixture(allocator, profiling_dir); const result = try generate(allocator, .{ .profiling_dir = profiling_dir, .output_dir = output_dir }); try std.testing.expectEqual(@as(usize, 2), result.runs); try std.testing.expectEqual(@as(usize, 9), result.pages); const memory_path = try std.fs.path.join(allocator, &.{ output_dir, "runs", "run-200-bbb", "memory.html" }); const memory_html = try sys.fs.readFileAlloc(allocator, memory_path, 8 * 1024 * 1024); try std.testing.expect(std.mem.indexOf(u8, memory_html, "memory accountability") != null); try std.testing.expect(std.mem.indexOf(u8, memory_html, "root/refill") != null); const index_html = try sys.fs.readFileAlloc(allocator, result.index_path, 8 * 1024 * 1024); try std.testing.expect(std.mem.indexOf(u8, index_html, "<!doctype html>") != null); try std.testing.expect(std.mem.indexOf(u8, index_html, "profile.css") != null); try std.testing.expect(std.mem.indexOf(u8, index_html, "tiny profiling") != null); const workload_path = try std.fs.path.join(allocator, &.{ output_dir, "runs", "run-200-bbb", "workloads", "gpalloc.allocator.html" }); const workload_html = try sys.fs.readFileAlloc(allocator, workload_path, 8 * 1024 * 1024); try std.testing.expect(std.mem.indexOf(u8, workload_html, "../../../profile.css") != null); try std.testing.expect(std.mem.indexOf(u8, workload_html, "Timing metrics") != null); try std.testing.expect(std.mem.indexOf(u8, workload_html, "gpalloc.allocator.flame.html#cpu") != null); const flame_path = try std.fs.path.join(allocator, &.{ output_dir, "runs", "run-200-bbb", "workloads", "gpalloc.allocator.flame.html" }); const flame_html = try sys.fs.readFileAlloc(allocator, flame_path, 8 * 1024 * 1024); try std.testing.expect(std.mem.indexOf(u8, flame_html, "flame-cpu") != null); try std.testing.expect(std.mem.indexOf(u8, flame_html, "flame-offcpu") != null); try std.testing.expect(std.mem.indexOf(u8, flame_html, "refill") != null); try std.testing.expect(std.mem.indexOf(u8, flame_html, "futex_wait") != null); try std.testing.expect(std.mem.indexOf(u8, flame_html, "callchain samples") != null); try std.testing.expect(std.mem.indexOf(u8, flame_html, "resolved frames") != null); const style_path = try std.fs.path.join(allocator, &.{ output_dir, style_name }); try std.testing.expect(sys.fs.exists(style_path));}test "site generates an empty shell when no runs exist" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const allocator = arena_state.allocator(); const profiling_dir = ".zig-cache/profile-web-site-empty"; const output_dir = ".zig-cache/profile-web-site-empty-out"; defer sys.fs.deleteTree(profiling_dir) catch {}; defer sys.fs.deleteTree(output_dir) catch {}; try sys.fs.createDirPath(profiling_dir); const result = try generate(allocator, .{ .profiling_dir = profiling_dir, .output_dir = output_dir }); try std.testing.expectEqual(@as(usize, 0), result.runs); try std.testing.expectEqual(@as(usize, 1), result.pages); const index_html = try sys.fs.readFileAlloc(allocator, result.index_path, 1024 * 1024); try std.testing.expect(std.mem.indexOf(u8, index_html, "No profiling runs found") != null);}Complete call list for report.site.generate
11 direct calls.
tiny.profiling.report.flame.sources[function] atsrc/profiling/report/flame.zig:55tiny.profiling.report.memory.load[function] atsrc/profiling/report/memory.zig:119tiny.profiling.report.model.load[function] atsrc/profiling/report/model.zig:87tiny.profiling.report.page.flameFile[function] atsrc/profiling/report/page.zig:2325tiny.profiling.report.page.flamePage[function] atsrc/profiling/report/page.zig:2126tiny.profiling.report.page.index[function] atsrc/profiling/report/page.zig:40tiny.profiling.report.page.memoryPage[function] atsrc/profiling/report/page.zig:238tiny.profiling.report.page.run[function] atsrc/profiling/report/page.zig:220tiny.profiling.report.page.workload[function] atsrc/profiling/report/page.zig:1006tiny.profiling.report.page.workloadFile[function] atsrc/profiling/report/page.zig:2319src.profiling.report.site.writePage[function] — private; no exact target atsrc/profiling/report/site.zig:218in nearest public ownertiny.profiling.report.site
Audit
| Definitions | 8 |
|---|---|
| Public names | 11 |
| Members | 6 |
| Version | 26.7.0 |
| Revision | daab053ee433 |