# PM2 Dashboard monitoring

**URL:** <https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869>\
**Category:** Share Your Projects\
**Tags:** dashboard-2\
**Created:** [30 June 2025 07:06 UTC](https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869 "2025-06-30T07:06:36Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![gerrybartley](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/gerrybartley/32/67495_2.png) [@gerrybartley](https://discourse.nodered.org/u/gerrybartley)\
**Post date:** [30 June 2025 07:06 UTC](https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869/1 "2025-06-30T07:06:36Z")

</div>

For those of you using pm2 to launch and monitor your instances, this flow lets you monitor from within Node-Red

**Update 04-Jul-2025**  
Just realised that port info is not available via the pm2 command. In my case I had set up my dashboard directory names with the port number and so was able to retrieve this from the pm2 jlist output

I've updated the flow to retrieve the port numbers from the pids of the node-red instances  
So for example  
ss -lptn | grep pid=357260  
LISTEN 0 511 0.0.0.0:1889 0.0.0.0:\* users:(("node-red",pid=357260,fd=19))

```flows
[{"id":"bfa40032e7b236f5","type":"group","z":"d63da7d39b03706f","name":"Node-Red","style":{"fill":"#7fb7df","label":true,"color":"#000000"},"nodes":["5c732e4feb3baed0","a08d268203b4a005","96c575b95962a468","a30fcfc64ebdd450","18220c4ca4e6f003","6d2ef10283fecb0f","63f97a4ad7c4c9e8","8fec360a293987af"],"x":34,"y":739,"w":952,"h":149.5},{"id":"5c732e4feb3baed0","type":"exec","z":"d63da7d39b03706f","g":"bfa40032e7b236f5","command":"/usr/local/bin/pm2 jlist","addpay":"","append":"","useSpawn":"false","timer":"","winHide":false,"oldrc":false,"name":"Node Info","x":200,"y":800,"wires":[["a30fcfc64ebdd450"],[],[]]},{"id":"a08d268203b4a005","type":"inject","z":"d63da7d39b03706f","g":"bfa40032e7b236f5","name":"","props":[{"p":"topic","vt":"str"}],"repeat":"300","crontab":"","once":false,"onceDelay":0.1,"topic":"OS Info","x":95,"y":800,"wires":[["5c732e4feb3baed0"]],"l":false},{"id":"96c575b95962a468","type":"function","z":"d63da7d39b03706f","g":"bfa40032e7b236f5","name":"Map","func":"const dashboards=msg.payload\nlet props = [];\nconst numberPattern = /\\d{4}/g\n//payload[1].pm2_env.args[1]\n\ndashboards.forEach(dashboard => {\n const d1 = new Date(dashboard.pm2_env.pm_uptime)\n const d2 = new Date();\n const diff = Math.abs(d2.getTime() - d1.getTime())\n const updays = Math.floor(diff / (1000 * 60 * 60 * 24))\n const name = dashboard.name.toUpperCase()\n //const pid = dashboard.pm2_env.args[1]\n //node.warn(path)\n //const port =path.match(numberPattern)[0]\n const mem = (dashboard.monit.memory/ (1024 * 1024)).toFixed(1) + \" MB\"\n const status = dashboard.pm2_env.status=='online' ?'lawngreen':'orange'\n const handles =dashboard.pm2_env.axm_monitor[\"Active handles\"]\n const requests =dashboard.pm2_env.axm_monitor[\"Active requests\"]\n\n\n let arr = { \n \"PMID\": dashboard.pm_id ,\n \"NAME\": name,\n \"PORT\": \"\",\n \"PID\": dashboard.pid,\n \"UPTIME\": updays+'D',\n \"STATUS\": status,\n \"CPU\": dashboard.monit.cpu +'%',\n \"HANDLES\": handles.value,\n \"REQUESTS\": requests.value,\n \"MEMORY\": mem,\n };\n props.push(arr);\n})\n\nmsg.payload=props\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":780,"wires":[["6d2ef10283fecb0f"]]},{"id":"a30fcfc64ebdd450","type":"json","z":"d63da7d39b03706f","g":"bfa40032e7b236f5","name":"","property":"payload","action":"","pretty":false,"x":370,"y":780,"wires":[["96c575b95962a468"]]},{"id":"18220c4ca4e6f003","type":"ui-table","z":"d63da7d39b03706f","g":"bfa40032e7b236f5","group":"1c54b30d7d8e7c7b","name":"Node-RED","label":"DASHBOARD INSTANCES","order":3,"width":0,"height":0,"maxrows":0,"passthru":false,"autocols":false,"showSearch":false,"deselect":true,"selectionType":"click","columns":[{"title":"PM ID","key":"PMID","keyType":"key","type":"text","width":"","align":"start"},{"title":"NAME","key":"NAME","keyType":"key","type":"text","width":"","align":"start"},{"title":"PORT","key":"PORT","keyType":"key","type":"text","width":"","align":"start"},{"title":"UPTIME","key":"UPTIME","keyType":"key","type":"text","width":"","align":"start"},{"title":"STATUS","key":"STATUS","keyType":"key","type":"color","width":"","align":"start"},{"title":"HANDLES","key":"HANDLES","keyType":"key","type":"text","width":"","align":"start"},{"title":"CPU","key":"CPU","keyType":"key","type":"progress","width":"","align":"start"},{"title":"MEMORY","key":"MEMORY","keyType":"key","type":"text","width":"","align":"start"}],"mobileBreakpoint":"sm","mobileBreakpointType":"defaults","action":"replace","x":890,"y":840,"wires":[[]]},{"id":"6d2ef10283fecb0f","type":"function","z":"d63da7d39b03706f","g":"bfa40032e7b236f5","name":"cmd","func":"const instances=msg.payload\nmsg.instances=instances\nlet cmd=''\ninstances.forEach(instance => {\n cmd += `ss -lptn | grep pid=${instance.PID} && `\n \n});\n\nmsg.payload=cmd.slice(0,-3)\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":430,"y":840,"wires":[["63f97a4ad7c4c9e8"]]},{"id":"63f97a4ad7c4c9e8","type":"exec","z":"d63da7d39b03706f","g":"bfa40032e7b236f5","command":"","addpay":"payload","append":"","useSpawn":"false","timer":"","winHide":false,"oldrc":false,"name":"Get Port","x":580,"y":840,"wires":[["8fec360a293987af"],[],[]]},{"id":"8fec360a293987af","type":"function","z":"d63da7d39b03706f","g":"bfa40032e7b236f5","name":"Port","func":"const ports = msg.payload.split('\\n')\nlet instances =msg.instances\nmsg.instances= null\nlet i=0\n\nports.forEach(p => {\n if(p !=\"\"){\n const port = p.substr(54, 4)\n instances[i].PORT = port\n i++\n }\n \n});\nmsg.payload=instances\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":840,"wires":[["18220c4ca4e6f003"]]},{"id":"1c54b30d7d8e7c7b","type":"ui-group","name":"Dashboard","page":"40b1bea1d5488e9d","width":"12","height":1,"order":4,"showTitle":false,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"40b1bea1d5488e9d","type":"ui-page","name":"System Status","ui":"4740393b964cb7af","path":"/system","icon":"mdi-cog","layout":"tabs","theme":"11edfaf96d0baec1","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":16,"className":"","visible":"true","disabled":"false"},{"id":"4740393b964cb7af","type":"ui-base","name":"Tiger","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control","ui-dropdown","ui-chart","ui-gauge","ui-button","ui-template","ui-text","ui-table","ui-form"],"showPathInSidebar":false,"headerContent":"page","navigationStyle":"fixed","titleBarStyle":"hidden","showReconnectNotification":true,"notificationDisplayTime":"1","showDisconnectNotification":true},{"id":"11edfaf96d0baec1","type":"ui-theme","name":"Dark1","colors":{"surface":"#424242","primary":"#0094ce","bgPage":"#424242","groupBg":"#424242","groupOutline":"#424242"},"sizes":{"density":"default","pagePadding":"2px","groupGap":"6px","groupBorderRadius":"4px","widgetGap":"10px"}}]

```

Page

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/f/6/f6943657811c0b37c812a20aeb368acd17e9dc9c.png)

---

<div class="post-metadata">

**Author:** ![smanjunath211](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/smanjunath211/32/95742_2.png) [@smanjunath211](https://discourse.nodered.org/u/smanjunath211)\
**Post date:** [30 June 2025 08:08 UTC](https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869/2 "2025-06-30T08:08:54Z")

</div>

Very Nice.  
I use NSSM to run Node-red as a windows service. Any possibility of similar application possible. if you could direct me to the right path, i will try to pursue.

EDIT: `sc query state= all` seems to give me some info, will follow from there.

---

<div class="post-metadata">

**Author:** ![gerrybartley](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/gerrybartley/32/67495_2.png) [@gerrybartley](https://discourse.nodered.org/u/gerrybartley)\
**Post date:** [30 June 2025 08:19 UTC](https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869/3 "2025-06-30T08:19:55Z")

</div>

> [@smanjunath211](#):
>
> NSSM

Don't know much about nssm but possibly powershell will give you what you want  
Add whatever columns are available to the select part. then modify the map function to suit

I'm pretty sure you can install pm2 on windows as well

```auto
Get-WmiObject win32_service | ?{$_.PathName -like '*nssm*'} | select Name, DisplayName, State, PathName | ConvertTo-Json

```

or as you mentioned sc although I think the sc output will just be one entry in json and very hard to parse

```auto
sc.exe query type= service| ConvertTo-Json

```

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [30 June 2025 09:30 UTC](https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869/4 "2025-06-30T09:30:17Z")

</div>

I run PM2 on Windows - but if already using nssm, it is rather redundant.

Brave's Leo AI gave me this for more detail (not tried it):

```powershell
$Details = @()
$AllRunningServices = Get-CimInstance -class win32_service | Where-Object {$_.State -eq 'Running'} | Select-Object ProcessId, Name

foreach($procid in $AllRunningServices) {
    $Details += Get-Process | Where-Object {$_.Id -eq $procid.ProcessId} | Select-Object ProcessName, Id, CPU, @{Name = "serviceName"; Expression={$procid.Name}}
}

$Details | Sort-Object -Property CPU -Descending | ft

```

Get-process gives you lots of detail about your running services.

---

<div class="post-metadata">

**Author:** ![smanjunath211](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/smanjunath211/32/95742_2.png) [@smanjunath211](https://discourse.nodered.org/u/smanjunath211)\
**Post date:** [30 June 2025 13:19 UTC](https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869/5 "2025-06-30T13:19:14Z")

</div>

> [@TotallyInformation](#):
>
> nssm, it is rather redundant.

Does this mean I have to switch to PM2?  
I am happy with NSSM and till today have no issues running 5 different instances of NR as windows services.

---

<div class="post-metadata">

**Author:** ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)\
**Post date:** [30 June 2025 13:44 UTC](https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869/6 "2025-06-30T13:44:33Z")

</div>

> [@smanjunath211](#):
>
> > [@TotallyInformation](#):
> >
> > nssm, it is rather redundant.
> 
> Does this mean I have to switch to PM2?

No, NSSM is perfectly fine. Julian was stating installing and setting up PM2 is redundant as you have a working solution.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [30 June 2025 15:27 UTC](https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869/7 "2025-06-30T15:27:03Z")

</div>

> [@smanjunath211](#):
>
> Does this mean I have to switch to PM2?

Not at all, you can get everything shown in the PM2 example using PowerShell and more besides. If nssm is working for you, it is fine. I really meant that PM2 is redundant for you, not nssm. 😊

I use PM2 for convenience as I don't always have Node-RED running on my Windows desktop. My "live" version is on a separate server. Though recently, I have changed things so that Node-RED (via PM2) runs anyway after I log into my user account.

---

<div class="post-metadata">

**Author:** ![smanjunath211](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/smanjunath211/32/95742_2.png) [@smanjunath211](https://discourse.nodered.org/u/smanjunath211)\
**Post date:** [1 July 2025 01:06 UTC](https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869/8 "2025-07-01T01:06:05Z")

</div>

Hmmm. Need to improve my English comprehension.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [1 July 2025 09:09 UTC](https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869/9 "2025-07-01T09:09:35Z")

</div>

> [@smanjunath211](#):
>
> Need to improve my English comprehension

😊 No worries at all. It is good for us English speakers to be reminded that not everyone has our language as their first.

---

<div class="post-metadata">

**Author:** ![gerrybartley](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/gerrybartley/32/67495_2.png) [@gerrybartley](https://discourse.nodered.org/u/gerrybartley)\
**Post date:** [4 July 2025 13:34 UTC](https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869/10 "2025-07-04T13:34:35Z")

</div>

> [@gerrybartley](#):
>
> **Update 04-Jul-2025**  
> Just realised that port info is not available via the pm2 command. In my case I had set up my dashboard directory names with the port number and so was able to retrieve this from the pm2 jlist output
> 
> I've updated the flow to retrieve the port numbers from the pids of the node-red instances  
> So for example  
> ss -lptn | grep pid=357260  
> LISTEN 0 511 0.0.0.0:1889 0.0.0.0:\* users:(("node-red",pid=357260,fd=19))

Original flow updated to pull port information which is not available with pm2 commands

Liking the new visual renderer for flows ❤

---

<div class="post-metadata">

**Author:** ![thebaldgeek](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/thebaldgeek/32/93523_2.png) [@thebaldgeek](https://discourse.nodered.org/u/thebaldgeek)\
**Post date:** [5 July 2025 00:06 UTC](https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869/11 "2025-07-05T00:06:43Z")

</div>

This flow did not go quite as smooth as your last system stats flow @gerrybartley  
Vanalla install of 11 Node-RED's on Linux, using PM2 to manage.  
Adding extra debugs, I can see the usual slug of JSON data come back from the command, but the `map` node returns: `TypeError: Cannot read properties of null (reading '0')`  
I can't follow the code in that node, so will leave it at that. Just an FYI incase others try it.

---

<div class="post-metadata">

**Author:** ![gerrybartley](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/gerrybartley/32/67495_2.png) [@gerrybartley](https://discourse.nodered.org/u/gerrybartley)\
**Post date:** [5 July 2025 12:55 UTC](https://discourse.nodered.org/t/pm2-dashboard-monitoring/97869/12 "2025-07-05T12:55:01Z")

</div>

I forgot to delete the old flow when I updated the new flow so there were 2 flows. The old one is now deleted. I wonder did you download the old flow. In my process.json file for pm2 the directory for each instance is passed as an argument. The old flow read this argument so worked for me. However may cause that error you encountered. I think if you download the flow again it should work .If not I'm sure we can work out what's happening

This should be the code you see in the map function

```auto
const dashboards=msg.payload
let props = [];
const numberPattern = /\d{4}/g
//payload[1].pm2_env.args[1]

dashboards.forEach(dashboard => {
    const d1 = new Date(dashboard.pm2_env.pm_uptime)
    const d2 = new Date();
    const diff = Math.abs(d2.getTime() - d1.getTime())
    const updays = Math.floor(diff / (1000 * 60 * 60 * 24))
    const name = dashboard.name.toUpperCase()
    //const pid = dashboard.pm2_env.args[1]
    //node.warn(path)
    //const port =path.match(numberPattern)[0]
    const mem = (dashboard.monit.memory/ (1024 * 1024)).toFixed(1) + " MB"
    const status = dashboard.pm2_env.status=='online' ?'lawngreen':'orange'
    const handles =dashboard.pm2_env.axm_monitor["Active handles"]
    const requests =dashboard.pm2_env.axm_monitor["Active requests"]

    let arr = { 
        "PMID": dashboard.pm_id ,
        "NAME": name,
        "PORT": "",
        "PID": dashboard.pid,
        "UPTIME": updays+'D',
        "STATUS": status,
        "CPU": dashboard.monit.cpu +'%',
        "HANDLES": handles.value,
        "REQUESTS": requests.value,
        "MEMORY": mem,
        };
    props.push(arr);
})

msg.payload=props
return msg;

```

If you can post your pm2 jlist output suitably sanitized even better
