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 # Set it to run every x minutes (set to 5 below) as a cron job (System >> Scheduled Tasks): # */5 * * * * /bin/sh -c /etc/memcompact.sh # 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 LOGGING=0 # ===================================================================== # 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 [ "${LOGGING}" -eq 1 ] && logger -t kernel_mem "Compacting memory. 4MB blocks: ${BLOCKS_4MB} | Threshold: ${THRESHOLD}." # Flush caches (aids memory compaction) echo 3 | sudo tee /proc/sys/vm/drop_caches # 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 [ "${LOGGING}" -eq 1 ] && 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 every 5 minutes (or whatever schedule you want):
# ┌───────────── minute (0–59); */5 every 5 minutes; */10 every 10 minutes # │ ┌───────────── 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 # */5 * * * * /bin/sh -c /etc/memcompact.sh
Why 5 minutes? Start up tcpdump with a command like this:
tcpdump -i eth0 -W 400 -C 256 -w /mnt/sda2/capture.pcap
… and watch it chew through your router memory. I had to do this recently to capture the thousands of dropped packets per minute on Comcast due to a bad coaxial cable outside the house. 20 hours of packet capture, and the first 256 MB file (spanning ~7.5 minutes of packet capture) showed:
TCP Retransmission: 5610 instances TCP Previous segment not captured: 540 instances TCP Dup ACK: 28208 instances
Anyway, if you’ve got logging enabled, you’ll see entries like:
user.notice: kernel_mem: Skipping memory compaction. 4MB blocks: 129 | Threshold: 49.
… otherwise you’ll only see a log entry when memory is actually compacted.
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.