From 642048d96994c16259b65708f5033a09c9be4f51 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Tue, 15 Jun 2021 12:28:24 -0700 Subject: [PATCH] init: Add extra_free_kbytes.sh script to adjust watermark_scale_factor Android uses an out-of-tree /proc/sys/vm/extra_free_kbytes kernel knob to increase low watermark. A similar /proc/sys/vm/watermark_scale_factor knob supported in upstream kernel can be used for the same purpose. The script implements conversion from desired extra_free_kbytes value to watermark_scale_factor value and sets watermark_scale_factor according to the value for extra_free_kbytes provided as a parameter. Note that watermark_scale_factor affects high watermark differently from extra_free_kbytes. While extra_free_kbytes increases low and high watermarks by the same amount, watermark_scale_factor affects high watermark 2x times. Therefore it is expected that replacing the usage extra_free_kbytes with watermark_scale_factor will keep the same low watermark but high watermark will be higher than before. Usage: extra_free_kbytes.sh Bug: 109664768 Signed-off-by: Suren Baghdasaryan Change-Id: Id8949be731da77be884cdeec3a765ea291dbc2f0 --- init/Android.bp | 6 ++ init/extra_free_kbytes.sh | 136 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100755 init/extra_free_kbytes.sh diff --git a/init/Android.bp b/init/Android.bp index a04d2db0e..5d0968717 100644 --- a/init/Android.bp +++ b/init/Android.bp @@ -236,6 +236,7 @@ cc_binary { "init.rc", "ueventd.rc", "e2fsdroid", + "extra_free_kbytes.sh", "make_f2fs", "mke2fs", "sload_f2fs", @@ -552,3 +553,8 @@ cc_binary { }, }, } + +sh_binary { + name: "extra_free_kbytes.sh", + src: "extra_free_kbytes.sh", +} diff --git a/init/extra_free_kbytes.sh b/init/extra_free_kbytes.sh new file mode 100755 index 000000000..aeaa91258 --- /dev/null +++ b/init/extra_free_kbytes.sh @@ -0,0 +1,136 @@ +#!/bin/sh + +# Script implements watermark_scale calculation which results in the same low +# watermark as if extra_free_kbytes tunable were to be used. +# +# Usage: extra_free_kbytes.sh +# +# extra_free_kbytes is distributed between zones based on +# zone.managed_pages/vm_total_pages ratio, where vm_total_pages is the sum of +# zone.managed_pages for all zones (zone.high used in this calculation is 0 +# when this is calculated). Therefore for each zone its share is calculated as: +# +# extra_free_pages = extra_free_kbytes / page_size +# extra_share = extra_free_pages * managed_pages / vm_total_pages +# +# This extra_share is added to the low and high watermarks: +# +# low = min + max(min / 4, managed_pages * (watermark_scale / 10000)) + extra_share +# high = min + 2 * max(min / 4, managed_pages * (watermark_scale / 10000)) + extra_share +# +# Because Android uses extra_free_kbytes to adjust the low watermark, we ignore +# the difference in how watermark_scale and extra_free_kbytes affect the high +# watermark and will match the low watermark only. +# +# To eliminate extra_share and compansate the difference with watermark_scale, +# a new watermark_scale_new is calculated as: +# +# (1) max(min / 4, managed_pages * (watermark_scale / 10000)) + extra_share = +# max(min / 4, managed_pages * (watermark_scale_new / 10000)) +# +# Two cases to consider: +# A. managed_pages * (watermark_scale / 10000) > min / 4 +# The formula (1) becomes: +# +# managed_pages * (watermark_scale / 10000) + extra_share = +# managed_pages * (watermark_scale_new / 10000) +# +# after simplifying and substituting extra_share formula becomes: +# +# (2) watermark_scale_new = watermark_scale + extra_free_pages / vm_total_pages * 10000 +# +# B. managed_pages * (watermark_scale / 10000) < min / 4 +# The formula (1) becomes: +# +# min / 4 + extra_share = max(min / 4, managed_pages * (watermark_scale_new / 10000)) +# +# after calculating watermark_scale_new, if (managed_pages * (watermark_scale_new / 10000)) +# is still smaller than min / 4 then we can't compensate extra_share with +# watermark_scale anyway. Therefore calculation becomes: +# +# watermark_scale_new = (min / 4 + extra_share) / managed_pages * 10000 +# +# after simplifying and substituting extra_share formula becomes: +# +# (3) watermark_scale_new = (min / 4) * 10000 / managed_pages + extra_free_pages / vm_total_pages * 10000 +# +# After defining watermark_delta = extra_free_pages / vm_total_pages * 10000: +# +# if (managed_pages * (watermark_scale / 10000) > min / 4) +# watermark_scale_new = watermark_scale + watermark_delta +# else +# watermark_scale_new = (min / 4) * 10000 / managed_pages + watermark_delta +# + +if [ "$#" -ne 1 ] +then + echo "Usage: $0 " + exit +fi + +extra_free_kbytes=$1 + +# if extra_free_kbytes knob exists, use it and exit +if [ -e /proc/sys/vm/extra_free_kbytes ] +then + echo $extra_free_kbytes > /proc/sys/vm/extra_free_kbytes + exit +fi + +watermark_scale=`cat /proc/sys/vm/watermark_scale_factor` + +# convert extra_free_kbytes to pages +page_size=$(getconf PAGESIZE) +page_size_kb=$((page_size/1024)) +extra_free_pg=$((extra_free_kbytes/page_size_kb)) + +managed=($(grep managed /proc/zoneinfo | awk '{print $2}')) +length=${#managed[@]} +min=($(grep "min" /proc/zoneinfo | awk '{print $2}')) + +# calculate vm_total_pages. +# WARNING: if the final low watermark differs from the original, the source of +# the error is likely vm_total_pages which is impossible to get exact from the +# userspace. Grep for "Total pages" in the kernel logs to see the actual +# vm_total_pages and plug it in the calculation to confirm the source of the +# error. Error caused by this inaccuracy is normally within 1% range. +vm_total_pages=0 +i=0 +while [ $i -lt $length ] +do + vm_total_pages=$((vm_total_pages + managed[i])) + i=$((i+1)) +done + +# calculate watermark_scale_new for each zone and choose the max +max_watermark_scale=0 +i=0 +while [ $i -lt $length ] +do + # skip unmanaged zones + if [ ${managed[i]} -eq 0 ] + then + i=$((i+1)) + continue + fi + + base_margin=$((min[i] / 4)) + calc_margin=$(echo "${managed[i]} * $watermark_scale / 10000" | bc) + # round the value by adding 0.5 and truncating the decimal part + watermark_delta=$(echo "x=($extra_free_pg / ($vm_total_pages / 10000) + 0.5); scale = 0; x/1" | bc -l) + if [ $calc_margin -gt $base_margin ] + then + watermark_scale_new=$(echo "$watermark_scale + $watermark_delta" | bc) + else + watermark_scale_new=$(echo "$base_margin / (${managed[i]} / 10000) + $watermark_delta" | bc) + fi + + if [ $max_watermark_scale -lt $watermark_scale_new ] + then + max_watermark_scale=$watermark_scale_new + fi + + i=$((i+1)) +done + +echo $max_watermark_scale > /proc/sys/vm/watermark_scale_factor