Openwhisk Sliding Window Action Monitor

This is a subflow node in order to monitor the latest performance (sliding window) of Openwhisk actions. The flow pings periodically the target Openwhisk installation in order to retrieve the last executed actions and extract statistics from their execution.

OW monitor

Configuration includes:

  • the polling time of the monitor (default 30 seconds, msg.pollingPeriod)
  • the target Openwhisk endpoint (msg.targetEndpoint)
  • credentials for that endpoint (msg.creds) in the form of user:key
  • the window of time (in minutes) in the past for which you want to retrieve results (msg.window)
  • an optional action name (msg.action), if one wants to filter specific action activations

The configuration can be set either via the flow UI or the incoming message. The incoming message prevails over the UI-set value.

The monitor can be stopped via a msg.stop=true message. The subflow uses a flow variable in order to control starting and stopping, hence only one OW monitor should be used in each flow.

The results include:

  • the raw data acquired (msg.payload.rawData) in the last window of time
  • the moving average of function duration (msg.payload.results.movingAverageDuration)
  • the moving average of init time (msg.payload.results.movingAverageInitTime)
  • the moving average of wait time (msg.payload.results.movingAverageWaitTime)
  • the number of cold starts (msg.payload.results.coldStarts)
  • the percentage with relation to the total calls (msg.payload.results.coldStartPercentage).
[{"id":"8262c8e75cfbabdd","type":"subflow","name":"OW Monitor","info":"\n\nThis is a subflow node in order to monitor the latest performance (sliding window) of Openwhisk actions. The flow pings periodically the target Openwhisk installation in order to retrieve the last executed actions and extract statistics from their execution.\n\nConfiguration includes:\n - the polling time of the monitor (default 30 seconds, `msg.pollingPeriod`)\n - the target Openwhisk endpoint (`msg.targetEndpoint`)\n - credentials for that endpoint (`msg.creds`) in the form of user:key\n - the window of time (in minutes) in the past for which you want to retrieve results (`msg.window`)\n - an optional action name (`msg.action`), if one wants to filter specific action activations\n\nThe configuration can be set either via the flow UI or the incoming message. The incoming message prevails over the UI-set value.\n\nThe monitor can be stopped via a `msg.stop=true` message. The subflow uses a flow variable in order to control starting and stopping, hence only one OW monitor should be used in each flow.\n\nThe results include the raw data acquired (`msg.payload.rawData`) in the last window of time, as well as the moving averages of duration (`msg.payload.results.movingAverageDuration`), init time (`msg.payload.results.movingAverageInitTime`) and wait time (`msg.payload.results.movingAverageWaitTime`) of the window. The number of cold starts are also included (`msg.payload.results.coldStarts`) as well as the percentage with relation to the total calls (`msg.payload.results.coldStartPercentage`).\n\n\n","category":"PHYSICS PEF","in":[{"x":60,"y":80,"wires":[{"id":"408b32b6eb687a08"}]}],"out":[{"x":820,"y":180,"wires":[{"id":"fdcd7c09df04efbb","port":0}]},{"x":560,"y":80,"wires":[{"id":"e3862e86d11b08d5","port":0}]}],"env":[{"name":"targetEndpoint","type":"str","value":"http://10.100.59.182:3233/api/v1","ui":{"label":{"en-US":"Target Endpoint"}}},{"name":"pollingPeriod","type":"num","value":"30","ui":{"label":{"en-US":"Polling Period (seconds)"}}},{"name":"window","type":"num","value":"10","ui":{"label":{"en-US":"Window of Time (minutes)"}}},{"name":"namespace","type":"str","value":"guest","ui":{"label":{"en-US":"Namespace"}}},{"name":"action","type":"str","value":"","ui":{"label":{"en-US":"Optional Action Name"}}},{"name":"creds","type":"cred","ui":{"label":{"en-US":"Credentials (user:key)"}}}],"meta":{},"color":"#E9967A","icon":"node-red/status.svg"},{"id":"f81580b2bf9ad891","type":"http request","z":"8262c8e75cfbabdd","name":"","method":"use","ret":"obj","paytoqs":"ignore","url":"","tls":"df3a306242e21be5","persist":false,"proxy":"","authType":"","x":290,"y":180,"wires":[["b61d171594e2cdf3"]]},{"id":"408b32b6eb687a08","type":"function","z":"8262c8e75cfbabdd","name":"defaults","func":"if (msg.hasOwnProperty('window')){\n    \n} else {\n    msg.window=env.get('window');\n}\n\nif (msg.hasOwnProperty('targetEndpoint')){\n    \n} else {\n    msg.targetEndpoint=env.get('targetEndpoint');\n}\n\nif (msg.hasOwnProperty('pollingPeriod')){\n    \n} else {\n    msg.pollingPeriod=env.get('pollingPeriod');\n}\n\nif (msg.hasOwnProperty('creds')){\n    \n} else {\n    msg.creds=env.get('creds');\n}\n\nif (msg.hasOwnProperty('namespace')){\n    \n} else {\n    msg.namespace=env.get('namespace');\n}\n\nif (msg.hasOwnProperty('action')){\n    \n} else {\n    msg.action=env.get('action');\n}\n\nif (msg.hasOwnProperty('stop')){\n    flow.set('stop',msg.stop);\n} else {\n    flow.set('stop',false);\n}\n\nmsg.headers={};\nvar auth = 'Basic ' + new Buffer(msg.creds).toString('base64');\nmsg.headers = {\n    \"Authorization\": auth\n}\n\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":180,"y":80,"wires":[["e3862e86d11b08d5"]]},{"id":"e3862e86d11b08d5","type":"function","z":"8262c8e75cfbabdd","name":"produce rate","func":"\nfunction sayHi(input) {\n  \n  if (flow.get('stop')) {\n    msg.payload=\"STOPPED\";\n    node.send([msg,null]);\n    clearTimeout(timerId);\n    \n  } else {\n      msg.since=Date.now()-msg.window*60*1000; //remove window of time from timestamp\n      //add since filter\n      msg.url=msg.baseurl+'?since='+msg.since;\n      msg.url=msg.url+'&limit=0';//by default, OW returns 30 results. We need this to return without limit\n      //add optional action name filter. This needs to be second since it may not always exist\n      if (msg.action!==\"\"){\n          msg.url=msg.url+'&name='+msg.action;\n      }\n\n      node.send([null,msg])\n      \n  }\n}\n\nmsg.baseurl=msg.targetEndpoint+'/namespaces/'+msg.namespace+'/activations';\n\nmsg.method='GET';\nvar timerId=setInterval(sayHi, msg.pollingPeriod*1000,msg);\n\n\n","outputs":"2","noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":80,"wires":[[],["f81580b2bf9ad891"]]},{"id":"b61d171594e2cdf3","type":"function","z":"8262c8e75cfbabdd","name":"preprocess results","func":"\n\nvar resultsArray=[];\n\nvar results={};\n\nfor (k=0;k<msg.payload.length;k++){\n    //from OW\n    results.duration=msg.payload[k].duration;\n    results.start=msg.payload[k].start;\n    results.end=msg.payload[k].end;\n    results.action=msg.payload[k].name;\n    results.namespace=msg.payload[k].namespace;\n    //results.success=msg.payload[k].response.success; This is not included in the summary results\n    for (i=0;i<msg.payload[k].annotations.length;i++){\n        if (msg.payload[k].annotations[i].key==='waitTime'){\n            results.waitTime=msg.payload[k].annotations[i].value;\n        }\n        if (msg.payload[k].annotations[i].key==='initTime'){\n            results.initTime=msg.payload[k].annotations[i].value;\n        }\n        \n    }\n    \n    if (!(results.hasOwnProperty('initTime'))){\n        results.initTime=0;\n    }\n    if (msg.action===\"\"){\n        resultsArray.push(results);    \n    } else {\n        if (results.action===msg.action){\n            resultsArray.push(results);\n        }\n    }\n    \n    results={};\n    \n}    \nmsg.payload={};\nmsg.payload=resultsArray;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":180,"wires":[["fdcd7c09df04efbb"]]},{"id":"10a43986c95ec05a","type":"comment","z":"8262c8e75cfbabdd","name":"PENDING","info":"check if the results are limited by something else, currently only 30 results are sent which seems los\n","x":310,"y":460,"wires":[]},{"id":"fdcd7c09df04efbb","type":"function","z":"8262c8e75cfbabdd","name":"averages","func":"var rawData=msg.payload;\nmsg.payload={};\nmsg.payload.rawData=rawData;\nvar samples=msg.payload.rawData.length;\n\nmsg.results={};\nmsg.results.averageDuration=0;\nmsg.results.averageWaitTime=0;\nmsg.results.averageInitTime=0;\nmsg.results.coldStarts=0;\n\nvar duration= [];//=new Array();\nvar waitTime=[];\nvar initTime=[];\n\n\nfor (i=0;i<msg.payload.rawData.length;i++){\n    msg.results.averageDuration=msg.results.averageDuration+msg.payload.rawData[i].duration;\n    duration.push(msg.payload.rawData[i].duration);\n    \n    msg.results.averageWaitTime=msg.results.averageWaitTime+msg.payload.rawData[i].waitTime;\n    waitTime.push(msg.payload.rawData[i].waitTime);\n    \n    msg.results.averageInitTime=msg.results.averageInitTime+msg.payload.rawData[i].initTime;\n    initTime.push(msg.payload.rawData[i].initTime);\n    if (msg.payload.rawData[i].initTime!==0){\n        msg.results.coldStarts=msg.results.coldStarts+1;\n    }\n\n}\nmsg.results.averageDuration=toFixedNumber(msg.results.averageDuration/samples,2);\nmsg.results.averageWaitTime=toFixedNumber(msg.results.averageWaitTime/samples,2);\nmsg.results.averageInitTime=toFixedNumber(msg.results.averageInitTime/samples,2);\nmsg.results.coldStartPercentage=toFixedNumber((msg.results.coldStarts/samples)*100,2);\n\nmsg.results.movingAverageDuration=msg.results.averageDuration;\ndelete msg.results.averageDuration;\n\nmsg.results.movingAverageWaitTime=msg.results.averageWaitTime;\ndelete msg.results.averageWaitTime;\n\nmsg.results.movingAverageInitTime=msg.results.averageInitTime;\ndelete msg.results.averageInitTime;\n\n\nmsg.payload.results=msg.results;\nreturn msg;\n\n\nfunction toFixedNumber(num, digits, base){\n  var pow = Math.pow(base||10, digits);\n  return Math.round(num*pow) / pow;\n}\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":680,"y":180,"wires":[[]]},{"id":"df3a306242e21be5","type":"tls-config","name":"","cert":"","key":"","ca":"","certname":"","keyname":"","caname":"","servername":"","verifyservercert":false,"alpnprotocol":""},{"id":"6f92d873678bcd26","type":"inject","z":"67840c79d73a38a4","name":"start","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":190,"y":120,"wires":[["5b6b2a38c50d3493"]]},{"id":"aac7a66d929ecefa","type":"debug","z":"67840c79d73a38a4","name":"MONITOR","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":120,"wires":[]},{"id":"adc7784d05984a40","type":"debug","z":"67840c79d73a38a4","name":"STOP","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":670,"y":220,"wires":[]},{"id":"cf1eadb38ea3bced","type":"inject","z":"67840c79d73a38a4","name":"stop","props":[{"p":"stop","v":"true","vt":"bool"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":190,"y":220,"wires":[["5b6b2a38c50d3493"]]},{"id":"5b6b2a38c50d3493","type":"subflow:8262c8e75cfbabdd","z":"67840c79d73a38a4","name":"","env":[{"name":"creds","type":"cred"}],"credentials":{"creds":""},"x":430,"y":160,"wires":[["aac7a66d929ecefa"],["adc7784d05984a40"]]}]

Flow Info

Created 6 months, 1 week ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • comment (x1)
  • debug (x2)
  • function (x4)
  • http request (x1)
  • inject (x2)
  • tls-config (x1)
Other
  • subflow (x1)
  • subflow:8262c8e75cfbabdd (x1)

Tags

  • Openwhisk
  • function
  • FaaS
  • performance
  • monitor
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option