summaryrefslogtreecommitdiff
path: root/init/extra_free_kbytes.sh
blob: aeaa9125864e8190e31d5a0a50717bcbf5fd22b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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 value>
#
# 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 <extra_free_kbytes value>"
    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