fixing bugs

This commit is contained in:
Jeremy Anderson 2026-03-25 18:33:56 -04:00
parent 5433679785
commit 654221d956
3 changed files with 225 additions and 403 deletions

View File

@ -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);

View File

@ -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>

View File

@ -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>