Memory compaction

If you run:

cat /proc/buddyinfo

… you’ll see output like this:

Node 0, zone      DMA    649   2370   2207   1116    428    147     81     40     18     14    129

… that last value is our target… it’s known as an ‘order-10 block’. 2^10 * 4 Kb = 4 MB. Each of those 129 blocks above is 4 MB in size.

The output above is the summation of each column of this command:

cat /proc/pagetypeinfo

Linux handles the other values automatically. If a process requests memory and there’s none to be had from the smaller (lower-order) blocks to the left, it’ll split up one of those order-10 4 MB blocks to handle the request. Thus, over time, the number of order-10 blocks dwindles. When it reaches a low point, Linux compacts memory, restoring contiguous 4 MB blocks available to be split up and used again. The problem is, that low point is usually when the router is under heavy load… the most inopportune time to be doing memory compaction.

We’re going to set it up such that the router does memory compaction before that heavy load hits and exhausts the number of order-10 blocks.

In a terminal window connected via ssh to your router (ssh [email protected], input password when prompted), copy-and-paste the entirety of the code below, then hit Enter:

cat << 'EOF' > /etc/memcompact.sh
#!/bin/sh

# =====================================================================
# USER CONFIGURATION
# Run: cat /proc/buddyinfo
# You'll see something like this:
# Node 0, zone      DMA    649   2370   2207   1116    428    147     81     40     18     14    129
# Note the last value.

# Run: awk '/MemTotal:/ {print $2}' /proc/meminfo
# You'll see something like this:
# 1010484

# This router, with 1010484 / 1024 = 986.8 MB of RAM and 129 blocks * 4MB = 516 MB contiguous free memory, is sitting at ~52.29% free contiguous memory.

# Set the target percentage of total RAM that should remain unfragmented.
# (e.g., TARGET_PERCENT=20 means "trigger compaction if 4MB blocks drop below 20% of total RAM")
# In this case, 1010484 / 1024 * 0.20 = 197.36 MB and 49 4MB contiguous free blocks

# This allows the router to compact memory before it is forced to do so (at the worst possible time, under heavy load).
# =====================================================================
TARGET_PERCENT=20

# Fetch total memory in Kilobytes from the kernel
MEM_TOTAL_KB=$(awk '/MemTotal:/ {print $2}' /proc/meminfo)

# Calculate the number of 4MB blocks which equals the target percentage.
THRESHOLD=$(( (MEM_TOTAL_KB * TARGET_PERCENT / 100) / 4096 ))

# Ensure the threshold is at least 2 4MB blocks on memory-constrained hardware
[ "$THRESHOLD" -lt 2 ] && THRESHOLD=2

# Fetch the count of 4MB blocks
BLOCKS_4MB=$(awk '{print $NF}' /proc/buddyinfo | awk '{s+=$1} END {print s}')

# Conditional compaction logic
if [ "$BLOCKS_4MB" -lt "$THRESHOLD" ]; then
    logger -t kernel_mem "Compacting memory. 4MB blocks: $BLOCKS_4MB | Threshold: $THRESHOLD."

    # Sync to commit dirty pages to storage (aids memory compaction)
    sync && sleep 5 && sync && sleep 5

    # Execute memory compaction
    sysctl -w vm.compact_memory=1 && sleep 5
    BLOCKS_4MB=$(awk '{print $NF}' /proc/buddyinfo | awk '{s+=$1} END {print s}')
    logger -t kernel_mem "Memory compacted. 4MB blocks: $BLOCKS_4MB | Threshold: $THRESHOLD."
else
    logger -t kernel_mem "Skipping memory compaction. 4MB blocks: $BLOCKS_4MB | Threshold: $THRESHOLD."
fi
EOF

Set the TARGET_PERCENT to suit your system. For memory-constrained system, it’ll automatically default to a minimum of two 4MB free, contiguous memory blocks.

Make that file executable:

chmod +x /etc/memcompact.sh

Set up a cron job (System >> Scheduled Tasks) to run memory compaction once a day (or whatever schedule you want):

# ┌───────────── minute (0–59)
# │ ┌───────────── hour (0–23)
# │ │ ┌───────────── day of month (1–31)
# │ │ │ ┌───────────── month (1–12 or JAN–DEC)
# │ │ │ │ ┌───────────── day of week (0–7 or SUN–SAT, 0 and 7 = Sunday)
# │ │ │ │ │
# * * * * *

# Memory compaction
* 6 * * * /bin/sh -c /etc/memcompact.sh

In your logs, you’ll see entries like:

user.notice: kernel_mem: Skipping memory compaction. 4MB blocks: 129 | Threshold: 49.

Make it impervious to firmware updates:

Put /etc/memcompact.sh in the list at System >> Backup / Flash Firmware >> Configuration tab, then click the Save button.