Using flow timer between 2 communication flows in node red

I have a question regarding the flow timer node in node red.
As it is used to measure the time in a flow .Can it be used to measure the time difference between a subscribing and publishing mqtt node in 2 different flows .

Do you mean you want to get the time when a msg is published and then get the time when that msg is received in another flow? If so, why not make the time part of the msg being sent?

What is the exact name of the node you are referring to? (i.e. node-red-contrib-???)
You could also store the time in a global context variable (using a change node) just before the msg gets to the Matt-out node then get that global context variable right after the mutt-in node receives the msg.

There is no "flow timer" in node-red. I assume you are refering to the flow-timer subflow?

Are the 2 flows in the same node-red instance?

Yes your assumption is correct ,I am refering to the flow-timer subflow .
yes both the flows are in the same node red instance.

yes the publishing and receiving is in different flows .
"If so, why not make the time part of the msg being sent?"
how to do that ?

I am using the flow-timer subflow.

You can use a link node to link back to the first flow from the second. It will add a small overhead, but much smaller than mqtt

1 Like

Ok but i have to find between mqtt itself .

To make it more clear what my question is ,please find an example of the flow


In this as you can see there are 2 flows ,so i need to find the duration between the mqtt publish and subscribe node in flow 1 and 2 .

So the problem that i am facing is, In flow 2 there is stop_1 ,start_2 and stop_2.
The stop_1 is not measure with start_1 instead it check the duration between start_2 and stop_1.

So i would like to know is there any other way to find the durations ?
@Steve-Mcl @zenofmud @Colin

As I suggested, feed the wire from the mqtt node back to flow 1 via link nodes so the stop 1 is in flow 1

could you please explain with an example ,because i am not sure how to do it .

I am on my phone. Feed the mqtt node into a link Out node. Put a link In node in flow 1 and connect them. Feed the link In node into stop 1.

If the flows are on the same tab, this should work.

What do you see in the context viewer (refresh flow context)

Also, share your flow so I can check the setup.

I assumed that @Johnson meant that they are on different flow tabs, but perhaps that is not correct.

Sorry my reply has taken so long but here is an example of what I suggested:

[{"id":"cca3b718b20a5e1a","type":"inject","z":"d18a83f3d7fc792d","name":"msg.payload is an object","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"timer/test","payload":"{\"data\": \"test data\"}","payloadType":"json","x":230,"y":120,"wires":[["dde373e1826d3fbc"]]},{"id":"b6df7f4f674b4d3e","type":"mqtt out","z":"d18a83f3d7fc792d","name":"","topic":"timer/test","qos":"0","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"99e5f3cc902351bb","x":800,"y":120,"wires":[]},{"id":"f0b070bb33737edd","type":"mqtt in","z":"d18a83f3d7fc792d","name":"","topic":"timer/test","qos":"2","datatype":"auto-detect","broker":"99e5f3cc902351bb","nl":false,"rap":true,"rh":0,"inputs":0,"x":180,"y":180,"wires":[["50502470be1b690b"]]},{"id":"dde373e1826d3fbc","type":"change","z":"d18a83f3d7fc792d","name":"","rules":[{"t":"set","p":"payload.startTime","pt":"msg","to":"","tot":"date"}],"action":"","property":"","from":"","to":"","reg":false,"x":540,"y":120,"wires":[["b6df7f4f674b4d3e"]]},{"id":"50502470be1b690b","type":"change","z":"d18a83f3d7fc792d","name":"set end time + calc difference","rules":[{"t":"set","p":"payload.endTime","pt":"msg","to":"","tot":"date"},{"t":"set","p":"elapseTime","pt":"msg","to":"payload.endTime - payload.startTime","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":180,"wires":[["e47b1c081cbcbeeb"]]},{"id":"e47b1c081cbcbeeb","type":"debug","z":"d18a83f3d7fc792d","name":"elapseTime","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"time","targetType":"msg","statusVal":"elapseTime","statusType":"msg","x":810,"y":180,"wires":[]},{"id":"8b2af6fa1be84c76","type":"comment","z":"d18a83f3d7fc792d","name":"result is in milli seconds","info":"","x":520,"y":60,"wires":[]},{"id":"99e5f3cc902351bb","type":"mqtt-broker","name":"mqttpizw","broker":"192.168.48.70","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"timer/status/connecting","birthQos":"0","birthRetain":"false","birthPayload":"timer connecting","birthMsg":{},"closeTopic":"timer/status/disconnecting","closeQos":"0","closeRetain":"false","closePayload":"timer disconnecting","closeMsg":{},"willTopic":"timer/status/lwt","willQos":"0","willRetain":"false","willPayload":"timer last will","willMsg":{},"userProps":"","sessionExpiry":""}]

I think you simply need to move the recorded performance into a unique place in the msg as it travels your flow.

e.g...

Demo Flow (use CTRL-I to import):

[{"id":"9a693308.7ebaf","type":"subflow","name":"flow-timer","info":"","category":"","in":[{"x":80,"y":100,"wires":[{"id":"7fc82258.93e36c"}]}],"out":[{"x":580,"y":80,"wires":[{"id":"cebf40311504c8e9","port":0}]}],"env":[{"name":"name","type":"str","value":"measure","ui":{"icon":"font-awesome/fa-tag","label":{"en-US":"Timer Name"},"type":"input","opts":{"types":["str","env"]}}},{"name":"operation","type":"str","value":"start","ui":{"icon":"font-awesome/fa-cog","label":{"en-US":"Operation"},"type":"select","opts":{"opts":[{"l":{"en-US":"start"},"v":"start"},{"l":{"en-US":"stop"},"v":"stop"},{"l":{"en-US":"msg.topic"},"v":"msg.topic"},{"l":{"en-US":"msg.operation"},"v":"msg.operation"},{"l":{"en-US":"msg.payload"},"v":"msg.payload"}]}}},{"name":"delay","type":"num","value":"500","ui":{"icon":"font-awesome/fa-clock-o","label":{"en-US":"Delay (ms)"},"type":"input","opts":{"types":["num","env"]}}}],"meta":{"module":"node-red-contrib-flow-performance","type":"flow-performance","version":"1.0.2","author":"Steve-Mcl","desc":"Inline flow performance measure node","keywords":"node-red performance","license":"MIT"},"color":"#DAEAAA","icon":"node-red/timer.svg","status":{"x":580,"y":160,"wires":[{"id":"7fc82258.93e36c","port":1}]}},{"id":"7fc82258.93e36c","type":"function","z":"9a693308.7ebaf","name":"do operation","func":"var name = msg.perfName || env.get(\"name\");\nvar operation = msg.perfOperation || env.get(\"operation\");\nvar delay = env.get(\"delay\") || 500;\nvar measures = global.get(\"flow_timers\") || {};\nvar measure = measures[name] || { count: 0, stopNode: null, startNode: null};\n\nfunction doOp(measure, op){\n    if(op === \"start\"){\n        measure.start = Date.now();//change to process.hrtime\n        measure.stop = null;\n        measure.durationMs = null;\n        measure.stopNode && measure.stopNode.send([null, { payload: {text: 'waiting...'} }]);\n    } else if(op === \"stop\") {\n        if (typeof measure.count !== 'number' || isNaN(measure.count) || measure.count < 0) {\n            measure.count = 0\n        }\n        measure.startNode && measure.startNode.send([null, { payload: { text: '' } }]);\n        measure.count++\n        measure.stop = Date.now();//change to process.hrtime\n        measure.durationMs = measure.start ? measure.stop - measure.start : null;\n        msg._performance = measure;\n    }\n}\n\n\nif(operation === \"start\"){\n    measure.startNode = node\n    const opt = {\n        hour: \"2-digit\",\n        minute: \"2-digit\",\n        second: \"2-digit\",\n        fractionalSecondDigits: 3,\n        hour12: false\n    }\n    const tn = (new Date()).toLocaleTimeString('en-GB', opt)\n    node.send([null, { payload: { text: `Started '${name}' @ ${tn}` } }]);\n    doOp(measure, operation);\n} else if(operation === \"stop\") {\n    measure.stopNode = node\n    doOp(measure, operation);\n    node.send([null, { payload: { text: name + \": \" + measure.durationMs + \"ms (count:\" + measure.count + \")\" }}]);\n} else if(operation === \"msg.topic\") {\n    operation = msg.topic;\n    doOp(measure, operation);\n} else if(operation === \"msg.operation\") {\n    operation = msg.operation;\n    doOp(measure, operation);\n} else if(operation === \"msg.payload\") {\n    operation = msg.payload;\n    doOp(measure, operation);\n} else {\n    return [msg, null];\n}\nmeasures[name] = measure;\nglobal.set(\"flow_timers\", measures);\n\nif (typeof delay === 'number' && delay > 0) {\n    setTimeout(function() {\n        measure.start = Date.now();//change to process.hrtime\n        node.send([msg, null])\n    }, delay);\n} else {\n    node.send([msg, null])\n}\n\n","outputs":2,"noerr":0,"initialize":"","finalize":"// Code added here will be run when the\n// node is being stopped or re-deployed.\nvar name = env.get(\"name\");\nvar measures = global.get(\"flow_timers\") || {};\ndelete measures[name]\n","libs":[],"x":250,"y":100,"wires":[["cebf40311504c8e9"],[]]},{"id":"cebf40311504c8e9","type":"delay","z":"9a693308.7ebaf","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":450,"y":80,"wires":[[]]},{"id":"3fedc4b958341f4a","type":"subflow:9a693308.7ebaf","z":"3b62e67b4dfcce5d","name":"mqtt_timer start","env":[{"name":"name","value":"mqtt_timer","type":"str"},{"name":"delay","value":"1","type":"num"}],"x":760,"y":380,"wires":[["9e56793a70636b74"]]},{"id":"9e56793a70636b74","type":"mqtt out","z":"3b62e67b4dfcce5d","name":"","topic":"testtopic/example","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"ac55cebb5e5db9e6","x":1030,"y":380,"wires":[]},{"id":"5772fcaff558d12e","type":"inject","z":"3b62e67b4dfcce5d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":560,"y":380,"wires":[["3fedc4b958341f4a"]]},{"id":"6d598bd873da1dce","type":"mqtt in","z":"3b62e67b4dfcce5d","name":"","topic":"testtopic/example","qos":"1","datatype":"auto-detect","broker":"ac55cebb5e5db9e6","nl":false,"rap":true,"rh":0,"inputs":0,"x":560,"y":480,"wires":[["1625dc09243bd3fc"]]},{"id":"1625dc09243bd3fc","type":"subflow:9a693308.7ebaf","z":"3b62e67b4dfcce5d","name":"mqtt_timer stop","env":[{"name":"name","value":"mqtt_timer","type":"str"},{"name":"operation","value":"stop","type":"str"},{"name":"delay","value":"1","type":"num"}],"x":760,"y":480,"wires":[["ceb55f5a45d96035"]]},{"id":"21a3fe912aabc828","type":"subflow:9a693308.7ebaf","z":"3b62e67b4dfcce5d","name":"code_timer start","env":[{"name":"name","value":"code_timer","type":"str"},{"name":"delay","value":"1","type":"num"}],"x":760,"y":560,"wires":[["ed933e9c55fedd43"]]},{"id":"31fb1887d4aa97ca","type":"subflow:9a693308.7ebaf","z":"3b62e67b4dfcce5d","name":"code_timer stop","env":[{"name":"name","value":"code_timer","type":"str"},{"name":"operation","value":"stop","type":"str"},{"name":"delay","value":"1","type":"num"}],"x":1100,"y":560,"wires":[["7b007486fe018b9a"]]},{"id":"ed933e9c55fedd43","type":"function","z":"3b62e67b4dfcce5d","name":"long process","func":"setTimeout(function() {\n    node.send(msg)\n}, 400);\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":930,"y":560,"wires":[["31fb1887d4aa97ca"]]},{"id":"8afedc22e6362bc4","type":"debug","z":"3b62e67b4dfcce5d","name":"debug 155","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1110,"y":640,"wires":[]},{"id":"ceb55f5a45d96035","type":"change","z":"3b62e67b4dfcce5d","name":"","rules":[{"t":"move","p":"_performance","pt":"msg","to":"mqtt_timer","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":970,"y":480,"wires":[["21a3fe912aabc828"]]},{"id":"7b007486fe018b9a","type":"change","z":"3b62e67b4dfcce5d","name":"","rules":[{"t":"move","p":"_performance","pt":"msg","to":"code_timer","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":790,"y":640,"wires":[["8afedc22e6362bc4"]]},{"id":"ac55cebb5e5db9e6","type":"mqtt-broker","name":"","broker":"broker.hivemq.com","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"5","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""}]

Thank you for reply.
It works for me ,but i have question is it okay to exclude the change node .

[{"id":"9a693308.7ebaf","type":"subflow","name":"flow-timer","info":"","category":"","in":[{"x":80,"y":100,"wires":[{"id":"7fc82258.93e36c"}]}],"out":[{"x":440,"y":100,"wires":[{"id":"7fc82258.93e36c","port":0}]}],"env":[{"name":"name","type":"str","value":"measure","ui":{"icon":"font-awesome/fa-tag","label":{"en-US":"Timer Name"},"type":"input","opts":{"types":["str","env"]}}},{"name":"operation","type":"str","value":"start","ui":{"icon":"font-awesome/fa-cog","label":{"en-US":"Operation"},"type":"select","opts":{"opts":[{"l":{"en-US":"start"},"v":"start"},{"l":{"en-US":"stop"},"v":"stop"},{"l":{"en-US":"msg.topic"},"v":"msg.topic"},{"l":{"en-US":"msg.operation"},"v":"msg.operation"},{"l":{"en-US":"msg.payload"},"v":"msg.payload"}]}}}],"meta":{"module":"node-red-contrib-flow-performance","type":"flow-performance","version":"1.0.1","author":"Steve-Mcl","desc":"Inline flow performance measure node","keywords":"node-red performance","license":"MIT"},"color":"#DAEAAA","icon":"node-red/timer.svg","status":{"x":280,"y":160,"wires":[{"id":"7fc82258.93e36c","port":1}]}},{"id":"7fc82258.93e36c","type":"function","z":"9a693308.7ebaf","name":"do operation","func":"// @ts-ignore\nvar name = msg.perfName || env.get(\"name\");\n//const NODE_ENV = process.env.NODE_ENV;\n// @ts-ignore\nvar operation = msg.perfOperation || env.get(\"operation\");\nvar measures = global.get(\"flow_timers\") || {};\nvar measure = measures[name] || {};\n\nfunction doOp(measure, operation){\n    if(operation === \"start\"){\n        measure.start = Date.now();//process.hrtime();//Date.now();//change to process.hrtime\n        measure.stop = null;\n        measure.durationMs = null;\n    } else if(operation === \"stop\") {\n        measure.stop = Date.now();//process.hrtime();//Date.now();//change to process.hrtime\n        measure.durationMs = measure.start ? measure.stop - measure.start : null;\n        msg._performance = measure;\n    }\n}\n\n\nif(operation === \"start\"){\n    doOp(measure, operation);\n} else if(operation === \"stop\") {\n    doOp(measure, operation);\n    node.send([null, { payload: { text: name + \": \" + measure.durationMs + \"ms\" }}]);\n} else if(operation === \"msg.topic\") {\n    operation = msg.topic;\n    doOp(measure, operation);\n} else if(operation === \"msg.operation\") {\n    operation = msg.operation;\n    doOp(measure, operation);\n} else if(operation === \"msg.payload\") {\n    operation = msg.payload;\n    doOp(measure, operation);\n} else {\n    return [msg, null];\n}\nmeasures[name] = measure;\nglobal.set(\"flow_timers\", measures);\n\nreturn [msg, null,];","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":250,"y":100,"wires":[[],[]]},{"id":"556931c02c4ce1d0","type":"tab","label":"Flow 4","disabled":false,"info":"","env":[]},{"id":"3fedc4b958341f4a","type":"subflow:9a693308.7ebaf","z":"556931c02c4ce1d0","name":"mqtt_timer start","env":[{"name":"name","value":"mqtt_timer","type":"str"},{"name":"delay","value":"1","type":"num"}],"x":460,"y":740,"wires":[["9e56793a70636b74"]]},{"id":"9e56793a70636b74","type":"mqtt out","z":"556931c02c4ce1d0","name":"","topic":"testtopic/example","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"ac55cebb5e5db9e6","x":730,"y":740,"wires":[]},{"id":"5772fcaff558d12e","type":"inject","z":"556931c02c4ce1d0","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":740,"wires":[["3fedc4b958341f4a"]]},{"id":"6d598bd873da1dce","type":"mqtt in","z":"556931c02c4ce1d0","name":"","topic":"testtopic/example","qos":"1","datatype":"auto-detect","broker":"ac55cebb5e5db9e6","nl":false,"rap":true,"rh":0,"inputs":0,"x":260,"y":840,"wires":[["1625dc09243bd3fc","21a3fe912aabc828"]]},{"id":"1625dc09243bd3fc","type":"subflow:9a693308.7ebaf","z":"556931c02c4ce1d0","name":"mqtt_timer stop","env":[{"name":"name","value":"mqtt_timer","type":"str"},{"name":"operation","value":"stop","type":"str"},{"name":"delay","value":"1","type":"num"}],"x":460,"y":840,"wires":[["bbe61dd5faa4de62"]]},{"id":"21a3fe912aabc828","type":"subflow:9a693308.7ebaf","z":"556931c02c4ce1d0","name":"code_timer start","env":[{"name":"name","value":"code_timer","type":"str"},{"name":"delay","value":"1","type":"num"}],"x":460,"y":920,"wires":[["ed933e9c55fedd43"]]},{"id":"31fb1887d4aa97ca","type":"subflow:9a693308.7ebaf","z":"556931c02c4ce1d0","name":"code_timer stop","env":[{"name":"name","value":"code_timer","type":"str"},{"name":"operation","value":"stop","type":"str"},{"name":"delay","value":"1","type":"num"}],"x":820,"y":920,"wires":[["8afedc22e6362bc4"]]},{"id":"ed933e9c55fedd43","type":"function","z":"556931c02c4ce1d0","name":"long process","func":"setTimeout(function() {\n    node.send(msg)\n}, 400);\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":630,"y":920,"wires":[["31fb1887d4aa97ca"]]},{"id":"8afedc22e6362bc4","type":"debug","z":"556931c02c4ce1d0","name":"debug 155","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1010,"y":920,"wires":[]},{"id":"bbe61dd5faa4de62","type":"debug","z":"556931c02c4ce1d0","name":"debug 156","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":650,"y":840,"wires":[]},{"id":"ac55cebb5e5db9e6","type":"mqtt-broker","name":"","broker":"broker.hivemq.com","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"5","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""}]

My concern is ,since the mqtt_timer_stop and code_timer_start are in same flow ,how accurate is this method .

Thank you for your reply .this method also works for me .

no they are in same flow tabs

Yes, but you will need to grab the results from flow context otherwise the performance results will be overwritten in the travelling msg.

It is accurate to the nearest ms with regards to how you built your flow and how the underlying engine operates. What i mean is, if they are in series like START -> 200ms -> STOP --> START --> 400ms --> STOP then you will get 2 results of ~200ms and ~400ms (plus some very small additional time may be present due to JS/NodeJS garbage collection and Node/Node-RED/JIT operations). In short, when the flow-timer node is "hit" by a msg the time is recorded and nothing can change that.

If you want reliable timings, then 1 off timings are no good. You need to do the operation many hundreds/thousands of times and summarise the results as "min", "max", "average" (for example)

ok understood .
thank you so much for helping me