Migrate to new Gitea instance (gitea.wbd-rd.nl)
- Update all submodule URLs from gitea.centraal.wbd-rd.nl to gitea.wbd-rd.nl - Add settler as proper submodule in .gitmodules - Add agent skills, function anchors, decisions, and improvements - Add Docker configuration and scripts - Add manuals and third_party docs - Update .gitignore with secrets and build artifacts - Remove stale .tgz build artifact Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
217
scripts/deploy-and-trace.js
Normal file
217
scripts/deploy-and-trace.js
Normal file
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Deploy the demo flow fresh and trace the first 60 seconds of behavior.
|
||||
* Captures: container logs, PS volume evolution, flow events.
|
||||
*/
|
||||
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const NR_URL = 'http://localhost:1880';
|
||||
const FLOW_FILE = path.join(__dirname, '..', 'docker', 'demo-flow.json');
|
||||
const TRACE_SECONDS = 45;
|
||||
|
||||
function httpReq(method, urlPath, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const parsed = new URL(NR_URL + urlPath);
|
||||
const opts = {
|
||||
hostname: parsed.hostname,
|
||||
port: parsed.port,
|
||||
path: parsed.pathname,
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Node-RED-Deployment-Type': 'full',
|
||||
},
|
||||
};
|
||||
if (body) {
|
||||
const buf = Buffer.from(JSON.stringify(body));
|
||||
opts.headers['Content-Length'] = buf.length;
|
||||
}
|
||||
const req = http.request(opts, (res) => {
|
||||
const chunks = [];
|
||||
res.on('data', (c) => chunks.push(c));
|
||||
res.on('end', () => {
|
||||
const text = Buffer.concat(chunks).toString();
|
||||
resolve({ status: res.statusCode, body: text });
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
if (body) req.write(JSON.stringify(body));
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function getLogs(since) {
|
||||
try {
|
||||
// Get ALL logs since our timestamp
|
||||
const cmd = `docker logs evolv-nodered --since ${since} 2>&1`;
|
||||
return execSync(cmd, { encoding: 'utf8', timeout: 5000 });
|
||||
} catch (e) {
|
||||
return 'Log error: ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
console.log('=== Deploy & Trace ===');
|
||||
console.log('Loading flow from', FLOW_FILE);
|
||||
|
||||
const flow = JSON.parse(fs.readFileSync(FLOW_FILE, 'utf8'));
|
||||
console.log(`Flow has ${flow.length} nodes`);
|
||||
|
||||
// Deploy
|
||||
const deployTime = new Date().toISOString();
|
||||
console.log(`\nDeploying at ${deployTime}...`);
|
||||
const res = await httpReq('POST', '/flows', flow);
|
||||
console.log(`Deploy response: ${res.status}`);
|
||||
|
||||
if (res.status !== 204 && res.status !== 200) {
|
||||
console.error('Deploy failed:', res.body);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Wait 3 seconds for initial setup
|
||||
console.log('Waiting 3s for init...\n');
|
||||
await new Promise((r) => setTimeout(r, 3000));
|
||||
|
||||
// Trace loop
|
||||
const traceStart = Date.now();
|
||||
const volumeHistory = [];
|
||||
let lastLogPos = 0;
|
||||
|
||||
for (let i = 0; i < Math.ceil(TRACE_SECONDS / 3); i++) {
|
||||
const elapsed = ((Date.now() - traceStart) / 1000).toFixed(1);
|
||||
|
||||
// Get new logs since deploy
|
||||
const logs = getLogs(deployTime);
|
||||
const newLines = logs.split('\n').slice(lastLogPos);
|
||||
lastLogPos = logs.split('\n').length;
|
||||
|
||||
// Parse interesting log lines
|
||||
const safeGuards = [];
|
||||
const pressureChanges = [];
|
||||
const modeChanges = [];
|
||||
const stateChanges = [];
|
||||
const other = [];
|
||||
|
||||
newLines.forEach((line) => {
|
||||
if (!line.trim()) return;
|
||||
|
||||
const volMatch = line.match(/vol=([-\d.]+) m3.*remainingTime=([\w.]+)/);
|
||||
if (volMatch) {
|
||||
safeGuards.push({ vol: parseFloat(volMatch[1]), remaining: volMatch[2] });
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.includes('Pressure change detected')) {
|
||||
pressureChanges.push(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.includes('Mode changed') || line.includes('setMode') || line.includes('Control mode')) {
|
||||
modeChanges.push(line.trim().substring(0, 200));
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.includes('machine state') || line.includes('State:') || line.includes('startup') || line.includes('shutdown')) {
|
||||
stateChanges.push(line.trim().substring(0, 200));
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.includes('q_in') || line.includes('netflow') || line.includes('Volume') ||
|
||||
line.includes('Height') || line.includes('Level') || line.includes('Controllevel')) {
|
||||
other.push(line.trim().substring(0, 200));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`--- t=${elapsed}s ---`);
|
||||
|
||||
if (safeGuards.length > 0) {
|
||||
const latest = safeGuards[safeGuards.length - 1];
|
||||
const first = safeGuards[0];
|
||||
console.log(` SAFETY: ${safeGuards.length} triggers, vol: ${first.vol} → ${latest.vol} m3, remaining: ${latest.remaining}s`);
|
||||
volumeHistory.push({ t: parseFloat(elapsed), vol: latest.vol });
|
||||
} else {
|
||||
console.log(' SAFETY: none (good)');
|
||||
}
|
||||
|
||||
if (pressureChanges.length > 0) {
|
||||
console.log(` PRESSURE: ${pressureChanges.length} changes`);
|
||||
}
|
||||
|
||||
if (modeChanges.length > 0) {
|
||||
modeChanges.forEach((m) => console.log(` MODE: ${m}`));
|
||||
}
|
||||
|
||||
if (stateChanges.length > 0) {
|
||||
stateChanges.slice(-5).forEach((s) => console.log(` STATE: ${s}`));
|
||||
}
|
||||
|
||||
if (other.length > 0) {
|
||||
other.slice(-5).forEach((o) => console.log(` INFO: ${o}`));
|
||||
}
|
||||
|
||||
console.log('');
|
||||
await new Promise((r) => setTimeout(r, 3000));
|
||||
}
|
||||
|
||||
// Final analysis
|
||||
console.log('\n=== Volume Trajectory ===');
|
||||
volumeHistory.forEach((v) => {
|
||||
const bar = '#'.repeat(Math.max(0, Math.round(v.vol / 2)));
|
||||
console.log(` t=${String(v.t).padStart(5)}s: ${String(v.vol.toFixed(2)).padStart(8)} m3 ${bar}`);
|
||||
});
|
||||
|
||||
if (volumeHistory.length >= 2) {
|
||||
const first = volumeHistory[0];
|
||||
const last = volumeHistory[volumeHistory.length - 1];
|
||||
const dt = last.t - first.t;
|
||||
const dv = last.vol - first.vol;
|
||||
const rate = dt > 0 ? (dv / dt * 3600).toFixed(1) : 'N/A';
|
||||
console.log(`\n Rate: ${rate} m3/h (${dv > 0 ? 'FILLING' : 'DRAINING'})`);
|
||||
}
|
||||
|
||||
// Get ALL logs for comprehensive analysis
|
||||
console.log('\n=== Full Log Analysis ===');
|
||||
const allLogs = getLogs(deployTime);
|
||||
const allLines = allLogs.split('\n');
|
||||
|
||||
// Count different message types
|
||||
const counts = { safety: 0, pressure: 0, mode: 0, state: 0, error: 0, warn: 0, flow: 0 };
|
||||
allLines.forEach((l) => {
|
||||
if (l.includes('Safe guard')) counts.safety++;
|
||||
if (l.includes('Pressure change')) counts.pressure++;
|
||||
if (l.includes('Mode') || l.includes('mode')) counts.mode++;
|
||||
if (l.includes('startup') || l.includes('shutdown') || l.includes('machine state')) counts.state++;
|
||||
if (l.includes('[ERROR]') || l.includes('Error')) counts.error++;
|
||||
if (l.includes('[WARN]')) counts.warn++;
|
||||
if (l.includes('netflow') || l.includes('q_in') || l.includes('flow')) counts.flow++;
|
||||
});
|
||||
|
||||
console.log('Message counts:', JSON.stringify(counts, null, 2));
|
||||
|
||||
// Print errors
|
||||
const errors = allLines.filter((l) => l.includes('[ERROR]') || l.includes('Error'));
|
||||
if (errors.length > 0) {
|
||||
console.log('\nErrors:');
|
||||
errors.slice(0, 20).forEach((e) => console.log(' ' + e.trim().substring(0, 200)));
|
||||
}
|
||||
|
||||
// Print first few non-pressure, non-safety lines
|
||||
console.log('\nKey events (first 30):');
|
||||
let keyCount = 0;
|
||||
allLines.forEach((l) => {
|
||||
if (keyCount >= 30) return;
|
||||
if (l.includes('Pressure change detected')) return;
|
||||
if (l.includes('Safe guard triggered')) return;
|
||||
if (!l.trim()) return;
|
||||
console.log(' ' + l.trim().substring(0, 200));
|
||||
keyCount++;
|
||||
});
|
||||
})().catch((err) => {
|
||||
console.error('Failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user