From 416d5190bc570eb6f75e042958916d0f7681af6c Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 1 Nov 2016 13:10:51 +0000 Subject: [PATCH] Flow debugger initial pass --- red.js | 7 +- red/runtime/nodes/Node.js | 1 + red/runtime/nodes/debugger/index.js | 262 ++++++++++++++++++++++++++++ red/runtime/nodes/flows/index.js | 5 + red/runtime/nodes/index.js | 11 +- red/runtime/nodes/router/index.js | 63 +++++-- 6 files changed, 335 insertions(+), 14 deletions(-) create mode 100644 red/runtime/nodes/debugger/index.js diff --git a/red.js b/red.js index c703ae878..757ffdcb7 100755 --- a/red.js +++ b/red.js @@ -37,7 +37,8 @@ var knownOpts = { "userDir":[path], "port": Number, "v": Boolean, - "help": Boolean + "help": Boolean, + "debug": Boolean }; var shortHands = { "s":["--settings"], @@ -120,6 +121,10 @@ if (parsedArgs.v) { settings.verbose = true; } +if (parsedArgs.debug) { + settings.enableDebugger = true; +} + if (settings.https) { server = https.createServer(settings.https,function(req,res){app(req,res);}); } else { diff --git a/red/runtime/nodes/Node.js b/red/runtime/nodes/Node.js index 9e9a2e865..04a59ff61 100644 --- a/red/runtime/nodes/Node.js +++ b/red/runtime/nodes/Node.js @@ -29,6 +29,7 @@ function Node(n) { this.type = n.type; this.z = n.z; this._closeCallbacks = []; + this._config = n; if (n.name) { this.name = n.name; diff --git a/red/runtime/nodes/debugger/index.js b/red/runtime/nodes/debugger/index.js new file mode 100644 index 000000000..ab483a480 --- /dev/null +++ b/red/runtime/nodes/debugger/index.js @@ -0,0 +1,262 @@ +/** + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var readline = require('readline'); +var rl; + +var runtime; +var components; + +var breakpoints = {}; + +var enabled = false; + +function enable() { + enabled = true; +} +function disable() { + enabled = false; +} + +function formatNode(n) { + return n.id+" ["+n.type+"]"+(n.name?" "+n.name:""); +} +function listNodes() { + var flowIds = runtime.nodes.listFlows(); + flowIds.forEach(function(id) { + var flow = runtime.nodes.getFlow(id); + console.log(flow.id+" [flow]"+(flow.label?" "+flow.label:"")); + if (flow.subflows) { + flow.subflows.forEach(function(sf) { + console.log(" - "+formatNode(sf)); + sf.configs.forEach(function(n) { + console.log(" - "+formatNode(n)); + }) + sf.nodes.forEach(function(n) { + console.log(" - "+formatNode(n)); + }) + + }) + } + if (flow.configs) { + flow.configs.forEach(function(n) { + console.log(" - "+formatNode(n)); + }) + } + if (flow.nodes) { + flow.nodes.forEach(function(n) { + console.log(" - "+formatNode(n)); + }) + } + + }) +} +function listNode(id) { + console.log(id); + var node = runtime.nodes.getNode(id); + if (node) { + console.log(node); + } +} +function listFlows() { +} + +function handleAddBreakpoint(args) { + var valid = false; + var bp = {}; + if (args.length > 0) { + bp.node = args[0]; + valid = true; + if (args.length > 1) { + if (args[1]==='i' || args[1]==='o') { + bp.type = args[1]; + if (args.length > 2) { + bp.index = args[2]; + } + } else { + valid = false; + } + } + } + if (valid) { + var id = addBreakpoint(bp); + console.log("Added breakpoint",id?id:""); + } else { + console.log("break [i|o] "); + } +} + +function handleRemoveBreakpoint(args) { + if (args.length === 0) { + breakpoints = {}; + console.log("Cleared all breakpoints"); + return; + } + if (args.length === 1 && breakpoints.hasOwnProperty(args[0])) { + delete breakpoints[args[0]]; + console.log("Cleared all breakpoints on node",args[0]); + return; + } + if (args.length === 2 && breakpoints.hasOwnProperty(args[0]) && (args[1] === 'i' || args[1] === 'o')) { + var c = Object.keys(breakpoints[args[0]][args[1]]).length; + breakpoints[args[0]][args[1]] = {}; + breakpoints[args[0]].c -= c; + if (breakpoints[args[0]].c === 0) { + delete breakpoints[args[0]]; + } + console.log("Cleared all",(args[1]==='i'?'input':'output'),"breakpoints on node",args[0]); + return; + } + if (args.length === 3 && breakpoints.hasOwnProperty(args[0]) && (args[1] === 'i' || args[1] === 'o')) { + var id = args.join(":"); + if (removeBreakpoint(args.join(":"))) { + console.log("Cleared breakpoint",id); + } + } +} +function handleInfo(args) { + if (args[0] === 'breakpoints') { + console.log(breakpoints); + } +} +function init(_runtime,_components) { + runtime = _runtime; + components = _components; + breakpoints = {}; + + + enabled = true; + + rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + prompt: 'nrdb> ' + }); + rl.on('line', function(input) { + input = input.trim(); + var parts = input.split(/\s+/); + var cmd = parts[0]; + var args = parts.slice(1) + + switch(cmd) { + case 'pause': + case 'p': + console.log("pausing"); + components.router.pause(); + break; + case 'continue': + case 'c': + console.log("resuming"); + components.router.resume(); + break; + case 'nodes': listNodes(); break; + case 'flows': listFlows(); break; + case 'node': if (args.length === 1) { listNode(args[0]); } break; + case 'break': handleAddBreakpoint(args); break; + case 'delete': handleRemoveBreakpoint(args); break; + case 'info': handleInfo(args); break; + } + var m = /^node\s+([^\s]+)$/.exec(input); + if (m) { + listNode(m[1]); + } + rl.prompt(); + }).on('close', function() { + console.log("exiting nrdb"); + runtime.stop().then(function() { + process.exit(0); + }); + }) + rl.setPrompt('nrdb> '); + rl.prompt(); + +} + +/* + * { + * node: id, + * type: 'i/o', + * index: 0 + * } + */ +function addBreakpoint(bp) { + if (!bp.hasOwnProperty('node')) { + return; + } + var node = runtime.nodes.getNode(bp.node); + if (!node || !node._config.hasOwnProperty('wires')) { + return; + } + + if (!bp.hasOwnProperty('type')) { + addBreakpoint({node:bp.node,type:'i'}); + addBreakpoint({node:bp.node,type:'o'}); + } else if (!bp.hasOwnProperty('index')) { + if (bp.type === 'i') { + addBreakpoint({node:bp.node,type:'i',index:0}); + } else { + for (var i=0;i 0) { + var sendEvent = sendQueue.shift(); + //console.log(ev.sourceNode.id+"["+ev.sourcePort+"] -> "+ev.destinationNode.id+"["+ev.destinationPort+"] : "+redDebugger.checkSendEvent(ev)); + if (!sendEvent.triggered && redDebugger.checkSendEvent(sendEvent)) { + sendEvent.triggered = true; + sendQueue.unshift(sendEvent); + pause(); + + } else { + sendEvent.destinationNode.receive(sendEvent.msg); + } + } + if (!paused && sendQueue.length > 0) { + setImmediate(processSendEvent); + } + } +} + function send(sourceNode, msg) { if (msg === null || typeof msg === "undefined") { return; @@ -73,13 +103,20 @@ function send(sourceNode, msg) { if (!sentMessageId) { sentMessageId = m._msgid; } + var sendEvent = { + sourceNode: sourceNode, + sourcePort:i, + destinationNode:node, + destinationPort:0 + } + if (msgSent) { - var clonedmsg = redUtil.cloneMessage(m); - sendEvents.push({n:node,m:clonedmsg}); + sendEvent.msg = redUtil.cloneMessage(m); } else { - sendEvents.push({n:node,m:m}); + sendEvent.msg = m; msgSent = true; } + sendEvents.push(sendEvent); } } } @@ -91,16 +128,16 @@ function send(sourceNode, msg) { if (!sentMessageId) { sentMessageId = redUtil.generateId(); } - sourceNode.metric("send",{_msgid:sentMessageId}); - for (i=0;i