UNPKG

node-red-contrib-facial-recognition

Version:

Provides a node-red node for Facial Detection & Facial Recognition.

330 lines (320 loc) 10.5 kB
const ts=(new Date().toString()).split(' '); console.log([parseInt(ts[2],10),ts[1],ts[4]].join(' ')+" - [info] loadbalance Copyright 2019 Jaroslav Peter Prib"); function routeAdmin(msg) { this.log("routeAdmin "+JSON.stringify(msg.payload)); switch (msg.payload.action) { case "add": addRoute.apply(this,[msg.payload]); break; default: this.error("unknown action "+JSON.stringify(msg.payload)); } } function simpleEqual(a,b) { var p; for(p in a) { if((p in b) && a[p] == b[p]) continue; return false; } for(p in b) { if(p in a) continue; return false; } return true } function addRoute(override) { for(var r of this.dynamicPaths) { if(simpleEqual(r.override,override)) { this.warn("route already defined, activated if deactivated"); this.paths[r.path].status=1; return; } } var route=this.dynamicTemplate; switch (route.type) { case 'http request' : if(!override.hasOwnProperty('url')) { this.error("http request requires url"); return } break; default: this.error("invalid template type"); return; } var rnode=this; route.send=function(msg) { rnode.paths[msg.loadbalance].capacity++; if(msg.statusCode && msg.statusCode==200) { rnode.send([null,msg]); // send to port return; } rnode.error('Stopping path '+msg.loadbalance+" status code: "+msg.statusCode); rnode.paths[msg.loadbalance+1].status=0; setAvailable.apply(rnode); rnode.send([msg]); // send admin port }; delete override.action; this.dynamicPaths.push({node:this.dynamicTemplate,override:override,path:this.paths.length}); addPath.apply(this); this.routes++; this.logicalPorts++; setAvailable.apply(this); } function addPath() { this.paths.push({status:1,capacity:this.defaultcapacity,baseCapacity:this.defaultcapacity,count:0,history:Array(3).fill({count:0,capacity:this.defaultcapacity,status:1})}) } function updatePath(data) { try { if(Array.isArray(data)) { for(var a of data) { updatePath.apply(this,[a]); } } else { if(data.hasOwnProperty('status') && data.status!=this.paths[data.path].status) { if(data.status) { this.warn("Available path "+data.path); } else { this.warn("Unavailable path "+data.path); } } Object.assign(this.paths[data.path],data); if(data.capacity) { this.paths[data.path].baseCapacity=data.capacity; } } } catch(e) { this.error("loadbalance update failed: "+e +" data:"+data); } } function setAvailable() { var available=[]; for(var i=0;i<this.routes;i++) { if(this.paths[i].status) { available.push(i); } } this.available=available; var routes=this.dynamicRouting?this.dynamicPaths.length:this.routes; this.status({ fill: (this.available.length?(this.available.length==routes?'green':'yellow'):'red'), shape: 'dot', text: "Routes: "+routes + " Available: "+this.available.length}); } var setPaths={ random: function() { this.path=Math.floor(Math.random()*this.available.length); return this.path; }, next: function() { if(--this.path<0) { this.path=this.available.length-1; } return this.path; }, foldweighted: function() { for(this.path=0;this.path<this.available.length;this.path++) { if(this.paths[this.available[this.path]].capacity>0) return this.path; } return this.pathNoCapacity.apply(this); }, nextsmoothed:function() { for(var i=0;i<this.available.length;i++) { if(--this.path<0) { this.path=this.available.length-1; } if(this.paths[this.available[this.path]].capacity>=this.pathCapacityAvg) { return this.path; } } return this.pathNoCapacity.apply(this); }, admin:function() { return -1; // port assigned to admin }, discard:function() { return -2; // by placing on none existent port } } function mpsCheckLoop(node) { try{ for(var n,c=0,i=0;i<node.routes;i++) { n=node.paths[i]; n.capacity=n.baseCapacity; if(n.status) { c+=n.capacity; } } node.pathCapacityAvg=node.available.length?c/node.available.length:0; } catch(e) { node.error("mpsCheckLoop error: "+e); } } function checkLoop(node) { try{ for(var n,i=0;i<node.routes;i++) { n=node.paths[i]; n.history.unshift({status:n.status,count:n.count,capacity:n.capacity}); n.history.pop(); n.count=0; } } catch(e) { node.error("checkloop error: "+e); node.error(n); } } module.exports = function(RED) { function loadBalanceNode(n) { RED.nodes.createNode(this,n); var node=Object.assign(this,{path:0,paths:[],available:[],pathCapacityAvg:100,discards:0,dynamicPaths:[]},n); node.defaultcapacity=node.defaultcapacity||100; node.logicalPorts=node.outputs; node.dynamicRouting=(node.routes==1 && this.dynamic!==""); for(var i=0;i<node.routes;i++) { addPath.apply(this); } node.orginalSend=node.send; if(node.dynamicRouting) { updatePath.apply(node,[{path:0,status:0}]); node.send=function(msg) { var i=msg.findIndex(v => v||false); if(i<2) { node.orginalSend.apply(node,[msg]); return; } try{ var m=msg[i]; Object.assign(m,{loadbalance:i-2},node.dynamicPaths[i-2].override); this.paths[m.loadbalance].capacity--; node.dynamicTemplate.emit('input',m); } catch(e) { node.paths[i-1].status=0; setAvailable.apply(node); node.error("dynamic path made unavalable due to error "+e); } } } setAvailable.apply(node); try{ var setPath=setPaths[node.selection]; } catch(e) { node.error("Selection mode not found: "+node.selection); var setPath=setPaths.random; } try{ this.pathNoCapacity=setPaths[node.nocapacity]; if(!this.pathNoCapacity) throw Error("not found"); } catch(e) { node.error("No capacity selection mode not found, value: "+node.nocapacity); this.pathNoCapacity=setPaths.random; } try{ this.pathNoAvailability=setPaths[node.noavailability]; if(!this.pathNoAvailability) throw Error("not found"); } catch(e) { node.error("No Availability selection mode not found, value: "+node.noavailability); this.pathNoAvailability=setPaths.admin; } RED.events.on("nodes-started",function() { if(node.dynamicRouting) { node.dynamicTemplate=RED.nodes.getNode(node.dynamic); if(!node.dynamicTemplate) { node.error("Dynamic template node not found, id: "+this.dynamic); } node.dynamicTemplate.orginalsend=node.dynamicTemplate.send; node.dynamicTemplate.send = function(msg) { if(msg.loaddbalance) { node.error("load balance port 1 send") node.orginalSend([null].append(msg)); // send to port 1 return; } node.error("original send"); node.dynamicTemplate.orginalsend.apply(node.dynamicTemplate,arguments); }; } }); node.on('input', function (msg) { switch (msg.topic) { case 'loadbalance': updatePath.apply(node,[msg.payload]); var t=0; for(var r of node.routes) { if(r.status) { t+=r.capacity; } } setAvailable.apply(node); node.pathCapacityAvg=node.available.length?t/node.available.length:0; node.orginalSend(); return; case 'loadbalance.list': msg.payload={discards:node.discards,capacityAverage:node.pathCapacityAvg,available:node.available,paths:node.paths}; node.orginalSend(msg); return; case 'loadbalance.debug': node.error("selection mode: "+node.selection+"path pointer: "+node.path+" average capacity: "+node.pathCapacityAvg +" available: "+JSON.stringify(node.available) +" paths: "+JSON.stringify(node.paths)); node.orginalSend(); return; case 'loadbalance.route': routeAdmin.apply(node,[msg]); node.orginalSend(); return; } if(node.sticky && msg.req.cookies && msg.req.cookies[node.id]) { var pathLastTime = Number(msg.req.cookies[node.id]); if(node.paths[pathLastTime].status) { var o=Array(node.outputs).fill(null).fill(msg,pathLastTime+1,pathLastTime+2); node.send(o); if(node.mpsCheck) { node.paths[pathLastTime].capacity--; } return; } } try{ var port=node.available[setPath.apply(node)]; // offset for admin port node.paths[port].count++; port++; } catch(e) { node.discards++; if(node.available.length<1) { // then no Availability port=node.pathNoAvailability.apply(node); } if(port<-1) { // drop message node.send(); return; } var port=0; // send to admin } if(node.sticky && port) { if(!msg.cookies) msg.cookies = {}; msg.cookies[node.id]={ value: port-1, maxAge:360000 // 1 hour }; /* domain - (String) domain name for the cookie expires - (Date) expiry date in GMT. If not specified or set to 0, creates a session cookie maxAge - (String) expiry date as relative to the current time in milliseconds path - (String) path for the cookie. Defaults to / value - (String) the value to use for the cookie */ } var o=Array(node.logicalPorts).fill(null).fill(msg,port,port+1); node.send(o); if(node.mpsCheck) { node.paths[port-1].capacity--; } }); node.check = setInterval(checkLoop, 60000 ,node); // check every minute if(node.mps && ['foldweighted','nextsmoothed'].includes(node.selection) ) { node.mpsCheck = setInterval(mpsCheckLoop, 1000, node); // check every second node.log("Established mps capacity"); } node.on("close", function(removed,done) { if(node.mpsCheck) { clearInterval(node.mpsCheck) } clearInterval(node.check); done(); }); } RED.nodes.registerType("Load Balance",loadBalanceNode); };