-
Notifications
You must be signed in to change notification settings - Fork 0
/
process-slice-manager.sh
328 lines (287 loc) · 12.5 KB
/
process-slice-manager.sh
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
#!/bin/bash
# Configuration
PIDFILE="/var/run/process-slice-manager.pid"
LOG_FILE="/var/log/process-slice-manager.log"
CGROUP_V1_CPU="/sys/fs/cgroup/cpu"
CGROUP_V1_MEMORY="/sys/fs/cgroup/memory"
CGROUP_V2_BASE="/sys/fs/cgroup"
POLL_INTERVAL=2
UNLIMITED_BYTES=$((1024 * 1024 * 1024 * 1024))
CGROUP_VERSION=1
# Check cgroup version and set paths
check_cgroup_version() {
log "INFO" "Checking cgroup version"
if [ -f "/sys/fs/cgroup/cgroup.controllers" ]; then
CGROUP_VERSION=2
# Enable controllers in root group
echo "+cpu" > /sys/fs/cgroup/cgroup.subtree_control || log "ERROR" "Failed to enable cpu controller in cgroup v2"
#echo "+cpuset" > /sys/fs/cgroup/cgroup.subtree_control || log "ERROR" "Failed to enable cpuset controller in cgroup v2"
#echo "+io" > /sys/fs/cgroup/cgroup.subtree_control || log "ERROR" "Failed to enable io controller in cgroup v2"
echo "+memory" > /sys/fs/cgroup/cgroup.subtree_control || log "ERROR" "Failed to enable memory controller in cgroup v2"
local available_controllers
available_controllers=$(cat /sys/fs/cgroup/cgroup.controllers)
if [[ ! "$available_controllers" =~ "cpu" ]] || [[ ! "$available_controllers" =~ "memory" ]]; then
log "ERROR" "Required controllers (cpu, memory) not available in cgroup v2"
exit 1
fi
log "INFO" "Cgroup v2 detected and configured - Using unified hierarchy"
else
CGROUP_VERSION=1
log "INFO" "Cgroup v1 detected - Using legacy hierarchy"
fi
}
# Declare associative arrays
declare -A USER_DATA
declare -A PACKAGE_DATA
# Ensure dependencies are available
REQUIRED_CMDS=("jq" "ps")
for cmd in "${REQUIRED_CMDS[@]}"; do
if ! command -v "$cmd" &>/dev/null; then
echo "Error: Required command '$cmd' is not installed." >&2
exit 1
fi
done
# Logging function with levels
log() {
local level="$1"
local message="$2"
echo "$(date '+%Y-%m-%d %H:%M:%S') - [$level] $message" >> "$LOG_FILE"
}
# Convert memory value to bytes
convert_memory_limit() {
local limit="$1"
local value="${limit//[!0-9]/}" # Extract numeric value
local unit="${limit//[0-9]/}" # Extract unit
case "${unit^^}" in
"K") echo $((value * 1024)) ;;
"M") echo $((value * 1024 * 1024)) ;;
"G") echo $((value * 1024 * 1024 * 1024)) ;;
"T") echo $((value * 1024 * 1024 * 1024 * 1024)) ;;
"") echo "$value" ;; # If no unit, assume bytes
*) echo "Invalid unit: $unit" >&2; return 1 ;; # Error handling for invalid units
esac
}
# Setup cgroup v2 slice
setup_cgroup_v2_slice() {
local package="$1"
local cpu_quota="$2"
local cpu_period="$3"
local mem_limit="$4"
local swap_limit="$5"
# Create and setup the package directory
local package_dir="$CGROUP_V2_BASE/$package"
mkdir -p "$package_dir" || { log "ERROR" "Failed to create package directory for $package"; return 1; }
# Enable controllers in package directory
echo "+cpu" > "$package_dir/cgroup.subtree_control" || \
log "ERROR" "Failed to enable cpu controller in $package"
#echo "+cpuset" > "$package_dir/cgroup.subtree_control" || \
# log "ERROR" "Failed to enable cpuset controller in $package"
#echo "+io" > "$package_dir/cgroup.subtree_control" || \
# log "ERROR" "Failed to enable io controller in $package"
echo "+memory" > "$package_dir/cgroup.subtree_control" || \
log "ERROR" "Failed to enable memory controller in $package"
# Create and setup the tasks directory
local tasks_dir="$package_dir/tasks"
mkdir -p "$tasks_dir" || { log "ERROR" "Failed to create tasks directory for $package"; return 1; }
# Set CPU limits in the tasks directory
if [[ "$cpu_quota" != "unlimited" && "$cpu_period" != "unlimited" ]]; then
# Convert period to microseconds based on unit
local period_us
if [[ "$cpu_period" =~ ms$ ]]; then
# Convert ms to microseconds
local ms=${cpu_period//ms/}
period_us=$((ms * 1000))
elif [[ "$cpu_period" =~ s$ ]]; then
# Convert s to microseconds
local s=${cpu_period//s/}
period_us=$((s * 1000000))
else
# Default to 1s (1000000 µs) if no unit specified
period_us=1000000
fi
# maximum period allowed is 1 second (1000000 µs)
if (( period_us > 1000000 )); then
period_us=1000000
fi
# minimum period allowed is 100ms (100000 µs)
if (( period_us < 100000 )); then
period_us=100000
fi
cpu_quota=${cpu_quota//%/}
local quota_us=$((cpu_quota * period_us / 100))
log "INFO" "Set CPU quota: ${quota_us}us period: ${period_us}us for $package"
echo "$quota_us $period_us" > "$tasks_dir/cpu.max" || \
log "ERROR" "Failed to set CPU limits for $package"
else
log "INFO" "Set unlimited CPU for $package"
echo "max 100000" > "$tasks_dir/cpu.max" || \
log "ERROR" "Failed to set unlimited CPU for $package"
fi
# Set Memory limits
if [[ "$mem_limit" != "unlimited" ]]; then
local mem_bytes
mem_bytes=$(convert_memory_limit "$mem_limit")
log "INFO" "Set memory limit: $mem_bytes bytes for $package"
echo "$mem_bytes" > "$tasks_dir/memory.max" || \
log "ERROR" "Failed to set memory limit for $package"
else
log "INFO" "Set unlimited memory for $package"
echo "max" > "$tasks_dir/memory.max" || \
log "ERROR" "Failed to set unlimited memory for $package"
fi
# Set Swap limits
if [[ "$swap_limit" != "unlimited" && "$mem_limit" != "unlimited" ]]; then
local swap_bytes
swap_bytes=$(convert_memory_limit "$swap_limit")
log "INFO" "Set swap limit: $swap_bytes bytes for $package"
echo "$swap_bytes" > "$tasks_dir/memory.swap.max" || \
log "ERROR" "Failed to set swap limit for $package"
else
log "INFO" "Set unlimited swap for $package"
echo "max" > "$tasks_dir/memory.swap.max" || \
log "ERROR" "Failed to set unlimited swap for $package"
fi
}
# Setup cgroup v1 slice
setup_cgroup_v1_slice() {
local package="$1"
local cpu_quota="$2"
local cpu_period="$3"
local mem_limit="$4"
local swap_limit="$5"
# Create CPU cgroup slice
local cpu_slice_dir="$CGROUP_V1_CPU/$package"
mkdir -p "$cpu_slice_dir" || { log "ERROR" "Failed to create CPU slice for $package"; return 1; }
if [[ "$cpu_quota" != "unlimited" && "$cpu_period" != "unlimited" ]]; then
cpu_period=${cpu_period//ms/}
cpu_quota=${cpu_quota//%/}
local cfs_period_us=$((cpu_period * 1000))
local cfs_quota_us=$((cfs_period_us * cpu_quota / 100))
log "INFO" "Set CPU quota: ${cfs_quota_us}us period: ${cfs_period_us}us for $package"
echo "$cfs_period_us" > "$cpu_slice_dir/cpu.cfs_period_us" || log "ERROR" "Failed to set CPU period for $package"
echo "$cfs_quota_us" > "$cpu_slice_dir/cpu.cfs_quota_us" || log "ERROR" "Failed to set CPU quota for $package"
else
log "INFO" "Set unlimited CPU for $package"
echo "100000" > "$cpu_slice_dir/cpu.cfs_period_us" || log "ERROR" "Failed to set CPU period for $package"
echo "-1" > "$cpu_slice_dir/cpu.cfs_quota_us" || log "ERROR" "Failed to set unlimited CPU quota for $package"
fi
# Create Memory cgroup slice
local mem_slice_dir="$CGROUP_V1_MEMORY/$package"
mkdir -p "$mem_slice_dir" || { log "ERROR" "Failed to create Memory slice for $package"; return 1; }
if [[ "$mem_limit" != "unlimited" ]]; then
local mem_bytes
mem_bytes=$(convert_memory_limit "$mem_limit")
log "INFO" "Set memory limit: $mem_bytes bytes for $package"
echo "$mem_bytes" > "$mem_slice_dir/memory.limit_in_bytes" || log "ERROR" "Failed to set memory limit for $package"
else
log "INFO" "Set unlimited memory for $package"
echo "$UNLIMITED_BYTES" > "$mem_slice_dir/memory.limit_in_bytes" || log "ERROR" "Failed to set unlimited memory for $package"
fi
if [[ "$swap_limit" != "unlimited" && "$mem_limit" != "unlimited" ]]; then
local swap_bytes
swap_bytes=$(convert_memory_limit "$swap_limit")
log "INFO" "Set swap limit: $swap_bytes bytes for $package"
echo "$swap_bytes" > "$mem_slice_dir/memory.memsw.limit_in_bytes" || log "ERROR" "Failed to set swap limit for $package"
elif [[ "$mem_limit" != "unlimited" || "$swap_limit" != "unlimited" ]]; then
log "INFO" "Set unlimited swap for $package"
echo "$UNLIMITED_BYTES" > "$mem_slice_dir/memory.memsw.limit_in_bytes" || log "ERROR" "Failed to set unlimited swap for $package"
fi
}
# Setup cgroup slices
setup_cgroup_slices() {
log "INFO" "Setting up cgroup slices for packages"
for user in "${!USER_DATA[@]}"; do
local data="${USER_DATA[$user]}"
local package
package=$(echo "$data" | jq -r '.PACKAGE')
[[ -n "$package" && -z "${PACKAGE_DATA[$package]}" ]] || continue
local cpu_quota
cpu_quota=$(echo "$data" | jq -r '.CPU_QUOTA')
local cpu_period
cpu_period=$(echo "$data" | jq -r '.CPU_QUOTA_PERIOD')
local mem_limit
mem_limit=$(echo "$data" | jq -r '.MEMORY_LIMIT')
local swap_limit
swap_limit=$(echo "$data" | jq -r '.SWAP_LIMIT')
log "INFO" "Package: $package - Initial values - CPU Quota: $cpu_quota, CPU Period: $cpu_period, Memory: $mem_limit, Swap: $swap_limit"
PACKAGE_DATA["$package"]="$cpu_quota:$cpu_period:$mem_limit:$swap_limit"
if [ "$CGROUP_VERSION" -eq 2 ]; then
setup_cgroup_v2_slice "$package" "$cpu_quota" "$cpu_period" "$mem_limit" "$swap_limit"
else
setup_cgroup_v1_slice "$package" "$cpu_quota" "$cpu_period" "$mem_limit" "$swap_limit"
fi
done
log "INFO" "Cgroup slices setup completed"
}
# Load user data
load_user_data() {
log "INFO" "Loading user data"
local users_json
users_json=$(/usr/local/hestia/bin/v-list-users json)
[[ -n "$users_json" ]] || { log "ERROR" "Failed to fetch user data"; return; }
USER_DATA=()
PACKAGE_DATA=()
while read -r user; do
[[ "$user" == "admin" ]] && continue
USER_DATA["$user"]=$(echo "$users_json" | jq -c --arg user "$user" '.[$user]')
log "INFO" "Loaded data for user: $user"
done < <(echo "$users_json" | jq -r 'keys[]')
setup_cgroup_slices
}
# Monitor resources
monitor_resources() {
log "INFO" "Starting process monitoring"
declare -A known_processes
while true; do
while read -r pid user comm; do
if [[ "$user" != "root" && -z "${known_processes[$pid]}" ]]; then
local package
package=$(echo "${USER_DATA[$user]}" | jq -r '.PACKAGE')
[[ -n "$package" ]] || continue
if [ "$CGROUP_VERSION" -eq 2 ]; then
echo "$pid" > "$CGROUP_V2_BASE/$package/tasks/cgroup.procs" 2>/dev/null || \
log "ERROR" "Failed to assign PID $pid to cgroup v2"
else
echo "$pid" > "$CGROUP_V1_CPU/$package/cgroup.procs" 2>/dev/null || \
log "ERROR" "Failed to assign PID $pid to CPU cgroup"
echo "$pid" > "$CGROUP_V1_MEMORY/$package/cgroup.procs" 2>/dev/null || \
log "ERROR" "Failed to assign PID $pid to Memory cgroup"
fi
known_processes["$pid"]="$user"
log "INFO" "Assigned PID $pid ($comm) of user $user to package $package"
fi
done < <(ps -eo pid=,user=,comm= --no-headers)
for pid in "${!known_processes[@]}"; do
if ! kill -0 "$pid" 2>/dev/null; then
log "INFO" "Process $pid terminated"
unset known_processes["$pid"]
fi
done
sleep "$POLL_INTERVAL"
done
}
# Cleanup
cleanup() {
log "INFO" "Stopping service"
rm -f "$PIDFILE"
exit 0
}
# Main
case "$1" in
start)
[[ -f "$PIDFILE" ]] && { log "ERROR" "Service already running"; exit 1; }
echo $$ > "$PIDFILE"
trap cleanup SIGINT SIGTERM
trap 'load_user_data' SIGHUP
check_cgroup_version
load_user_data
monitor_resources
;;
stop)
[[ -f "$PIDFILE" ]] && { kill -TERM "$(cat "$PIDFILE")" && log "INFO" "Service stopped"; rm -f "$PIDFILE"; } || log "ERROR" "Service not running"
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
;;
esac