fixing bugs
This commit is contained in:
parent
5433679785
commit
654221d956
|
|
@ -1,30 +1,84 @@
|
|||
async function loadTargets() {
|
||||
// cockpit/fester-module/targets.js
|
||||
|
||||
const res = await fetch("/api/targets");
|
||||
const data = await res.json();
|
||||
/**
|
||||
* Fester Targets Module
|
||||
* Handles nodes, edges, metrics, heatmaps, and live updates for cockpit UI.
|
||||
*/
|
||||
|
||||
const sys = document.getElementById("system");
|
||||
const arch = document.getElementById("arch");
|
||||
const runtime = document.getElementById("runtime");
|
||||
(function(global) {
|
||||
|
||||
data.systems.forEach(s => {
|
||||
let o = document.createElement("option");
|
||||
o.value = s;
|
||||
o.text = s;
|
||||
sys.appendChild(o);
|
||||
});
|
||||
const targetsModule = {
|
||||
nodes: {},
|
||||
edges: [],
|
||||
metrics: {},
|
||||
heatmaps: {},
|
||||
callbacks: [],
|
||||
|
||||
data.arches.forEach(a => {
|
||||
let o = document.createElement("option");
|
||||
o.value = a;
|
||||
o.text = a;
|
||||
arch.appendChild(o);
|
||||
});
|
||||
|
||||
data.runtimes.forEach(r => {
|
||||
let o = document.createElement("option");
|
||||
o.value = r;
|
||||
o.text = r;
|
||||
runtime.appendChild(o);
|
||||
});
|
||||
/**
|
||||
* Register a callback to receive updated DAG data
|
||||
*/
|
||||
onUpdate(callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this.callbacks.push(callback);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the internal state from WS or iframe message
|
||||
*/
|
||||
update(data) {
|
||||
if (!data) return;
|
||||
|
||||
if (data.nodes) {
|
||||
data.nodes.forEach(node => this.nodes[node.id] = node);
|
||||
}
|
||||
if (data.edges) {
|
||||
this.edges = data.edges;
|
||||
}
|
||||
if (data.metrics) {
|
||||
Object.assign(this.metrics, data.metrics);
|
||||
}
|
||||
if (data.heatmaps) {
|
||||
Object.assign(this.heatmaps, data.heatmaps);
|
||||
}
|
||||
|
||||
this.dispatch();
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify all registered callbacks with the updated data
|
||||
*/
|
||||
dispatch() {
|
||||
const snapshot = {
|
||||
nodes: Object.values(this.nodes),
|
||||
edges: this.edges,
|
||||
metrics: this.metrics,
|
||||
heatmaps: this.heatmaps
|
||||
};
|
||||
this.callbacks.forEach(cb => cb(snapshot));
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all stored DAG state
|
||||
*/
|
||||
clear() {
|
||||
this.nodes = {};
|
||||
this.edges = [];
|
||||
this.metrics = {};
|
||||
this.heatmaps = {};
|
||||
this.dispatch();
|
||||
}
|
||||
};
|
||||
|
||||
// Listen for iframe messages from cockpit.html
|
||||
window.addEventListener('message', (event) => {
|
||||
const data = event.data;
|
||||
if (data && typeof data === 'object') {
|
||||
targetsModule.update(data);
|
||||
}
|
||||
});
|
||||
|
||||
// Expose module globally
|
||||
global.FesterTargets = targetsModule;
|
||||
|
||||
})(window);
|
||||
|
|
|
|||
|
|
@ -2,242 +2,56 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Fester Debug Cockpit</title>
|
||||
|
||||
<title>Fester Cockpit</title>
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: sans-serif;
|
||||
background: #0b0f14;
|
||||
color: #d0d0d0;
|
||||
}
|
||||
|
||||
#layout {
|
||||
display: grid;
|
||||
grid-template-columns: 320px 1fr 320px;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
CONTROL PANEL
|
||||
------------------------- */
|
||||
#controls {
|
||||
padding: 12px;
|
||||
border-right: 1px solid #1f2a38;
|
||||
background: #0e141d;
|
||||
}
|
||||
|
||||
select, button {
|
||||
width: 100%;
|
||||
margin: 6px 0;
|
||||
padding: 6px;
|
||||
background: #1a2432;
|
||||
color: #ddd;
|
||||
border: 1px solid #2c3b4d;
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
GRAPH
|
||||
------------------------- */
|
||||
#graph {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
background: #05070b;
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
DEBUG PANEL
|
||||
------------------------- */
|
||||
#debug {
|
||||
padding: 12px;
|
||||
border-left: 1px solid #1f2a38;
|
||||
background: #0e141d;
|
||||
font-size: 12px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.node {
|
||||
position: absolute;
|
||||
width: 130px;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
background: #1b2430;
|
||||
border: 1px solid #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* states */
|
||||
.node.running { border-color: #ffb300; }
|
||||
.node.done { border-color: #66bb6a; }
|
||||
.node.failed { border-color: #ef5350; }
|
||||
.node.scheduled { border-color: #42a5f5; }
|
||||
|
||||
.highlight {
|
||||
outline: 2px solid #ffd54f;
|
||||
}
|
||||
body { margin: 0; font-family: Arial, sans-serif; background: #1e1e1e; color: #eee; }
|
||||
header { background: #2c2c2c; padding: 10px; font-size: 1.2em; display: flex; align-items: center; }
|
||||
header h1 { flex: 1; margin: 0; font-weight: normal; }
|
||||
#tabs { display: flex; background: #252525; }
|
||||
.tab { flex: 1; text-align: center; padding: 10px; cursor: pointer; }
|
||||
.tab.active { background: #3a3a3a; }
|
||||
iframe { border: none; width: 100%; height: calc(100vh - 80px); }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="layout">
|
||||
|
||||
<!-- LEFT -->
|
||||
<div id="controls">
|
||||
|
||||
<h3>Build Control</h3>
|
||||
|
||||
<select id="arch">
|
||||
<option>x86_64</option>
|
||||
<option>arm64</option>
|
||||
<option>riscv64</option>
|
||||
</select>
|
||||
|
||||
<select id="target">
|
||||
<option>linux-glibc</option>
|
||||
<option>linux-musl</option>
|
||||
<option>openwrt</option>
|
||||
</select>
|
||||
|
||||
<select id="toolchain">
|
||||
<option>gcc</option>
|
||||
<option>clang</option>
|
||||
</select>
|
||||
|
||||
<button onclick="startBuild()">Start Build</button>
|
||||
<header>
|
||||
<h1>Fester Cockpit</h1>
|
||||
</header>
|
||||
|
||||
<div id="tabs">
|
||||
<div class="tab active" data-target="live_dag">Live DAG</div>
|
||||
<div class="tab" data-target="replay">Replay / Debugger</div>
|
||||
</div>
|
||||
|
||||
<!-- CENTER -->
|
||||
<div id="graph"></div>
|
||||
|
||||
<!-- RIGHT DEBUG -->
|
||||
<div id="debug">
|
||||
<h3>Node Debugger</h3>
|
||||
<div id="trace">Select a node</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<iframe id="cockpit-frame" src="../../ui/live_dag.html"></iframe>
|
||||
|
||||
<script src="./targets.js"></script>
|
||||
<script>
|
||||
|
||||
const ws = new WebSocket("ws://localhost:8080/ws");
|
||||
|
||||
const nodes = {};
|
||||
const traces = {};
|
||||
const deps = {};
|
||||
|
||||
// -------------------------
|
||||
// POSITIONING
|
||||
// -------------------------
|
||||
let index = 0;
|
||||
|
||||
function pos(name) {
|
||||
if (nodes[name]) return nodes[name].pos;
|
||||
|
||||
const p = {
|
||||
x: 80 + (index % 4) * 200,
|
||||
y: 80 + Math.floor(index / 4) * 120
|
||||
};
|
||||
|
||||
index++;
|
||||
return p;
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// NODE CLICK DEBUGGER
|
||||
// -------------------------
|
||||
function showTrace(name) {
|
||||
|
||||
const trace = traces[name];
|
||||
|
||||
const panel = document.getElementById("trace");
|
||||
|
||||
if (!trace) {
|
||||
panel.innerHTML = "No trace data yet";
|
||||
return;
|
||||
}
|
||||
|
||||
panel.innerHTML = `
|
||||
<b>${name}</b><br><br>
|
||||
|
||||
<b>State:</b> ${trace.state}<br>
|
||||
<b>Cache:</b> ${trace.cache || "unknown"}<br>
|
||||
<b>Node:</b> ${trace.node || "n/a"}<br>
|
||||
|
||||
<hr>
|
||||
|
||||
<b>Scheduler Score Breakdown</b><br>
|
||||
CPU: ${trace.score?.cpu || 0}<br>
|
||||
Thermal: ${trace.score?.thermal || 0}<br>
|
||||
Stability: ${trace.score?.instability || 0}<br>
|
||||
Final: ${trace.score?.final || 0}<br>
|
||||
|
||||
<hr>
|
||||
|
||||
<b>Deps:</b><br>
|
||||
${(trace.deps || []).join(", ")}
|
||||
`;
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// WEBSOCKET
|
||||
// -------------------------
|
||||
ws.onmessage = (msg) => {
|
||||
|
||||
const event = JSON.parse(msg.data);
|
||||
|
||||
if (event.type !== "node") return;
|
||||
|
||||
const n = event.data.node;
|
||||
|
||||
traces[n] = {
|
||||
...traces[n],
|
||||
...event.data
|
||||
};
|
||||
|
||||
// create node
|
||||
if (!nodes[n]) {
|
||||
|
||||
const el = document.createElement("div");
|
||||
el.className = "node";
|
||||
el.innerText = n;
|
||||
|
||||
const p = pos(n);
|
||||
el.style.left = p.x + "px";
|
||||
el.style.top = p.y + "px";
|
||||
|
||||
el.onclick = () => showTrace(n);
|
||||
|
||||
document.getElementById("graph").appendChild(el);
|
||||
|
||||
nodes[n] = { el, pos: p };
|
||||
}
|
||||
|
||||
const el = nodes[n].el;
|
||||
|
||||
el.classList.remove("running","done","failed","scheduled");
|
||||
el.classList.add(event.data.state);
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
// START BUILD
|
||||
// -------------------------
|
||||
function startBuild() {
|
||||
|
||||
fetch("/api/build", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type":"application/json"},
|
||||
body: JSON.stringify({
|
||||
arch: arch.value,
|
||||
target: target.value,
|
||||
toolchain: toolchain.value
|
||||
})
|
||||
// Tab switching logic
|
||||
const tabs = document.querySelectorAll('.tab');
|
||||
const iframe = document.getElementById('cockpit-frame');
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
iframe.src = tab.dataset.target === 'live_dag' ? '../../ui/live_dag.html' : '../../ui/replay.html';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize WebSocket connection and dispatch events to iframe
|
||||
const ws = new WebSocket(`ws://${window.location.hostname}:8080/ws`);
|
||||
ws.onopen = () => console.log("Cockpit WS connected");
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
// Forward event to iframe
|
||||
iframe.contentWindow.postMessage(data, '*');
|
||||
} catch (err) {
|
||||
console.error("Invalid WS event", err);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
|
|
|||
240
ui/live_dag.html
240
ui/live_dag.html
|
|
@ -1,169 +1,123 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Fester Live DAG + Cause Graph</title>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<title>Fester Live DAG</title>
|
||||
<link rel="stylesheet" href="../style.css">
|
||||
<style>
|
||||
body {
|
||||
font-family: monospace;
|
||||
background: #0e0f12;
|
||||
color: #d0d0d0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#graph {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
body { font-family: sans-serif; margin: 0; overflow: hidden; }
|
||||
#graph { position: relative; width: 100vw; height: 100vh; background: #111; }
|
||||
.node {
|
||||
position: absolute;
|
||||
padding: 8px 10px;
|
||||
width: 120px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
min-width: 140px;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid #333;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s, transform 0.2s;
|
||||
}
|
||||
|
||||
.edge {
|
||||
svg.edge {
|
||||
position: absolute;
|
||||
height: 2px;
|
||||
background: #444;
|
||||
transform-origin: left center;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
background: #111;
|
||||
border: 1px solid #333;
|
||||
padding: 6px;
|
||||
font-size: 11px;
|
||||
display: none;
|
||||
max-width: 300px;
|
||||
.heatmap {
|
||||
border: 2px solid yellow;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="graph"></div>
|
||||
<div id="tooltip" class="tooltip"></div>
|
||||
<svg id="edges" class="edge"></svg>
|
||||
|
||||
<script src="../cockpit/fester-module/targets.js"></script>
|
||||
<script>
|
||||
const graphEl = document.getElementById('graph');
|
||||
const edgesSVG = document.getElementById('edges');
|
||||
|
||||
function createNodeElement(node) {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'node';
|
||||
el.id = `node-${node.id}`;
|
||||
el.style.left = `${node.x || Math.random() * (window.innerWidth-150)}px`;
|
||||
el.style.top = `${node.y || Math.random() * (window.innerHeight-50)}px`;
|
||||
el.innerText = node.name || node.id;
|
||||
graphEl.appendChild(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
function updateNode(node) {
|
||||
let el = document.getElementById(`node-${node.id}`);
|
||||
if (!el) {
|
||||
el = createNodeElement(node);
|
||||
}
|
||||
el.innerText = node.name || node.id;
|
||||
|
||||
// color based on state
|
||||
switch(node.state) {
|
||||
case 'done': el.style.background = 'green'; break;
|
||||
case 'running': el.style.background = 'orange'; break;
|
||||
case 'failed': el.style.background = 'red'; break;
|
||||
default: el.style.background = 'gray';
|
||||
}
|
||||
|
||||
// heatmap border
|
||||
if (node.heatmap) {
|
||||
el.classList.add('heatmap');
|
||||
} else {
|
||||
el.classList.remove('heatmap');
|
||||
}
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
function drawEdges(edges, nodes) {
|
||||
edgesSVG.innerHTML = '';
|
||||
edges.forEach(edge => {
|
||||
const fromEl = document.getElementById(`node-${edge.from}`);
|
||||
const toEl = document.getElementById(`node-${edge.to}`);
|
||||
if (!fromEl || !toEl) return;
|
||||
|
||||
const line = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
const fromRect = fromEl.getBoundingClientRect();
|
||||
const toRect = toEl.getBoundingClientRect();
|
||||
line.setAttribute('x1', fromRect.left + fromRect.width/2);
|
||||
line.setAttribute('y1', fromRect.top + fromRect.height/2);
|
||||
line.setAttribute('x2', toRect.left + toRect.width/2);
|
||||
line.setAttribute('y2', toRect.top + toRect.height/2);
|
||||
line.setAttribute('stroke', 'white');
|
||||
line.setAttribute('stroke-width', '2');
|
||||
edgesSVG.appendChild(line);
|
||||
});
|
||||
}
|
||||
|
||||
// subscribe to FesterTargets updates
|
||||
FesterTargets.onUpdate(snapshot => {
|
||||
snapshot.nodes.forEach(updateNode);
|
||||
drawEdges(snapshot.edges, snapshot.nodes);
|
||||
});
|
||||
|
||||
// connect to WebSocket
|
||||
const ws = new WebSocket("ws://localhost:8080/ws");
|
||||
|
||||
const nodes = {};
|
||||
const edges = {};
|
||||
const graph = document.getElementById("graph");
|
||||
const tooltip = document.getElementById("tooltip");
|
||||
|
||||
function getColor(state) {
|
||||
return {
|
||||
"scheduled": "#2b6cb0",
|
||||
"running": "#dd6b20",
|
||||
"done": "#2f855a",
|
||||
"failed": "#c53030",
|
||||
"hit": "#805ad5"
|
||||
}[state] || "#444";
|
||||
}
|
||||
|
||||
function drawNode(id, label, state, x, y) {
|
||||
|
||||
if (!nodes[id]) {
|
||||
const el = document.createElement("div");
|
||||
el.className = "node";
|
||||
graph.appendChild(el);
|
||||
nodes[id] = el;
|
||||
|
||||
el.onmousemove = (e) => {
|
||||
tooltip.style.display = "block";
|
||||
tooltip.style.left = e.pageX + 10 + "px";
|
||||
tooltip.style.top = e.pageY + 10 + "px";
|
||||
};
|
||||
|
||||
el.onmouseleave = () => {
|
||||
tooltip.style.display = "none";
|
||||
};
|
||||
}
|
||||
|
||||
const el = nodes[id];
|
||||
|
||||
el.innerText = `${label}\n${state}`;
|
||||
el.style.background = getColor(state);
|
||||
el.style.left = x + "px";
|
||||
el.style.top = y + "px";
|
||||
}
|
||||
|
||||
function drawEdge(id, x1, y1, x2, y2) {
|
||||
|
||||
if (!edges[id]) {
|
||||
const el = document.createElement("div");
|
||||
el.className = "edge";
|
||||
graph.appendChild(el);
|
||||
edges[id] = el;
|
||||
}
|
||||
|
||||
const el = edges[id];
|
||||
|
||||
const dx = x2 - x1;
|
||||
const dy = y2 - y1;
|
||||
const length = Math.sqrt(dx*dx + dy*dy);
|
||||
|
||||
el.style.width = length + "px";
|
||||
el.style.left = x1 + "px";
|
||||
el.style.top = y1 + "px";
|
||||
el.style.transform = `rotate(${Math.atan2(dy, dx)}rad)`;
|
||||
}
|
||||
|
||||
ws.onmessage = (msg) => {
|
||||
|
||||
ws.onmessage = msg => {
|
||||
const event = JSON.parse(msg.data);
|
||||
|
||||
// ----------------------------
|
||||
// NODE UPDATE
|
||||
// ----------------------------
|
||||
if (event.type === "task_update") {
|
||||
|
||||
const key = event.node + ":" + event.action;
|
||||
|
||||
const x = Math.random() * window.innerWidth * 0.8;
|
||||
const y = Math.random() * window.innerHeight * 0.8;
|
||||
|
||||
drawNode(
|
||||
key,
|
||||
event.action,
|
||||
event.state,
|
||||
x,
|
||||
y
|
||||
);
|
||||
if (event.type === 'node') {
|
||||
const n = event.data.node;
|
||||
const state = event.data.state;
|
||||
FesterTargets.update({ nodes: [{ id: n, name: n, state: state }] });
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// FAILURE HIGHLIGHT
|
||||
// ----------------------------
|
||||
if (event.type === "failure") {
|
||||
|
||||
const key = event.node + ":" + event.action;
|
||||
|
||||
if (nodes[key]) {
|
||||
nodes[key].style.boxShadow = "0 0 12px red";
|
||||
if (event.type === 'pipeline') {
|
||||
console.log('Pipeline event:', event.data);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// CAUSE GRAPH HOOK (NEW)
|
||||
// ----------------------------
|
||||
if (event.type === "debug") {
|
||||
|
||||
tooltip.innerText = JSON.stringify(event, null, 2);
|
||||
if (event.type === 'heatmap') {
|
||||
// { id: 'node1', heatmap: true }
|
||||
FesterTargets.update({ nodes: [event.data] });
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in New Issue