| 1 | const std = @import("std"); |
| 2 | const gpa = std.heap.wasm_allocator; |
| 3 | const abi = std.Build.abi.time_report; |
| 4 | const fmtEscapeHtml = @import("root").fmtEscapeHtml; |
| 5 | const step_list = &@import("root").step_list; |
| 6 | |
| 7 | const js = struct { |
| 8 | extern "time_report" fn updateGeneric( |
| 9 | /// The index of the step. |
| 10 | step_idx: u32, |
| 11 | // The HTML which will be used to populate the template slots. |
| 12 | inner_html_ptr: [*]const u8, |
| 13 | inner_html_len: usize, |
| 14 | ) void; |
| 15 | extern "time_report" fn updateCompile( |
| 16 | /// The index of the step. |
| 17 | step_idx: u32, |
| 18 | // The HTML which will be used to populate the template slots. |
| 19 | inner_html_ptr: [*]const u8, |
| 20 | inner_html_len: usize, |
| 21 | // The HTML which will populate the <tbody> of the file table. |
| 22 | file_table_html_ptr: [*]const u8, |
| 23 | file_table_html_len: usize, |
| 24 | // The HTML which will populate the <tbody> of the decl table. |
| 25 | decl_table_html_ptr: [*]const u8, |
| 26 | decl_table_html_len: usize, |
| 27 | /// Whether the LLVM backend was used. If not, LLVM-specific statistics are hidden. |
| 28 | use_llvm: bool, |
| 29 | ) void; |
| 30 | extern "time_report" fn updateRunTest( |
| 31 | /// The index of the step. |
| 32 | step_idx: u32, |
| 33 | // The HTML which will populate the <tbody> of the test table. |
| 34 | table_html_ptr: [*]const u8, |
| 35 | table_html_len: usize, |
| 36 | ) void; |
| 37 | }; |
| 38 | |
| 39 | pub fn genericResultMessage(msg_bytes: []u8) error{OutOfMemory}!void { |
| 40 | if (msg_bytes.len != @sizeOf(abi.GenericResult)) @panic("malformed GenericResult message"); |
| 41 | const msg: *const abi.GenericResult = @ptrCast(msg_bytes); |
| 42 | if (msg.step_idx >= step_list.*.len) @panic("malformed GenericResult message"); |
| 43 | const inner_html = try std.fmt.allocPrint(gpa, |
| 44 | \\<code slot="step-name">{[step_name]f}</code> |
| 45 | \\<span slot="stat-total-time">{[stat_total_time]f}</span> |
| 46 | , .{ |
| 47 | .step_name = fmtEscapeHtml(step_list.*[msg.step_idx].name), |
| 48 | .stat_total_time = std.Io.Duration{ .nanoseconds = msg.ns_total }, |
| 49 | }); |
| 50 | defer gpa.free(inner_html); |
| 51 | js.updateGeneric(msg.step_idx, inner_html.ptr, inner_html.len); |
| 52 | } |
| 53 | |
| 54 | pub fn compileResultMessage(msg_bytes: []u8) error{ OutOfMemory, WriteFailed }!void { |
| 55 | const max_table_rows = 500; |
| 56 | |
| 57 | if (msg_bytes.len < @sizeOf(abi.CompileResult)) @panic("malformed CompileResult message"); |
| 58 | const hdr: *const abi.CompileResult = @ptrCast(msg_bytes[0..@sizeOf(abi.CompileResult)]); |
| 59 | if (hdr.step_idx >= step_list.*.len) @panic("malformed CompileResult message"); |
| 60 | var trailing = msg_bytes[@sizeOf(abi.CompileResult)..]; |
| 61 | |
| 62 | const llvm_pass_timings = trailing[0..hdr.llvm_pass_timings_len]; |
| 63 | trailing = trailing[hdr.llvm_pass_timings_len..]; |
| 64 | |
| 65 | const FileTimeReport = struct { |
| 66 | name: []const u8, |
| 67 | ns_sema: u64, |
| 68 | ns_codegen: u64, |
| 69 | ns_link: u64, |
| 70 | }; |
| 71 | const DeclTimeReport = struct { |
| 72 | file_name: []const u8, |
| 73 | name: []const u8, |
| 74 | sema_count: u32, |
| 75 | ns_sema: u64, |
| 76 | ns_codegen: u64, |
| 77 | ns_link: u64, |
| 78 | }; |
| 79 | |
| 80 | const slowest_files = try gpa.alloc(FileTimeReport, hdr.files_len); |
| 81 | defer gpa.free(slowest_files); |
| 82 | |
| 83 | const slowest_decls = try gpa.alloc(DeclTimeReport, hdr.decls_len); |
| 84 | defer gpa.free(slowest_decls); |
| 85 | |
| 86 | for (slowest_files) |*file_out| { |
| 87 | const i = std.mem.findScalar(u8, trailing, 0) orelse @panic("malformed CompileResult message"); |
| 88 | file_out.* = .{ |
| 89 | .name = trailing[0..i], |
| 90 | .ns_sema = 0, |
| 91 | .ns_codegen = 0, |
| 92 | .ns_link = 0, |
| 93 | }; |
| 94 | trailing = trailing[i + 1 ..]; |
| 95 | } |
| 96 | |
| 97 | for (slowest_decls) |*decl_out| { |
| 98 | const i = std.mem.findScalar(u8, trailing, 0) orelse @panic("malformed CompileResult message"); |
| 99 | const file_idx = std.mem.readInt(u32, trailing[i..][1..5], .little); |
| 100 | const sema_count = std.mem.readInt(u32, trailing[i..][5..9], .little); |
| 101 | const sema_ns = std.mem.readInt(u64, trailing[i..][9..17], .little); |
| 102 | const codegen_ns = std.mem.readInt(u64, trailing[i..][17..25], .little); |
| 103 | const link_ns = std.mem.readInt(u64, trailing[i..][25..33], .little); |
| 104 | const file = &slowest_files[file_idx]; |
| 105 | decl_out.* = .{ |
| 106 | .file_name = file.name, |
| 107 | .name = trailing[0..i], |
| 108 | .sema_count = sema_count, |
| 109 | .ns_sema = sema_ns, |
| 110 | .ns_codegen = codegen_ns, |
| 111 | .ns_link = link_ns, |
| 112 | }; |
| 113 | trailing = trailing[i + 33 ..]; |
| 114 | file.ns_sema += sema_ns; |
| 115 | file.ns_codegen += codegen_ns; |
| 116 | file.ns_link += link_ns; |
| 117 | } |
| 118 | |
| 119 | const S = struct { |
| 120 | fn fileLessThan(_: void, lhs: FileTimeReport, rhs: FileTimeReport) bool { |
| 121 | const lhs_ns = lhs.ns_sema + lhs.ns_codegen + lhs.ns_link; |
| 122 | const rhs_ns = rhs.ns_sema + rhs.ns_codegen + rhs.ns_link; |
| 123 | return lhs_ns > rhs_ns; // flipped to sort in reverse order |
| 124 | } |
| 125 | fn declLessThan(_: void, lhs: DeclTimeReport, rhs: DeclTimeReport) bool { |
| 126 | //if (true) return lhs.sema_count > rhs.sema_count; |
| 127 | const lhs_ns = lhs.ns_sema + lhs.ns_codegen + lhs.ns_link; |
| 128 | const rhs_ns = rhs.ns_sema + rhs.ns_codegen + rhs.ns_link; |
| 129 | return lhs_ns > rhs_ns; // flipped to sort in reverse order |
| 130 | } |
| 131 | }; |
| 132 | std.mem.sort(FileTimeReport, slowest_files, {}, S.fileLessThan); |
| 133 | std.mem.sort(DeclTimeReport, slowest_decls, {}, S.declLessThan); |
| 134 | |
| 135 | const stats = hdr.stats; |
| 136 | const inner_html = try std.fmt.allocPrint(gpa, |
| 137 | \\<code slot="step-name">{[step_name]f}</code> |
| 138 | \\<span slot="stat-reachable-files">{[stat_reachable_files]d}</span> |
| 139 | \\<span slot="stat-imported-files">{[stat_imported_files]d}</span> |
| 140 | \\<span slot="stat-generic-instances">{[stat_generic_instances]d}</span> |
| 141 | \\<span slot="stat-inline-calls">{[stat_inline_calls]d}</span> |
| 142 | \\<span slot="stat-compilation-time">{[stat_compilation_time]f}</span> |
| 143 | \\<span slot="cpu-time-parse">{[cpu_time_parse]f}</span> |
| 144 | \\<span slot="cpu-time-astgen">{[cpu_time_astgen]f}</span> |
| 145 | \\<span slot="cpu-time-sema">{[cpu_time_sema]f}</span> |
| 146 | \\<span slot="cpu-time-codegen">{[cpu_time_codegen]f}</span> |
| 147 | \\<span slot="cpu-time-link">{[cpu_time_link]f}</span> |
| 148 | \\<span slot="real-time-files">{[real_time_files]f}</span> |
| 149 | \\<span slot="real-time-decls">{[real_time_decls]f}</span> |
| 150 | \\<span slot="real-time-llvm-emit">{[real_time_llvm_emit]f}</span> |
| 151 | \\<span slot="real-time-link-flush">{[real_time_link_flush]f}</span> |
| 152 | \\<pre slot="llvm-pass-timings"><code>{[llvm_pass_timings]f}</code></pre> |
| 153 | \\ |
| 154 | , .{ |
| 155 | .step_name = fmtEscapeHtml(step_list.*[hdr.step_idx].name), |
| 156 | .stat_reachable_files = stats.n_reachable_files, |
| 157 | .stat_imported_files = stats.n_imported_files, |
| 158 | .stat_generic_instances = stats.n_generic_instances, |
| 159 | .stat_inline_calls = stats.n_inline_calls, |
| 160 | .stat_compilation_time = std.Io.Duration{ .nanoseconds = hdr.ns_total }, |
| 161 | |
| 162 | .cpu_time_parse = std.Io.Duration{ .nanoseconds = stats.cpu_ns_parse }, |
| 163 | .cpu_time_astgen = std.Io.Duration{ .nanoseconds = stats.cpu_ns_astgen }, |
| 164 | .cpu_time_sema = std.Io.Duration{ .nanoseconds = stats.cpu_ns_sema }, |
| 165 | .cpu_time_codegen = std.Io.Duration{ .nanoseconds = stats.cpu_ns_codegen }, |
| 166 | .cpu_time_link = std.Io.Duration{ .nanoseconds = stats.cpu_ns_link }, |
| 167 | .real_time_files = std.Io.Duration{ .nanoseconds = stats.real_ns_files }, |
| 168 | .real_time_decls = std.Io.Duration{ .nanoseconds = stats.real_ns_decls }, |
| 169 | .real_time_llvm_emit = std.Io.Duration{ .nanoseconds = stats.real_ns_llvm_emit }, |
| 170 | .real_time_link_flush = std.Io.Duration{ .nanoseconds = stats.real_ns_link_flush }, |
| 171 | |
| 172 | .llvm_pass_timings = fmtEscapeHtml(llvm_pass_timings), |
| 173 | }); |
| 174 | defer gpa.free(inner_html); |
| 175 | |
| 176 | var file_table_html: std.Io.Writer.Allocating = .init(gpa); |
| 177 | defer file_table_html.deinit(); |
| 178 | |
| 179 | for (slowest_files[0..@min(max_table_rows, slowest_files.len)]) |file| { |
| 180 | try file_table_html.writer.print( |
| 181 | \\<tr> |
| 182 | \\ <th scope="row"><code>{f}</code></th> |
| 183 | \\ <td>{f}</td> |
| 184 | \\ <td>{f}</td> |
| 185 | \\ <td>{f}</td> |
| 186 | \\ <td>{f}</td> |
| 187 | \\</tr> |
| 188 | \\ |
| 189 | , .{ |
| 190 | fmtEscapeHtml(file.name), |
| 191 | std.Io.Duration{ .nanoseconds = file.ns_sema }, |
| 192 | std.Io.Duration{ .nanoseconds = file.ns_codegen }, |
| 193 | std.Io.Duration{ .nanoseconds = file.ns_link }, |
| 194 | std.Io.Duration{ .nanoseconds = file.ns_sema + file.ns_codegen + file.ns_link }, |
| 195 | }); |
| 196 | } |
| 197 | if (slowest_files.len > max_table_rows) { |
| 198 | try file_table_html.writer.print( |
| 199 | \\<tr><td colspan="4">{d} more rows omitted</td></tr> |
| 200 | \\ |
| 201 | , .{slowest_files.len - max_table_rows}); |
| 202 | } |
| 203 | |
| 204 | var decl_table_html: std.Io.Writer.Allocating = .init(gpa); |
| 205 | defer decl_table_html.deinit(); |
| 206 | |
| 207 | for (slowest_decls[0..@min(max_table_rows, slowest_decls.len)]) |decl| { |
| 208 | try decl_table_html.writer.print( |
| 209 | \\<tr> |
| 210 | \\ <th scope="row"><code>{f}</code></th> |
| 211 | \\ <th scope="row"><code>{f}</code></th> |
| 212 | \\ <td>{d}</td> |
| 213 | \\ <td>{f}</td> |
| 214 | \\ <td>{f}</td> |
| 215 | \\ <td>{f}</td> |
| 216 | \\ <td>{f}</td> |
| 217 | \\</tr> |
| 218 | \\ |
| 219 | , .{ |
| 220 | fmtEscapeHtml(decl.file_name), |
| 221 | fmtEscapeHtml(decl.name), |
| 222 | decl.sema_count, |
| 223 | std.Io.Duration{ .nanoseconds = decl.ns_sema }, |
| 224 | std.Io.Duration{ .nanoseconds = decl.ns_codegen }, |
| 225 | std.Io.Duration{ .nanoseconds = decl.ns_link }, |
| 226 | std.Io.Duration{ .nanoseconds = decl.ns_sema + decl.ns_codegen + decl.ns_link }, |
| 227 | }); |
| 228 | } |
| 229 | if (slowest_decls.len > max_table_rows) { |
| 230 | try decl_table_html.writer.print( |
| 231 | \\<tr><td colspan="6">{d} more rows omitted</td></tr> |
| 232 | \\ |
| 233 | , .{slowest_decls.len - max_table_rows}); |
| 234 | } |
| 235 | |
| 236 | js.updateCompile( |
| 237 | hdr.step_idx, |
| 238 | inner_html.ptr, |
| 239 | inner_html.len, |
| 240 | file_table_html.written().ptr, |
| 241 | file_table_html.written().len, |
| 242 | decl_table_html.written().ptr, |
| 243 | decl_table_html.written().len, |
| 244 | hdr.flags.use_llvm, |
| 245 | ); |
| 246 | } |
| 247 | |
| 248 | pub fn runTestResultMessage(msg_bytes: []u8) error{OutOfMemory}!void { |
| 249 | if (msg_bytes.len < @sizeOf(abi.RunTestResult)) @panic("malformed RunTestResult message"); |
| 250 | const hdr: *const abi.RunTestResult = @ptrCast(msg_bytes[0..@sizeOf(abi.RunTestResult)]); |
| 251 | if (hdr.step_idx >= step_list.*.len) @panic("malformed RunTestResult message"); |
| 252 | const trailing = msg_bytes[@sizeOf(abi.RunTestResult)..]; |
| 253 | |
| 254 | const durations: []align(1) const u64 = @ptrCast(trailing[0 .. hdr.tests_len * 8]); |
| 255 | var offset: usize = hdr.tests_len * 8; |
| 256 | |
| 257 | var table_html: std.ArrayList(u8) = .empty; |
| 258 | defer table_html.deinit(gpa); |
| 259 | |
| 260 | for (durations) |test_ns| { |
| 261 | const test_name_len = std.mem.findScalar(u8, trailing[offset..], 0) orelse @panic("malformed RunTestResult message"); |
| 262 | const test_name = trailing[offset..][0..test_name_len]; |
| 263 | offset += test_name_len + 1; |
| 264 | try table_html.print(gpa, "<tr><th scope=\"row\"><code>{f}</code></th>", .{fmtEscapeHtml(test_name)}); |
| 265 | if (test_ns == std.math.maxInt(u64)) { |
| 266 | try table_html.appendSlice(gpa, "<td class=\"empty-cell\"></td>"); // didn't run |
| 267 | } else { |
| 268 | try table_html.print(gpa, "<td>{f}</td>", .{std.Io.Duration{ .nanoseconds = test_ns }}); |
| 269 | } |
| 270 | try table_html.appendSlice(gpa, "</tr>\n"); |
| 271 | } |
| 272 | |
| 273 | if (offset != trailing.len) @panic("malformed RunTestResult message"); |
| 274 | |
| 275 | js.updateRunTest( |
| 276 | hdr.step_idx, |
| 277 | table_html.items.ptr, |
| 278 | table_html.items.len, |
| 279 | ); |
| 280 | } |