|
387 | 387 | <button id="btn-zoom-in">Zoom +</button> |
388 | 388 | <button id="btn-zoom-out">Zoom −</button> |
389 | 389 | <button id="btn-fit">Fit All</button> |
| 390 | + <button id="btn-time-mode" title="Toggle between relative (offset from trace start) and absolute timestamps">Time: Relative</button> |
| 391 | + <button id="btn-tz-mode" title="Toggle between UTC and local timezone for absolute timestamps" style="display:none">TZ: UTC</button> |
390 | 392 | <button id="btn-reset">New File</button> |
391 | 393 | </div> |
392 | 394 | <div id="main-area" tabindex="0" role="application" aria-label="Trace timeline"> |
@@ -568,6 +570,38 @@ <h3>⌨ Keyboard</h3> |
568 | 570 | let minTs = 0, |
569 | 571 | maxTs = 0, |
570 | 572 | durationNs = 0; |
| 573 | + let useAbsoluteTime = false; |
| 574 | + let useLocalTz = false; |
| 575 | + |
| 576 | + function fmtDuration(ns) { |
| 577 | + const prefix = "+"; |
| 578 | + const v = ns; |
| 579 | + if (v >= 1e9) return prefix + (v / 1e9).toFixed(2) + "s"; |
| 580 | + if (v >= 1e6) return prefix + (v / 1e6).toFixed(2) + "ms"; |
| 581 | + if (v >= 1e3) return prefix + (v / 1e3).toFixed(1) + "µs"; |
| 582 | + return prefix + v.toFixed(0) + "ns"; |
| 583 | + } |
| 584 | + |
| 585 | + function fmtWallClock(ns) { |
| 586 | + const ms = ns / 1e6; |
| 587 | + const d = new Date(ms); |
| 588 | + const pad2 = n => String(n).padStart(2, "0"); |
| 589 | + if (useLocalTz) { |
| 590 | + return pad2(d.getHours()) + ":" + pad2(d.getMinutes()) + ":" + |
| 591 | + pad2(d.getSeconds()); |
| 592 | + } else { |
| 593 | + return pad2(d.getUTCHours()) + ":" + pad2(d.getUTCMinutes()) + ":" + |
| 594 | + pad2(d.getUTCSeconds()); |
| 595 | + } |
| 596 | + } |
| 597 | + |
| 598 | + // Format a timestamp (ns) for display. In relative mode, shows |
| 599 | + // offset from trace start with a "+" prefix. In absolute mode, |
| 600 | + // shows wall-clock time. |
| 601 | + function fmtTs(ns) { |
| 602 | + if (useAbsoluteTime) return fmtWallClock(ns); |
| 603 | + return fmtDuration(ns - minTs); |
| 604 | + } |
571 | 605 | // View window in nanoseconds |
572 | 606 | let viewStart = 0, |
573 | 607 | viewEnd = 0; |
@@ -1268,12 +1302,7 @@ <h3>⌨ Keyboard</h3> |
1268 | 1302 | ctx.moveTo(x, 20); |
1269 | 1303 | ctx.lineTo(x, 30); |
1270 | 1304 | ctx.stroke(); |
1271 | | - const relMs = (t - minTs) / 1e6; |
1272 | | - let label; |
1273 | | - if (relMs >= 1000) label = (relMs / 1000).toFixed(2) + "s"; |
1274 | | - else if (relMs >= 1) label = relMs.toFixed(2) + "ms"; |
1275 | | - else label = (relMs * 1000).toFixed(0) + "µs"; |
1276 | | - ctx.fillText(label, x, 16); |
| 1305 | + ctx.fillText(fmtTs(t), x, 16); |
1277 | 1306 | } |
1278 | 1307 | } |
1279 | 1308 |
|
@@ -2084,6 +2113,20 @@ <h3>⌨ Keyboard</h3> |
2084 | 2113 | viewEnd = maxTs; |
2085 | 2114 | renderAll(); |
2086 | 2115 | }); |
| 2116 | + document.getElementById("btn-time-mode").addEventListener("click", () => { |
| 2117 | + useAbsoluteTime = !useAbsoluteTime; |
| 2118 | + document.getElementById("btn-time-mode").textContent = |
| 2119 | + useAbsoluteTime ? "Time: Absolute" : "Time: Relative"; |
| 2120 | + document.getElementById("btn-tz-mode").style.display = |
| 2121 | + useAbsoluteTime ? "" : "none"; |
| 2122 | + renderAll(); |
| 2123 | + }); |
| 2124 | + document.getElementById("btn-tz-mode").addEventListener("click", () => { |
| 2125 | + useLocalTz = !useLocalTz; |
| 2126 | + document.getElementById("btn-tz-mode").textContent = |
| 2127 | + useLocalTz ? "TZ: Local" : "TZ: UTC"; |
| 2128 | + renderAll(); |
| 2129 | + }); |
2087 | 2130 |
|
2088 | 2131 | function zoom(factor, centerFrac = 0.5) { |
2089 | 2132 | clearToasts(); |
@@ -2582,9 +2625,8 @@ <h3>⌨ Keyboard</h3> |
2582 | 2625 | html += `Example polls: `; |
2583 | 2626 | for (let i = 0; i < examples.length; i++) { |
2584 | 2627 | const ex = examples[i]; |
2585 | | - const tMs = ((ex.poll.start - minTs) / 1e6).toFixed(2); |
2586 | 2628 | const durUs = ((ex.poll.end - ex.poll.start) / 1e3).toFixed(0); |
2587 | | - html += `<span class="sched-jump" style="cursor:pointer;text-decoration:underline;margin-right:8px" data-time="${ex.poll.start}" data-end="${ex.poll.end}" data-worker="${ex.worker}">W${ex.worker} @${tMs}ms (${durUs}µs)</span>`; |
| 2629 | + html += `<span class="sched-jump" style="cursor:pointer;text-decoration:underline;margin-right:8px" data-time="${ex.poll.start}" data-end="${ex.poll.end}" data-worker="${ex.worker}">W${ex.worker} @${fmtTs(ex.poll.start)} (${durUs}µs)</span>`; |
2588 | 2630 | } |
2589 | 2631 | if (g.polls.length > 5) html += `<span style="color:#888">… ${g.polls.length - 5} more</span>`; |
2590 | 2632 | html += `</div>`; |
@@ -2783,7 +2825,6 @@ <h3>⌨ Keyboard</h3> |
2783 | 2825 |
|
2784 | 2826 | const w = workerIds[laneIdx]; |
2785 | 2827 | const spans = workerSpans[w]; |
2786 | | - const relMs = (ns - minTs) / 1e6; |
2787 | 2828 |
|
2788 | 2829 | // Determine worker state at this point in time |
2789 | 2830 | let state = "🟢 Active"; |
@@ -2875,7 +2916,7 @@ <h3>⌨ Keyboard</h3> |
2875 | 2916 | } |
2876 | 2917 |
|
2877 | 2918 | tooltip.innerHTML = |
2878 | | - `<span class="label">Worker</span> <span class="value">${w}</span> · <span class="label">Time</span> <span class="value">${relMs.toFixed(3)}ms</span><br>` + |
| 2919 | + `<span class="label">Worker</span> <span class="value">${w}</span> · <span class="label">Time</span> <span class="value">${fmtTs(ns)}</span><br>` + |
2879 | 2920 | `<span class="label">State:</span> ${state}${stateDetail}<br>` + |
2880 | 2921 | schedInfo + |
2881 | 2922 | pollInfo + |
@@ -3344,8 +3385,7 @@ <h3>⌨ Keyboard</h3> |
3344 | 3385 | // Show first few tasks as clickable links |
3345 | 3386 | const show = taskList.slice(0, 5); |
3346 | 3387 | for (const t of show) { |
3347 | | - const tMs = ((t.firstPoll - minTs) / 1e6).toFixed(3); |
3348 | | - html += `<div style="margin-left:8px"><span class="spawned-task-link" style="color:#6c63ff;cursor:pointer;text-decoration:underline" data-task-id="${t.taskId}">0x${t.taskId.toString(16)}</span> <span style="color:#888">@ ${tMs}ms</span></div>`; |
| 3388 | + html += `<div style="margin-left:8px"><span class="spawned-task-link" style="color:#6c63ff;cursor:pointer;text-decoration:underline" data-task-id="${t.taskId}">0x${t.taskId.toString(16)}</span> <span style="color:#888">@ ${fmtTs(t.firstPoll)}</span></div>`; |
3349 | 3389 | } |
3350 | 3390 | if (taskList.length > 5) html += `<div style="color:#888;margin-left:8px;font-size:0.9em">… ${taskList.length - 5} more</div>`; |
3351 | 3391 | html += `</div>`; |
|
0 commit comments