7
\$\begingroup\$

Backstory

Recently, we've changed the PCs at my workplace to un Linux Mint.
It works amazing, is blazing fast and the adjustment time was very short.

However, I noticed my PC would freeze completely, sometimes.
I've determined that it is low RAM memory.

My working habits require me to open 10+ tabs (sometimes 100, in 5-8 Google Chrome windows).
This makes it so the swap is stuffed, as well as the RAM (Physical Memory).
Sometimes I have to use a VM running off of my PC, which takes 4GB for itself.

The code

Since old habits are hard to kill and isn't fun, I've decided to write code (which is a lot more fun).

Since Linux Mint doesn't warn about low available RAM, I've scattered around and put together a script to run every minute, and warn me when the memory is running low.

This script can also be executed from the console, displaying a message on it, if needed.
A detection method had to be added, since the output sent to cron is emailed by default.

#!/usr/bin/env bash
# based from https://askubuntu.com/questions/234292/warning-when-available-ram-approaches-zero
LANG=en_US.UTF-8

# gets available and total ram
RAM=$(free -m)
total=$(echo "$RAM"|awk '/^[mM]em\.?:/{print $2}')
available=$(echo "$RAM"|awk '/^[mM]em\.?:/{print $7}')

# warn if less than these levels is free
# warning = 20%
# critical = 10%
WARNING=$(expr $total / 5)
CRITICAL=$(expr $total / 10)

# -h int:transient:1 <-- don't store the notification
# https://unix.stackexchange.com/questions/393397/get-notify-send-to-clear-itself-from-notification-tray/401587
if [ $available -lt $CRITICAL ]; then
    # using -u critical doesn't allow the notification to go away after -t ms have past
    # this causes issues if afk, since the notifications will queue until the -u critical is closed
    notify-send -i error -h int:transient:1 -t 60000 "Low memory!" "$available/$total MB free, critical at $CRITICAL MB"
elif [ $available -lt $WARNING ]; then
    notify-send -h int:transient:1 -t 15000 "Memory is going low" "Available: $available/$total MB, warns at $WARNING MB"
fi

# outputs if not ran by cron
# https://unix.stackexchange.com/questions/46789/check-if-script-is-started-by-cron-rather-than-invoked-manually
if [ -t 0 ]; then
    echo "Available: $available/$total MB, warns at $WARNING MB, critical at $CRITICAL MB"
fi

This script runs in crontab -e, with the following:

# https://unix.stackexchange.com/questions/247860/notify-send-doesnt-work-at-cinnamon
DISPLAY=":0.0"
XAUTHORITY="/home/<username>/.Xauthority"
XDG_RUNTIME_DIR="/run/user/<output from id -u>"

* * * * * /usr/bin/env bash /home/<username>/<script-from-above>.sh

Conclusion

It was really hard to get to this point, with plenty of issues.
Most information was available online, but, making it work was a pain.

I'm not really good with Bash scripting, which probably means that I have some really bad mistakes.
As far as I know, it works as intended, when intended:

Notification

Besides that, is there anything I can improve or change?
Any optimization/optimisation I can do?
Any localization/localisation issues that may come?

\$\endgroup\$
10
  • 1
    \$\begingroup\$ Bravo for a large percentage of your code comments being StackExchange links. \$\endgroup\$
    – chicks
    Sep 30, 2018 at 14:55
  • \$\begingroup\$ Consider adding a couple gigabytes of swap space, it is a cheap solution nowadays. Easy to do in the form of an extra swapfile. Or Simply buy more ram, if you need it. \$\endgroup\$ Sep 30, 2018 at 14:57
  • \$\begingroup\$ @MichelBillaud Well, even swap has a limit. To add swap, I would have to re-format the SSD (or mess with already-existing partitions) and that's a bigger pain that warning me that i have too much trash open. \$\endgroup\$ Sep 30, 2018 at 14:58
  • \$\begingroup\$ @chicks That's where I took most of the solutions I've found for my problems. I like to give credit where credit is due. \$\endgroup\$ Sep 30, 2018 at 15:00
  • 1
    \$\begingroup\$ Adding swap soon reaches a point of diminishing returns. You'd do better by reducing kernel overcommit, or identifying problem processes (e.g. Web browsers, for some reason) and arranging to run them with a reasonable ulimit -v. \$\endgroup\$ Oct 24, 2018 at 14:13

2 Answers 2

3
\$\begingroup\$

Kudos to you! This is a nice little script, easy to read and to understand. However, there is no reason to reach for expr. Most shells (including dash or even busybox sh) can interpret arithmetic expressions on their own:

WARNING=$(($total / 5))
CRITICAL=$(($total / 10))

Also, in [ ] expressions, I'd use quotes around variables, to make sure that they don't contain any spaces. In this case, we can be sure that they won't contain some, but it's still good practice to use them:

if [ "$available" -lt "$CRITICAL" ]; then
    ...
elif [ "$available" -lt "$WARNING" ]; then
    ...
fi

Other than that, I'd write the -u comment as a direct negative instead of a positive:

# Don't use `-u critical', as it will keep the notification even after -t ms
# and will cause notifications to queue up.

This comment still contains the same message: "don't use -u critical", but it's much more direct.

Since no bashism are used, we can even change the shebang to #!/bin/sh. By the way, some of those remarks were also found by ShellCheck, but it rightfully marks them as info level only.

\$\endgroup\$
4
  • \$\begingroup\$ Thank you! I completely forgot about ShellCheck! Been years since I've read that name. You actually made really good points and I find your version quite superior. The only thing I didn't really liked was the shortening of the question URLs. It is fine for sharing over email, IM or something that shows a preview. Otherwise, the link doesn't really say what it is for. Can you quickly say what unix.stackexchange.com/q/393397 is for? I can't. But if the only thing I have to say that's slightly negative is a comment, it means you have here a great answer. \$\endgroup\$ Oct 24, 2018 at 19:29
  • \$\begingroup\$ @IsmaelMiguel Thank you too. To be honest, the short URLs are a fragment of a general line-shortening process, as I also shortened the notify-send lines to 72 characters with \ , but then thought "that's personal preference, not necessarily style, although it would make it easier to read with less or vi on getty or similar". I reverted those changes but apparently forgot to revert the short links :| \$\endgroup\$
    – Zeta
    Oct 25, 2018 at 6:35
  • \$\begingroup\$ By the way, I also forget about ShellCheck all the time when I write my own scripts. I only remember it when I write a review, to be honest :) \$\endgroup\$
    – Zeta
    Oct 25, 2018 at 6:36
  • \$\begingroup\$ It's alright. I can apply the changes to the code without applying the short links. Also, you said to use quotes in the [ ] expressions, but you don't use it in the final version. I should start using ShellCheck more often as well. Another question, won't the command in the crontab change? \$\endgroup\$ Oct 25, 2018 at 8:46
1
\$\begingroup\$

I like the script. But I have maybe some suggestions for improvement.

This part:

RAM=$(free -m)
total=$(echo "$RAM"|awk '/^[mM]em\.?:/{print $2}')
available=$(echo "$RAM"|awk '/^[mM]em\.?:/{print $7}')

Personalty I prefer to dig these values from /proc/meminfo to avoid running unnecessary free utility, but I saw several scripts based on it. And in case that is a small script for desktop... why not :)

However. The main advantage of the output from free -m is, that all information you need contains just one line. Try to hold the advantage. Multiple lines with awk usage isn't necessary here.

Try this:

read -r _ total _ _ _ _ available <<< $(free -m | grep -i mem)

Second thing is arithmetic: as @Zeta mentioned in previous answer, there isn't a reason for using expr. But instead:

WARNING=$(($total / 5))
CRITICAL=$(($total / 10))

I would use the syntax below, due to issue which i had in bash GNU bash, 4.2.4(2)-release. (Btw. ShellCheck complains about that as well.)

warning=$(( total / 2 ))
critical=$(( total / 10 ))

The rest could be okay. But you are forcing bash to test condition about warning and critical, and when the script wasn't run by cron, you don't use it? Pity.

I would do that this way:

#!/usr/bin/env bash

# gets available and total ram
read -r _ total _ _ _ _ available <<< $(free -m | grep -i mem)

warning=$(( total / 5 ))
critical=$(( total / 10 ))

message="${available}/${total} MB free"

if (( $available < $critical )); then
  icon="error"
  title="Memory critical!"
  message+=", critical at $critical MB"
elif (( $available < $warning )); then
  icon=""
  title="Memory warning."
  message+=", warning at $warning MB"
fi

if [[ -t 0 ]]; then
  echo "$title $message"
else
  notify-send "--icon=$icon" "$title" "$message"
fi

Regards :)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.