aboutsummaryrefslogtreecommitdiff
path: root/maint/get-max-parallelism
blob: c5a9a8ac3d1c2ddbf47021b0da89c3d464e7587e (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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/usr/bin/env sh

# 'local' isn't POSIX-compliant, but in practice is available in
# implementations of sh that we care about.
# shellcheck disable=SC3043

set -eu


SCRIPT_NAME=$(basename "$0")

usage ()
{
    cat <<EOF
${SCRIPT_NAME}: Find parallelism limit based on memory and CPUs available.
Requires some upper bound (i.e. reasonable guesstimate) of how much RAM each job needs.
Takes into account the physical memory available on the machine, and cgroup limits.
Reports but doesn't take into account how much memory appears to be currently
available.

The result is printed as a single integer to stdout. Additional information is
printed to stderr.

Usage:
  $SCRIPT_NAME MAX_MB_RAM_per_job : Calculate max parallelism for given max-MB-RAM-per-job

Options:
  -h: Print this message.
EOF
}

# echo to stderr
eecho () {
    >&2 echo "$*"
}

min () {
    local lhs="$1"
    local rhs="$2"
    echo $(( lhs < rhs ? lhs : rhs ))
}

max () {
    local lhs="$1"
    local rhs="$2"
    echo $(( lhs > rhs ? lhs : rhs ))
}

is_numeric () {
    case "$1" in
    ''|*[!0-9]*) return 1;;
    *) return 0;;
    esac
}

while getopts "h" opt ; do
    case "$opt" in
	h) usage
	   exit 0
	   ;;
	*) echo "Unknown option."
	   exit 1
	   ;;
    esac
done

# Remove the flags we parsed.
shift $((OPTIND-1))

if [ $# -ne 1 ] || ! is_numeric "$1";
then
    usage
    exit 1
fi
max_mb_per_job="$1"

# Follow the cgroup hierarchy and return the effective memory limit,
# which is the minimum of the limits at each level of the hierarchy.
get_cgroup_memmax_MB () {
    local cgroup_root
    cgroup_root=/sys/fs/cgroup
    local cgroup
    cgroup=$(cut -d : -f3 < /proc/self/cgroup)
    local next_cgroup_dir
    next_cgroup_dir="$cgroup_root$cgroup"
    local res="unlimited"
    while true
    do
        case "$next_cgroup_dir" in
        "$cgroup_root"*) : ;;
        *) break;;
        esac

	local cgroup_dir="$next_cgroup_dir"
	next_cgroup_dir=$(dirname "$next_cgroup_dir")
	local f="${cgroup_dir}memory.max"
	if [ ! -f "$f" ]
        then
            continue
	fi
	local this_max
	this_max=$(cat "$f")
	if ! is_numeric "$this_max"
        then
            continue
	fi
	eecho "found cgroup limit $cgroup_dir: $this_max"
	if ! is_numeric "$res"
        then
            res=$(( this_max / 1024 / 1024 ))
	fi
	res=$(min "$this_max" "$res")
    done
    echo "$res"
}

get_meminfo_available_MB () {
    local x
    x="$(grep ^MemAvailable /proc/meminfo | awk '{ print $2; }')"
    echo $(( x / 1024 ))
}

get_meminfo_total_MB () {
    local x
    x="$(grep ^MemTotal /proc/meminfo | awk '{ print $2; }')"
    echo $(( x / 1024 ))
}

get_max_mem_MB () {
    # Report available, but for now we don't use it since it may give
    # inconsistent results. e.g. we don't want to severely limit parallelism if
    # something happens to be briefly using a lot of memory at the moment.
    local meminfo_available
    meminfo_available=$(get_meminfo_available_MB)
    eecho "MemAvailable: $meminfo_available MB"

    local meminfo_total
    meminfo_total=$(get_meminfo_total_MB)
    eecho "MemTotal: $meminfo_total MB"

    local cgroup_memmax
    cgroup_memmax=$(get_cgroup_memmax_MB)
    eecho "cgroup limit: $cgroup_memmax MB"

    if ! is_numeric "$cgroup_memmax"
    then
        # No cgroup limit; fall back to total
        echo "$meminfo_total"
    else
        # Report smaller of total or cgroup limit
        min "${meminfo_total}" "${cgroup_memmax}"
    fi
}

get_parallelism_limit() {
    local mb_per_job="$1"
    local max_mem_MB
    max_mem_MB=$(get_max_mem_MB)
    local mem_based_limit=$(( max_mem_MB / mb_per_job ))
    eecho "memory based parallelism limit: $mem_based_limit"
    local cpu_based_limit
    cpu_based_limit=$(nproc)
    eecho "cpu based parallelism limit: $cpu_based_limit"
    max 1 "$(min "$mem_based_limit" "$cpu_based_limit")"
}

get_parallelism_limit "$max_mb_per_job"