Heresy I say
Or, writing my DWM/I3 status line in Javascript⌗
I decided I wanted to learn to program with Nodejs. Granted, this project wasn’t web programming, however it was a great way to learn asynchronous programming. I re-implemented my statusline in Nodejs. Here’s what I learned.
I titled this post ‘Heresy I say!’ because I’m a huge fan of light-weight utilities, and Node didn’t end up being a light-weight utility when compared to my current status line solution. I wasn’t expecting it to be light weight, but I also wasn’t expecting it to be as resource intensive as it ended up being. Javascript, as most developers have experienced, isn’t exactly a minimal language runtime. However, in spite of significant performance overhead when compared to my shell script statusline, there are some unexpected performance gains.
Background⌗
I currently run DWM on OpenSUSE Tumbleweed. I’ve also run I3 on both Arch and Tumbleweed. Both of these environments are very light weight and provide ways of customizing a ‘statusline’ to provide useful information such as the time, network info or other miscellaneous things. The lightweight nature of these enviroments is important to me because my laptop is extremely under-powered having only a dual core 1.6Ghz processor, 2Gb of RAM, and a hard drive (not solid state). I need the light weight environments so my computer will get out of my way.
The state of affairs⌗
My current statusline solution is a pair of shell scripts. The first one is just a one second loop that calls the second script which just displays the data and exits immediately. This solution uses almost 0 CPU and 0 RAM, and it gets the job done perfectly. So, why would I want to change it?
In spite of the ultra-lightweight nature of my current statusline there is one issue with it that I can ignore, but would like to fix. The clock skips seconds. I can tighten the loop, but that makes everything take more resources. Also, I do a lot of time based checks on processes in my statusline to make sure I’m only running things once every x
amount of time, and having the loop run every half second means that the processes are more frequently executed multiple times.
Ultimately though, I decided to re-implement my statusline in Javascript for two reasons:
- SCIENCE! Or, because I wanted to learn something.
- I’m a web developer and I’m behind on the NodeJS tech, so this was a good way to catch up. (See point 1)
Writing the code⌗
While you’re reading this, you might want to have my old statusline open, along with my new one for reference.
I ran into two issues that had to be overcome. The first was a major roadblock, and the second is a minor annoyance with Javacript. The first issue was how to get data out of the asynchronous callbacks. This puzzled me for a while because returning values from functions didn’t do what I expected. The simplest example of this is trying to get the SSID of my current wifi network. The final solution ended up being to use events to extract the data, which looks like this:
let statusLine = {};
class MyEmitter extends EventEmitter {};
const ssidEvent = new MyEmitter();
let getSsid = function() {
let d = new Date();
let seconds = d.getSeconds();
if (seconds % 30 === 0) {
exec('nmcli', {}, function(jsrr, out, err) {
let id = out.split('\n')[0]; id = id.split(' ');
ssidEvent.emit('event', id[3]);
if (err) {
console.error(err);
}
});
}
};
ssidEvent.on('event', function(ssid) {
statusLine.network = ssid + ' ' + network();
});
This wasn’t my initial instinct though, and unfortunately I don’t have my progression in git, so I’ll just have to edit what I have above to something similar to what I first attempted.
let getSsid = function() {
let d = new Date(); let seconds = d.getSeconds();
if (seconds % 30 === 0) {
let ssid = exec('nmcli', {}, function(jsrr, out, err) {
let id = out.split('\n')[0];
id = id.split(' ');
if (err) {
console.error(err);
} return id[3];
});
}
};
console.log(getSsid());
The above code doesn’t log my SSID the way I expected. It logs the object returned by exec
. It took a while, but eventually I remembered that Node is an event driven environment, so I thought I’d try that. As my first example showed, it ended up working.
The astute Node programmers among you will probably have noticed by now that I’ve used exec
instead of what is probably the more idiomatic choice spawn
. This is intentional. Spawn
returns data in a stream as it’s coming, exec
returns all the data in one big string after the process has exited, which makes it possible for me to get exactly one line out and parse it to get exactly the info I want. I wasn’t able to manage that with spawn
.
The second issue was easier to solve. Javascript’s date parsing is crap. I ended up getting my date and time info from the UNIX date
command. There is no concise with native Node to get a date formatted in anything other than ISO or U.S. format, and while I’m an American from birth, I think that day/month/year makes sense, and I don’t have space on my screen for anything more than the most abbreviated date format. The only way to get a date in any custom format is to do some nasty concatenation similar to:
let d = new Date();
let formattedDate = d.getDay() + '/' + d.getMonth() + '/' + d.getYear();
// Prints 5/0/118, which makes no sense at all. The day is
// probably the day of the week. The month is counting from 0, which is odd,
// and I really don't understand the 118 for the year.
That ignores the current time as well. Compare that to the “normal” way to do dates using format strings:
date +"%d/%m/%Y %I:%M:%S" # prints 12/01/2018 10:18:00
You can see the complete failure of native Javascript to provide sane date formatting.
Pros and cons⌗
Pros⌗
Aside from the learning, why would you want to write your statusline in Javascript? Honestly, the only benefits I can think of are:
- It’s asynchronous, which means that it won’t slow itself down.
- It’s Javascript. This is also a detriment, but the fact that it’s Javascript means that as a web developer it’s familiar.
Point one ended up being the most interesting to me. My Node backed statusline updates the clock exactly every second, in spite of the fact that it takes anywhere between 3% and 7% of my CPU at any given time. This isn’t true of my shell script based statusline.
Point two is a strange one. Compared to ZSH as a scripting language, Javascript is a more “comfortable language” to read for most people:
ZSH⌗
function my_wifi(){
ssid=$( nmcli | head -n 1 | awk -F ' ' '{print $4}' )
ip=$($HOME/bin/tmuxip)
strength=$(wifi_strength)
if [[ $ssid = 'hide_yo_kids_hide_yo_wi-fi' ]]; then
msg="\xe2\x8c\x82" else msg=$ssid
fi
echo "N:$msg $ip"
}
Javascript⌗
let getSsid = function() {
let d = new Date();
let seconds = d.getSeconds();
if (seconds % 30 === 0) {
exec('nmcli', {}, function(jsrr, out, err) {
let id = out.split('\n')[0];
id = id.split(' ');
ssidEvent.emit('event', id[3]);
if (err) {
console.error(err);
}
});
}
};
Shell script isn’t exactly difficult to read, but for a web developer who sees Javascript all the time, Javascript is arguably faster to parse mentally. However, ZSH is more concise. In the above example the ZSH function prints both the IP and SSID of my computer/network, the Javascript just gets my SSID, the IP info comes from a separate function.
Cons⌗
The biggest problem I have with the NodeJS statusline is how much CPU it consumes. Frequently it shows up at the top of htop
when sorted by %CPU. Granted, I’m not often doing anything terribly CPU intensive, but for a statusline, that seems a bit excessive.
The other issue is maintaining the program. The shell script statusline is almost exactly half as many lines as the Javascript one. This makes it a lot easier to maintain. The other thing is shell scripting is about as close as you can get to native programming without a compiler. Javascript on the other hand needs a full runtime to boot up before it can do anything. This runtime takes almost no time to start, however it does consume a lot more resources than the shell script.
TL;DR⌗
I reimplemented my statusline using Node as a backend. It ended up being twice the code, hogging more CPU than any statuline ever should, and being way more responsive that expected. I will probably never actually use my Javascript statusline, but it was a fun project, and I learned quite a bit, which was the ultimate goal.