zwave-js / zwave-js-ui

Full featured Z-Wave Control Panel UI and MQTT gateway. Built using Nodejs, and Vue/Vuetify
https://zwave-js.github.io/zwave-js-ui
MIT License
971 stars 203 forks source link

OpenWrt (and other minimal embedded environment) support #3473

Closed dwmw2 closed 10 months ago

dwmw2 commented 11 months ago

We package Domoticz for the OpenWrt distribution, and have historically used OpenZWave with it. Domoticz is now switching to ZWave-JS-UI so it would be great to package ZWave-JS-UI to work out of the box on OpenWrt too: https://github.com/nxhack/openwrt-node-packages/pull/1826

An always-on embedded router/AP is a perfect place to run home automation, but the environment can be a little constrained in CPU, memory and "disk" storage. For the last few years mine has been running on a MIPS24k with 128MiB of RAM.

We have a proof of concept basically working (not on that box, but on a quad Arm7 with 256MiB of RAM). I'd appreciate some help cleaning it up though, please. There are a few things to look at. Please let me know if I should file separate issues for each.

1. The package is huge — about 100MiB.

Are we doing it wrong? Are there any parts which can be left out? Here are some of the largest files....

644 ./node_modules/vue/node_modules/@vue/compiler-sfc/dist/compiler-sfc.js
644 ./node_modules/vue/packages/compiler-sfc/dist/compiler-sfc.js
676 ./node_modules/vis-network/standalone/esm/vis-network.min.js
676 ./node_modules/vis-network/standalone/umd/vis-network.min.js
704 ./node_modules/vis-network/esnext/esm/vis-network.js
740 ./node_modules/mqtt/dist/mqtt.js
748 ./node_modules/vis-network/esnext/umd/vis-network.js
1136    ./dist/assets/index-822e6be1.js
1192    ./node_modules/vis-network/peer/esm/vis-network.js
1224    ./node_modules/vis-network/peer/umd/vis-network.js
1364    ./node_modules/vis-network/dist/vis-network.esm.js
1368    ./node_modules/vuetify/dist/json/web-types.json
1400    ./node_modules/vis-network/dist/vis-network.js
1552    ./node_modules/vis-network/standalone/esm/vis-network.js
1592    ./node_modules/vis-network/standalone/umd/vis-network.js
1628    ./node_modules/vuetify/dist/vuetify.js
2748    ./node_modules/@mdi/js/mdi.js
2928    ./node_modules/@mdi/js/commonjs/mdi.js

Some of those seem like amalgamated or preprocessed versions of the original sources; in some cases we end up with what looks like three copies of the same code. Do we need to ship them all? In the case of MQTT there's a separate package for that anyway, which has the same three files that the zwave-js-ui package is shipping too.

root@OpenWrt:/usr/lib/node/mqtt/dist# ls -l
-rw-r--r--    1 root     root        319479 Nov 27 07:02 mqtt.esm.js
-rw-r--r--    1 root     root        741706 Nov 27 07:02 mqtt.js
-rw-r--r--    1 root     root        319504 Nov 27 07:02 mqtt.min.js

Some help understanding how this stuff works would be much appreciated.

Also, is it possible to compile down to bytecode or even native code, and ship only that to the target system? That might help with...

2. Memory usage

Its virtual memory size is 180MiB. Is there anything which can be done to reduce that? Is the full UI loaded into the interpreter even when only the ZWave←→MQTT bridge is active? Is it possible to run a version with the UI completely removed? It would actually be OK even if the user has to stop the zwave-js-ui process, and restart it with UI enabled.

Of course it would be nice to have a separate minimal server, and run the UI on demand (and as much as possible on the browser side), but that's probably a lot more work. Anything we can do to reduce memory usage in a relatively non-intrusive fashion would be useful though.

3. File system usage in /etc/zwave-js-ui.

In OpenWrt we typically use a writable overlay flash file system on top of a read-only root, and have the following categories of storage:

• The initial root filesystem (including the 100MiB package, as discussed above). • Files written to flash in the overlay, which are preserved across upgrade. • Files written to flash in the overlay, which are lost across upgrade. • Files in RAM, lost on every reboot.

We try to reduce the amount written to the flash file system — both in terms of size, and frequency of writes. We also try to minimize the amount that's preserved across system upgrade, as it's packaged into a smallish tarball which is then extracted into the new system after upgrade. Looking through the contents of /etc/zwave-js-ui, here's my idea of what might go where; please let me know if it's sane...

robertsLando commented 11 months ago

Hi David. Thanks for your issue, I will try to answer to all your questions.

Are we doing it wrong? Are there any parts which can be left out? Here are some of the largest files....

Everything inside node_modules folder is needed. Apart from that I use this command after building ui and server to cleanup useless files:

find . -mindepth 1 -maxdepth 1 \
    ! -name "node_modules" \
    ! -name "snippets" \
    ! -name ".git" \
    ! -name "package.json" \
    ! -name "server" \
    ! -name "dist" \

It essentially keeps only the folders/files that are listed and remove the rest. In-fact if you check what's inside docker you will see:

➜  ~ docker run --rm -it zwavejs/zwave-js-ui:latest /bin/sh
/usr/src/app # ls
dist          node_modules  package.json  server        snippets

In case of mqttjs (I'm also the maintainer of that library) I can easily use esbuild in order to create a bundle with all the files needed, that's not really easy to do with zwave-js-ui as it also have a webserver and many other things that makes that hard to bundle everything in a single file, paths to things will just be wrong everywhere or less predictable.

About compiling it to bytecode, I already do that with pkg, you see the available builds in releases: https://github.com/zwave-js/zwave-js-ui/releases/tag/v9.5.1

Its virtual memory size is 180MiB. Is there anything which can be done to reduce that? Is the full UI loaded into the interpreter even when only the ZWave←→MQTT bridge is active? Is it possible to run a version with the UI completely removed? It would actually be OK even if the user has to stop the zwave-js-ui process, and restart it with UI enabled.

Everything is loaded by default, UI, webserver etc... What could help here could be to use env vars to disable the webserver, dunno how much memory that could save BTW...

backups/: Flash file system, not preserved.

Not ideal but I let you decide

config-db/: Our packaging seems to point $ZWAVEJS_EXTERNAL_CONFIG to this, but I think that's wrong because it makes another copy of everything that's already shipped in the package? We should leave this to be used "sparingly, when custom files are absolutely necessary" as the documentation says. And then it should be in the flash file system and preserved over system upgrades.

The $ZWAVEJS_EXTERNAL_CONFIG env var is used because by default when doing a configuration db update that would use the location of the actual db in node_modules and that would be overriden by an update, also in docker-env that folder is not preserverd in volumes so we set it by default on a folder in store that is hidden to the user in store.

xxxxxxxx.json, xxxxxxxx.metadata.json, xxxxxxxx.values.json: Flash file system, perhaps not preserved? They can all be recreated, I believe?

I think yes? @AlCalzone can tell you that

config/, sessions/, snippets/: Not sure, these are empty for me.

dwmw2 commented 11 months ago

If I click on the controller in the node map, there's an orange button for 'Open' above the Background RSSI chart. If I click it, I get an empty browser window pointing at localhost:8091/controller-chart/#no-topbar and a backtrace from the zwave-js-ui process:

Error: Not Found
    at /usr/lib/node/zwave-js-ui/server/app.js:1167:17
    at Layer.handle [as handle_request] (/usr/lib/node/zwave-js-ui/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/usr/lib/node/zwave-js-ui/node_modules/express/lib/router/index.js:328:13)
    at /usr/lib/node/zwave-js-ui/node_modules/express/lib/router/index.js:286:9
    at Function.process_params (/usr/lib/node/zwave-js-ui/node_modules/express/lib/router/index.js:346:12)
    at next (/usr/lib/node/zwave-js-ui/node_modules/express/lib/router/index.js:280:10)
    at session (/usr/lib/node/zwave-js-ui/node_modules/express-session/index.js:479:7)
    at Layer.handle [as handle_request] (/usr/lib/node/zwave-js-ui/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/usr/lib/node/zwave-js-ui/node_modules/express/lib/router/index.js:328:13)
    at /usr/lib/node/zwave-js-ui/node_modules/express/lib/router/index.js:286:

Is that because of our packaging?

robertsLando commented 11 months ago

Oh that could be an issue, I changed the routing to hash based some months ago and I may have missed to fix the path for that specific window

robertsLando commented 11 months ago

https://github.com/zwave-js/zwave-js-ui/commit/c618fd10cd5c5f5e2444ed097c64dcc9e7f4aa73 Fixed on linked commit above

dwmw2 commented 11 months ago

Hi David. Thanks for your issue, I will try to answer to all your questions.

Thanks for the prompt response!

Are we doing it wrong? Are there any parts which can be left out? Here are some of the largest files....

Everything inside node_modules folder is needed. Apart from that I use this command after building ui and server to cleanup useless files:

On top of what @nxhack already does in the package build, that basically just removing the LICENSE, README.md and public/ directory I think. There's still a lot of duplication under node_modules.

In case of mqttjs (I'm also the maintainer of that library) I can easily use esbuild in order to create a bundle with all the files needed, that's not really easy to do with zwave-js-ui as it also have a webserver and many other things that makes that hard to bundle everything in a single file, paths to things will just be wrong everywhere or less predictable.

Is there something we should be doing to ensure that npm doesn't install a duplicate copy of mqttjs, and uses the one that's already supposed to be there?

(Apologies, some of these are basic questions about node packaging).

About compiling it to bytecode, I already do that with pkg, you see the available builds in releases: https://github.com/zwave-js/zwave-js-ui/releases/tag/v9.5.1

That's 230MiB which is more than twice the size of existing package. Is that expected, or is that a bad comparison? I guess this x86_64 executable includes the node runtime itself, and that's probably quite large? Is it possible to package just the bytecode and not a separate copy of the runtime?

Its virtual memory size is 180MiB. Is there anything which can be done to reduce that? Is the full UI loaded into the interpreter even when only the ZWave←→MQTT bridge is active? Is it possible to run a version with the UI completely removed? It would actually be OK even if the user has to stop the zwave-js-ui process, and restart it with UI enabled.

Everything is loaded by default, UI, webserver etc... What could help here could be to use env vars to disable the webserver, dunno how much memory that could save BTW...

Probably depends on whether setting the environment variable causes the whole thing to drop out of visibility and not get loaded by the runtime at all?

In an ideal world, we'd be able to make use of the existing webserver that's running on the system. That's not just about runtime footprint, but also about making the pages available from the same public-facing port as the existing user connection to the system. But that's probably a topic for another day.

The $ZWAVEJS_EXTERNAL_CONFIG env var is used because by default when doing a configuration db update that would use the location of the actual db in node_modules and that would be overriden by an update, also in docker-env that folder is not preserverd in volumes so we set it by default on a folder in store that is hidden to the user in store.

Hm, so ZWave-JS-UI will download a new copy of the configuration db? And by default that would try to overwrite the original one in the pre-packaged node_modules, which is perhaps read-only or at least changes will be lost when running in docker?

I wonder if we could package the config-db separately in that case, and not ship a copy in the actual zwave-js-ui code package at all? Or even not ship it at all, and download only the information we need on demand?

I think for now it's enough just to stop setting $ZWAVEJS_EXTERNAL_CONFIG. Can we disable the update of the configuration db completely?

xxxxxxxx.json, xxxxxxxx.metadata.json, xxxxxxxx.values.json: Flash file system, perhaps not preserved? They can all be recreated, I believe?

I think yes? @AlCalzone can tell you that

I threw away everything except {nodes,settings,users}.json and restarted, and that seemed to work OK. It did go and re-interview every node, but I think that's OK on a system upgrade. Then again, I suppose on most platforms which are actually powerful enough to run this, also copying a few highly compressible JSON files across sysupgrade is simple enough too. I might just preserve them until/unless someone objects.

config/, sessions/, snippets/: Not sure, these are empty for me.

* Config is used to load users custom configuration files, dunno if you need that

* Sessions contains the users sessions, them could be stored on ram

* Snippets: users custom snippets to use with driver function, dunno if you need that

Thanks.

robertsLando commented 11 months ago

Is there something we should be doing to ensure that npm doesn't install a duplicate copy of mqttjs, and uses the one that's already supposed to be there?

The only way I think is to try using tools like esbuild or else but as said that's not an easy route.

That's 230MiB which is more than twice the size of existing package. Is that expected, or is that a bad comparison?

That could be improved a bit by enabling executable zip, anyway that means that on runtime it will unzip his bytecode content somewhere in the device to run it (and the size will be the same). The reason it is so heavy is that it also ships nodejs within it, so no need to install nodejs runtime on the device in that case. I use this tool to package the application and convert the files to bytecode: https://github.com/yao-pkg/pkg (I'm actually a maintainer of that too and there is no available valid alternative to it right now, the future of it will be nodejs SEA but it's definetly far from being usable in a complex application).

Anyway pkg application are known to have a little more RAM impact as they store a reference on memory of each file packaged inside it (it creates a virtual filesystem to serve the files)

dwmw2 commented 11 months ago

Yeah, it ends up being a trade-off of "disk" (really flash) storage vs. RAM usage and in that case on this class of device it's often better to use more flash so that you use less RAM. If we can mmap files from the filesystem then the system can drop pages when they're not being accessed, and bring them back again on demand. But if they were decompressed into anonymous pages, there's nothing the system can do (we typically don't even have swap).

We typically use a compressed file system too, which means that compressing the file internally doesn't even give much of a win; an uncompressed file doesn't actually take that much more space on the real flash anyway. Compression just makes demand-paging hard for little benefit.

kpine commented 11 months ago
  • xxxxxxxx.json, xxxxxxxx.metadata.json, xxxxxxxx.values.json: Flash file system, perhaps not preserved? They can all be recreated, I believe?

Aside from the ZUI settings, these are the most important files to preserve forever. Speaking generally, it would be a very poor user experience to require re-interviewing the network anytime ZUI restarts.

dwmw2 commented 11 months ago

It wouldn't be on a ZUI restart, not even a router reboot. Only on a full system upgrade to a new version of the OS (including packages such as ZUI). But yeah, let's start by preserving them even then.

kpine commented 11 months ago

Changing most settings restarts ZUI, the process could crash, etc...

EDIT: N/M, I get what you're saying, the files persist until router upgrade.

dwmw2 commented 11 months ago

I suspect the ideal option for a constrained environment would be to ship the code which runs locally as bytecode, but still use the system's node runtime. And for any "files" accessed by it (including those served by the web server) to be just present on the file system (as they are at the moment).

Compiling to bytecode would presumably mean that we don't get to "share" certain libraries like mqttjs with other projects that also use them. But that sharing clearly isn't working right now anyway, and using bytecode will probably reduce the memory footprint and possible also the disk footprint too?

Is that feasible?

dwmw2 commented 11 months ago

FWIW running with --optimize-for-size --gc-interval=100 --max-old-space-size=50 and not connecting a web client doesn't seem to make a lot of difference:

  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
17811  8757 root     S     167m  69%   1% node --optimize-for-size --gc-interval=100 --max-old-space-size=50 /usr/bin/zwave-js-ui
robertsLando commented 11 months ago

I think that you may be able to enable those sharing of modules using pnpm in place of npm? Not 100% sure but may be worth a try

dwmw2 commented 11 months ago

I think that you may be able to enable those sharing of modules using pnpm in place of npm? Not 100% sure but may be worth a try

@nxhack I think that suggestion might be applicable to the whole of https://github.com/nxhack/openwrt-node-packages ?

nxhack commented 11 months ago

The issue is not the size of the package, but the memory size required by the node.js virtual machine.

IMO, we would choose the hardware to run node.js to its full potential.

AlCalzone commented 11 months ago

Some thoughts on the above: You should make sure to preserve the aforementioned .jsonl files. While they can be recreated, interviewing devices can fail due to temporary communication issues, and if battery-powered devices are involved, they won't be ready to use until they wake up and are fully interviewed.

root@OpenWrt:/usr/lib/node/mqtt/dist# ls -l
-rw-r--r--    1 root     root        319479 Nov 27 07:02 mqtt.esm.js
-rw-r--r--    1 root     root        741706 Nov 27 07:02 mqtt.js
-rw-r--r--    1 root     root        319504 Nov 27 07:02 mqtt.min.js

@robertsLando this looks to me like pkg doesn't do any tree shaking whatsoever and just includes everything in node_modules. Most likely only one of those is actually used at runtime. Pre-bundling and tree-shaking with esbuild before pkg should help, but I think you had issues with that? IMO if disk space is a constraint, getting tree-shaking to work reliably should be a top priority. (Also can't hurt to keep our official images and binaries small).

Hm, so ZWave-JS-UI will download a new copy of the configuration db?

This is used for updating the configuration DB on an existing system without updating the entire stack. Usually this is only possible if you're using an up to date version and there have been only config .json file changes recently. Which might happen in the next few months, but not very often when I'm actively working on the driver side.

robertsLando commented 10 months ago

@robertsLando this looks to me like pkg doesn't do any tree shaking whatsoever and just includes everything in node_modules.

Nope it doesn't it would be too complicated as there could be dinamically loaded files/libraries that would fail to load...

Also I'm not sure using esbuild will make the application reliable but I can try...

robertsLando commented 10 months ago

I have drafted a PR on #3480 please @dwmw2 check it out. Just run npm run bundle to create the bundle. The bundle will be inside build folder, that will be around 24MB. I tried it and seems to run

dwmw2 commented 10 months ago

Thanks. I ran 'npm run bundle' on my x86 host and it seems to run OK for the build directory. So I rsynced it to the router, where it looks like this:

root@OpenWrt:/usr/lib/node/zwave-bundle# du -s */*
4804    dist/static
1860    node_modules/@serialport
11352   node_modules/@zwave-js
4   snippets/access-store-dir.js
4   snippets/clone-config.js
4   snippets/pingNodes.js
4   snippets/reinterview-nodes.js
10244   src/bin

But here it doesn't find the @zwave-js/config which is present in that node_modules directory:

root@OpenWrt:~# HOME=/root NODE_PATH=/usr/lib/node/zwave-bundle/node_modules STORE_DIR=/etc/zwave-js-ui /usr/bin/node --optimize_for_size --max_old_space_size=128 --gc_interval
=100 /usr/lib/node/zwave-bundle/src/bin/index.js 
Error: Cannot find module '@zwave-js/config'
Require stack:
- /usr/lib/node/zwave-bundle/src/bin/index.js
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:1144:15)
    at Function.Module._load (node:internal/modules/cjs/loader:985:27)
    at Module.require (node:internal/modules/cjs/loader:1235:19)
    at require (node:internal/modules/helpers:176:18)
    at node_modules/@zwave-js/cc/build/cc/NotificationCC.js (/usr/lib/node/zwave-bundle/src/bin/index.js:51541:20)
    at __require (/usr/lib/node/zwave-bundle/src/bin/index.js:12:50)
    at node_modules/@zwave-js/cc/build/cc/BatteryCC.js (/usr/lib/node/zwave-bundle/src/bin/index.js:52565:28)
    at __require (/usr/lib/node/zwave-bundle/src/bin/index.js:12:50)
    at node_modules/@zwave-js/cc/build/cc/index.js (/usr/lib/node/zwave-bundle/src/bin/index.js:84296:23)
    at __require (/usr/lib/node/zwave-bundle/src/bin/index.js:12:50)

Should there have been a package.json somewhere in /usr/lib/node/zwave-bundle/node_modules/@zwave-js/config/ ? Or am I Doing It Wrong?

robertsLando commented 10 months ago

That's strange, doing the exact same on my end doesn't throw :(

➜  zwave-js-ui git:(esbuild) npm run bundle 

> zwave-js-ui@9.5.1 bundle
> node esbuild.js

Build took 593ms
Bundle size: 10.49MB

Copying "dist" to "build" folder
Copying "snippets" to "build" folder
Copying "node_modules/@serialport/bindings-cpp" to "build" folder
Asset "node_modules/@zwave-js/serial/node_modules/@serialport/bindings-cpp" does not exist. Skipping...
Asset "node_modules/zwave-js/node_modules/@serialport/bindings-cpp" does not exist. Skipping...
Copying "node_modules/@zwave-js/config/config/devices" to "build" folder
➜  zwave-js-ui git:(esbuild) PORT=8092 node build/src/bin/index.js
2023-12-11 17:20:00.833 INFO APP: Version: 9.5.1.b88bacb
2023-12-11 17:20:00.835 INFO APP: Application path:/home/daniel/GitProjects/zwave-js-ui/build
  ______  __          __                      _  _____     _    _ _____ 
 |___  /  \ \        / /                     | |/ ____|   | |  | |_   _|
    / /____\ \  /\  / /_ ___   _____         | | (___     | |  | | | |  
   / /______\ \/  \/ / _' \ \ / / _ \    _   | |\___ \    | |  | | | |  
  / /__      \  /\  / (_| |\ V /  __/   | |__| |____) |   | |__| |_| |_ 
 /_____|      \/  \/ \__,_| \_/ \___|    \____/|_____/     \____/|_____|

2023-12-11 17:20:00.841 WARN STORE: settings.json not found
2023-12-11 17:20:00.842 WARN STORE: scenes.json not found
2023-12-11 17:20:00.842 WARN STORE: nodes.json not found
2023-12-11 17:20:00.843 WARN STORE: users.json not found
2023-12-11 17:20:00.847 INFO APP: Listening on port 8092 protocol HTTP
2023-12-11 17:20:00.935 WARN BACKUP: Store backup is disabled
2023-12-11 17:20:00.937 WARN BACKUP: Nvm backup is disabled
2023-12-11 17:20:00.938 WARN Z-WAVE: Z-Wave driver not inited, no port configured
^C2023-12-11 17:20:02.109 WARN APP: Shutdown detected: closing clients...
2023-12-11 17:20:02.110 INFO GATEWAY: Closing Gateway...
2023-12-11 17:20:02.111 INFO GATEWAY: Driver is CLOSED
2023-12-11 17:20:02.112 INFO Z-WAVE: Client closed
➜  zwave-js-ui git:(esbuild) 
robertsLando commented 10 months ago

Oh wait I got it, let me try to fix

robertsLando commented 10 months ago

Ok it has been more painful then expected but it's working now. Try it now @dwmw2

dwmw2 commented 10 months ago
root@OpenWrt:/usr/lib/node/zwave-js-ui# HOME=/root NODE_PATH=/usr/lib/node/zwave-js-ui/node_modules STORE_DIR=/etc/zwave-js-ui node index.js 
2023-12-12 08:48:40.234 INFO APP: Version: 9.5.1
2023-12-12 08:48:40.278 INFO APP: Application path:/usr/lib/node/zwave-js-ui
  ______  __          __                      _  _____     _    _ _____ 
 |___  /  \ \        / /                     | |/ ____|   | |  | |_   _|
    / /____\ \  /\  / /_ ___   _____         | | (___     | |  | | | |  
   / /______\ \/  \/ / _' \ \ / / _ \    _   | |\___ \    | |  | | | |  
  / /__      \  /\  / (_| |\ V /  __/   | |__| |____) |   | |__| |_| |_ 
 /_____|      \/  \/ \__,_| \_/ \___|    \____/|_____/     \____/|_____|

2023-12-12 08:48:40.475 WARN STORE: scenes.json not found
Segmentation fault

Will debug further... first suspicion is the serial port bindings, but it doesn't open the serial port until later, does it? Don't have gdb on this board (yet) but strace suggests it's doing MQTT things.

writev(22, [{iov_base="\202", iov_len=1}, {iov_base="\31", iov_len=1}, {iov_base="\6|", iov_len=2}, {iov_base="\0\24", iov_len=2}, {iov_base="homeassistant/status", iov_len=20}, {iov_base="\1", iov_len=1}, {iov_base="\202", iov_len=1}, {iov_base="9", iov_len=1}, {iov_base="\6}", iov_len=2}, {iov_base="\0004", iov_len=2}, {iov_base="zwave/_CLIENTS/ZWAVE_GATEWAY-zwa"..., iov_len=52}, {iov_base="\1", iov_len=1}, {iov_base="\202", iov_len=1}, {iov_base="3", iov_len=1}, {iov_base="\6~", iov_len=2}, {iov_base="\0.", iov_len=2}, {iov_base="zwave/_CLIENTS/ZWAVE_GATEWAY-zwa"..., iov_len=46}, {iov_base="\1", iov_len=1}, {iov_base="\202", iov_len=1}, {iov_base="9", iov_len=1}, {iov_base="\6\177", iov_len=2}, {iov_base="\0004", iov_len=2}, {iov_base="zwave/_CLIENTS/ZWAVE_GATEWAY-zwa"..., iov_len=52}, {iov_base="\1", iov_len=1}], 24) = 198
futex(0xb3affc80, FUTEX_WAKE_PRIVATE, 1) = 1
futex(0x22d4eac, FUTEX_WAKE_PRIVATE, 1) = 1
+++ killed by SIGSEGV +++
Segmentation fault
dwmw2 commented 10 months ago

(What I've done here is just run 'npm run bundle' on the x86 host and then rsync the build/ directory to /usr/lib/node/zwave-js-ui/ on the target).

If I give it a different (and empty) STORE_DIR, it does start up OK. However, the HTTP service can't find its files. Even on th x86 host:

[dwoodhou@i7 build]$ node index.js 
2023-12-12 08:56:34.497 INFO APP: Version: 9.5.1
2023-12-12 08:56:34.500 INFO APP: Application path:/home/dwmw2/git/zwave-js-ui/build
  ______  __          __                      _  _____     _    _ _____ 
 |___  /  \ \        / /                     | |/ ____|   | |  | |_   _|
    / /____\ \  /\  / /_ ___   _____         | | (___     | |  | | | |  
   / /______\ \/  \/ / _' \ \ / / _ \    _   | |\___ \    | |  | | | |  
  / /__      \  /\  / (_| |\ V /  __/   | |__| |____) |   | |__| |_| |_ 
 /_____|      \/  \/ \__,_| \_/ \___|    \____/|_____/     \____/|_____|

2023-12-12 08:56:34.512 WARN STORE: settings.json not found
2023-12-12 08:56:34.517 WARN STORE: scenes.json not found
2023-12-12 08:56:34.531 INFO APP: Listening on port 8091 protocol HTTP
2023-12-12 08:56:34.540 WARN BACKUP: Store backup is disabled
2023-12-12 08:56:34.541 WARN BACKUP: Nvm backup is disabled
2023-12-12 08:56:34.542 WARN Z-WAVE: Z-Wave driver not inited, no port configured
2023-12-12 08:56:47.383 INFO APP: GET / 404 110.817 ms - 1013
Error: Not Found
    at /home/dwmw2/git/zwave-js-ui/build/index.js:280905:15
    at Layer.handle [as handle_request] (/home/dwmw2/git/zwave-js-ui/build/index.js:188344:9)
    at trim_prefix (/home/dwmw2/git/zwave-js-ui/build/index.js:188745:17)
    at /home/dwmw2/git/zwave-js-ui/build/index.js:188718:13
    at Function.process_params (/home/dwmw2/git/zwave-js-ui/build/index.js:188753:16)
    at next (/home/dwmw2/git/zwave-js-ui/build/index.js:188712:15)
    at session3 (/home/dwmw2/git/zwave-js-ui/build/index.js:242222:11)
    at Layer.handle [as handle_request] (/home/dwmw2/git/zwave-js-ui/build/index.js:188344:9)
    at trim_prefix (/home/dwmw2/git/zwave-js-ui/build/index.js:188745:17)
    at /home/dwmw2/git/zwave-js-ui/build/index.js:188718:13
dwmw2 commented 10 months ago

Is it looking for its HTTP files within that massive index.js? If I strace on the x86 host, I see it doing this when it gets the request, before reporting the error:

access("/home/dwmw2/git/zwave-js-ui/build/index.js", F_OK) = 0
openat(AT_FDCWD, "/home/dwmw2/git/zwave-js-ui/build/index.js", O_RDONLY|O_CLOEXEC) = 29
statx(29, "", AT_STATX_SYNC_AS_STAT|AT_EMPTY_PATH, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0775, stx_size=10845320, ...}) = 0
brk(0x5555cc33d000)                     = 0x5555cc33d000
read(29, "#!/usr/bin/env node\nvar __create"..., 10845320) = 10845320
close(29)                               = 0

That's loading 10MiB of data into memory just to look for a snippet of HTML inside it? :)

robertsLando commented 10 months ago

Sorry, forgot to include frontend, was focusing on backend 😆

robertsLando commented 10 months ago

@dwmw2 Now frontend is loading too. I still can't load configs files right now...

dwmw2 commented 10 months ago

Thanks for working on this!

I'm still seeing the same thing (load index.js and then give 404 error) with commit dd35bfbe though.

robertsLando commented 10 months ago

Try to rebuild now, I'm not getting that error, are you opening http://localhost:8081 ?

robertsLando commented 10 months ago

Is it looking for its HTTP files within that massive index.js? If I strace on the x86 host, I see it doing this when it gets the request, before reporting the error:

No no that was just wrong, now it will load index.html from dist folder

dwmw2 commented 10 months ago

The UI is loading now, thanks! I see this on the target host which I don't think I've seen before:

2023-12-12 09:40:52.633 ERROR APP: spawn udevadm ENOENT
Error: spawn udevadm ENOENT
    at Process.ChildProcess._handle.onexit (node:internal/child_process:286:19)
    at onErrorNT (node:internal/child_process:484:16)
    at processTicksAndRejections (node:internal/process/task_queues:82:21)

Still crashes with the 'real' config file.

Separately, I tried adding 'minify: true' to esbuild options. Doesn't work but looks like it would be nice to have!

dwmw2 commented 10 months ago

On the x86 host with the same config file it just sits there and does nothing after printing the 'scenes.json not found' line.

robertsLando commented 10 months ago

2023-12-12 09:40:52.633 ERROR APP: spawn udevadm ENOENT

@dwmw2 If you google that error you will see some solutions, that's not something related to the bundle

dwmw2 commented 10 months ago

Ah, the logs are disabled in the config file, so this isn't a good comparison...

dwmw2 commented 10 months ago

It's working fine; just hadn't said anything about listening on port 8091. And couldn't open the serial port since the dongle isn't on the x86 host. Trying again with debug enabled on the target:

root@OpenWrt:/usr/lib/node/zwave-js-ui# HOME=/root NODE_PATH=/usr/lib/node/zwave-js-ui/node_modules STORE_DIR=/etc/zwave-js-ui node index.js 
2023-12-12 09:51:53.165 INFO APP: Version: 9.5.1
2023-12-12 09:51:53.210 INFO APP: Application path:/usr/lib/node/zwave-js-ui
  ______  __          __                      _  _____     _    _ _____ 
 |___  /  \ \        / /                     | |/ ____|   | |  | |_   _|
    / /____\ \  /\  / /_ ___   _____         | | (___     | |  | | | |  
   / /______\ \/  \/ / _' \ \ / / _ \    _   | |\___ \    | |  | | | |  
  / /__      \  /\  / (_| |\ V /  __/   | |__| |____) |   | |__| |_| |_ 
 /_____|      \/  \/ \__,_| \_/ \___|    \____/|_____/     \____/|_____|

2023-12-12 09:51:53.410 WARN STORE: scenes.json not found
2023-12-12 09:51:53.574 INFO APP: Listening on port 8091 protocol HTTP
2023-12-12 09:51:53.919 INFO MQTT: Connecting to mqtt://90.155.92.219:1884
2023-12-12 09:51:54.595 WARN BACKUP: Store backup is disabled
2023-12-12 09:51:54.602 WARN BACKUP: Nvm backup is disabled
2023-12-12 09:51:54.714 INFO Z-WAVE: Connecting to /dev/ttyACM0
2023-12-12 09:51:54.833 INFO MQTT: MQTT client connected
2023-12-12 09:51:54.874 DEBUG MQTT: Subscribing to homeassistant/status with options { qos: 1 }
2023-12-12 09:51:54.904 DEBUG MQTT: Subscribing to zwave/_CLIENTS/ZWAVE_GATEWAY-zwave-js-ui/broadcast/# with options { qos: 1 }
2023-12-12 09:51:54.913 DEBUG MQTT: Subscribing to zwave/_CLIENTS/ZWAVE_GATEWAY-zwave-js-ui/api/# with options { qos: 1 }
2023-12-12 09:51:54.922 DEBUG MQTT: Subscribing to zwave/_CLIENTS/ZWAVE_GATEWAY-zwave-js-ui/multicast/# with options { qos: 1 }
Segmentation fault
robertsLando commented 10 months ago

LOL segmentation fault 😕 It's hard to debug that...

dwmw2 commented 10 months ago

Happens with MQTT disabled too. Still inclined to blame the prebuilt serial port bindings. I have a native copy of those built for this system.

robertsLando commented 10 months ago

For sure that's from serialport as the segmentation fault only happens on native code. Did you tried to do a build from source for that?

PS: I enabled minification and now the entire bundle is about 5.37MB :)

dwmw2 commented 10 months ago

Trying marking the serial port stuff as external so that it uses the native build.

dwmw2 commented 10 months ago
root@OpenWrt:/usr/lib/node/zwave-js-ui/node_modules# rm -rf @serialport/
root@OpenWrt:/usr/lib/node/zwave-js-ui/node_modules# ln -sf ../../@serialport/bindings

That works (/cc @nxhack). Next up...

2023-12-12 10:13:22.611 INFO Z-WAVE: Controller status: Driver: Failed to initialize the driver: ZWaveError: The driver is not ready or has been destroyed (ZW0103)
    at Driver3.ensureReady (/usr/lib/node/zwave-js-ui/index.js:161650:17)
    at Driver3.sendMessage (/usr/lib/node/zwave-js-ui/index.js:162987:14)
    at ZWaveController.identify (/usr/lib/node/zwave-js-ui/index.js:129507:43)
    at Driver3.initializeControllerAndNodes (/usr/lib/node/zwave-js-ui/index.js:160855:33)
    at Immediate.<anonymous> (/usr/lib/node/zwave-js-ui/index.js:160742:24) (ZW0100)
2023-12-12 10:13:22.879 DEBUG Z-WAVE: Client listening on '/dev/ttyACM0' is destroyed, closing
dwmw2 commented 10 months ago

Hm, that one went away when I enabled logging (for which I had to reinstate the config override directory, which I'd set to "" to make it start up on the x86). I think it's working.

Thanks so much for your help!

dwmw2 commented 10 months ago

The minified build fails here with:

... typeof __webpack_require__=="function"?"webpack=true":""].filter(Boolean).join(" ");throw new Error("No native build was found for "+o+`
^
Error: No native build was found for platform=linux arch=arm runtime=node abi=115 uv=1 armv=7 libc=glibc node=20.10.0
    loaded from: /usr/lib/node

    at Function.eb.resolve.eb.path (/usr/lib/node/zwave-js-ui/index.js:77:12632)
    at eb (/usr/lib/node/zwave-js-ui/index.js:77:11939)
    at /usr/lib/node/zwave-js-ui/index.js:79:2490
    at /usr/lib/node/zwave-js-ui/index.js:2:250
    at /usr/lib/node/zwave-js-ui/index.js:79:8451
    at /usr/lib/node/zwave-js-ui/index.js:2:250
    at /usr/lib/node/zwave-js-ui/index.js:80:8772
    at /usr/lib/node/zwave-js-ui/index.js:2:250
    at /usr/lib/node/zwave-js-ui/index.js:80:9282
    at /usr/lib/node/zwave-js-ui/index.js:2:250
dwmw2 commented 10 months ago

I get it on x86 too:

Error: No native build was found for platform=linux arch=x64 runtime=node abi=108 uv=1 libc=glibc node=18.18.2
    loaded from: /home/dwmw2/git/zwave-js-ui
dwmw2 commented 10 months ago

With sourcemap:

/home/dwmw2/git/zwave-js-ui/node_modules/node-gyp-build/node-gyp-build.js:60
  throw new Error('No native build was found for ' + target + '\n    loaded from: ' + dir + '\n')
        ^
Error: No native build was found for platform=linux arch=x64 runtime=node abi=108 uv=1 libc=glibc node=18.18.2
    loaded from: /home/dwmw2/git/zwave-js-ui

    at Function.eb.resolve.eb.path (/home/dwmw2/git/zwave-js-ui/node_modules/node-gyp-build/node-gyp-build.js:60:9)
    at eb (/home/dwmw2/git/zwave-js-ui/node_modules/node-gyp-build/node-gyp-build.js:22:30)
    at /home/dwmw2/git/zwave-js-ui/node_modules/@serialport/bindings-cpp/dist/load-bindings.js:11:38
    at /home/dwmw2/git/zwave-js-ui/build/index.js:2:250
    at /home/dwmw2/git/zwave-js-ui/node_modules/@serialport/bindings-cpp/dist/darwin.js:8:25
    at /home/dwmw2/git/zwave-js-ui/build/index.js:2:250
    at /home/dwmw2/git/zwave-js-ui/node_modules/@serialport/bindings-cpp/dist/index.js:23:18
    at /home/dwmw2/git/zwave-js-ui/build/index.js:2:250
    at /home/dwmw2/git/zwave-js-ui/node_modules/serialport/dist/serialport.js:5:24
    at /home/dwmw2/git/zwave-js-ui/build/index.js:2:250
robertsLando commented 10 months ago

@dwmw2 Oh I know why... The solution is to don't do the minification from esbuild but do it after the npm run bundle. This is because I actually have to patch the output file here:

https://github.com/zwave-js/zwave-js-ui/blob/330d085805f5914c8873a78c3df854b1081da423/esbuild.js#L85-L97

And that find/replace is broken when I do minification. I suggest you to use terser or an online tool for now to minify it

I have disabled minification from esbuild options now

AlCalzone commented 10 months ago

@robertsLando This is a problem, since some parts of Z-Wave JS match on the constructor names:

Driver3.ensureReady

You'll have to enable the keepNames option: https://esbuild.github.io/api/#keep-names

robertsLando commented 10 months ago

@AlCalzone That's only relevant when doing minification and I don't do that for the reason described above

robertsLando commented 10 months ago

I have just pushed a change to the PR to also support minification with npm run bundle -- --minify command. Try it and let me know @dwmw2

dwmw2 commented 10 months ago

That works as of commit 72bdc6246, thanks. The minified version is "only" 180MiB instead of 200MiB too, which I think is fairly repeatable. It's still a lot for something that used to be virtually nothing, but it's working.

  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
29067 28701 root     S     181m  75%   0% /usr/bin/node --optimize_for_size --max_old_space_size=64 --gc_interval=100 index.js

I can use it from Domoticz over MQTT and latency is minimal.

robertsLando commented 10 months ago

The minified version is "only" 180MiB instead of 200MiB too, which I think is fairly repeatable. It's still a lot for something that used to be virtually nothing, but it's working.

If you are referencing to virtual memory I cannot do so much about that, I think that comes from all the things we keep in memory expecially zwave-js in its jsonl db that loads everything on RAM