Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Inject small delay to improve requests distribution
Ruby MRI when used can at most process a single thread concurrently due to GVL. This results in a over-utilisation if unfavourable distribution of connections is happening. This tries to prefer less-busy workers (ie. faster to accept the connection) to improve workers utilisation.
- Loading branch information
Showing
8 changed files
with
189 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
#!/bin/bash | ||
|
||
set -eo pipefail | ||
|
||
CPU_TIME=50 | ||
HOST=127.0.0.1:9292 | ||
URL="http://$HOST/cpu/$CPU_TIME" | ||
|
||
MIN_WORKERS=1 | ||
MAX_WORKERS=4 | ||
|
||
MIN_THREADS=4 | ||
MAX_THREADS=4 | ||
|
||
REQUESTS_PER_TEST=300 | ||
MIN_CONCURRENT=1 | ||
MAX_CONCURRENT=8 | ||
|
||
retry() { | ||
local tries="$1" | ||
local sleep="$2" | ||
shift 2 | ||
|
||
for i in $(seq 1 $tries); do | ||
if eval "$@"; then | ||
return 0 | ||
fi | ||
|
||
sleep "$sleep" | ||
done | ||
|
||
return 1 | ||
} | ||
|
||
run_ab() { | ||
result=$(ab -q -n "$requests" -c "$concurrent" "$@") | ||
time_taken=$(echo "$result" | grep "Time taken for tests:" | cut -d" " -f7) | ||
time_per_req=$(echo "$result" | grep "Time per request:" | grep "(mean)" | cut -d" " -f10) | ||
|
||
echo -e "$workers\t$threads\t$requests\t$concurrent\t$time_taken\t$time_per_req" | ||
} | ||
|
||
run_concurrency_tests() { | ||
echo | ||
echo -e "PUMA_W\tPUMA_T\tAB_R\tAB_C\tT_TOTAL\tT_PER_REQ" | ||
for concurrent in $(seq $MIN_CONCURRENT $MAX_CONCURRENT); do | ||
requests="$((concurrent*$REQUESTS_PER_TEST))" | ||
eval "$@" | ||
sleep 1 | ||
done | ||
echo | ||
} | ||
|
||
with_puma() { | ||
# start puma and wait for 10s for it to start | ||
bundle exec bin/puma -w "$workers" -t "$threads" -b "tcp://$HOST" -C test/config/cpu_spin.rb & | ||
local puma_pid=$! | ||
trap "kill $puma_pid" EXIT | ||
|
||
# wait for Puma to be up | ||
if ! retry 10 1s curl --fail "$URL" &>/dev/null; then | ||
echo "Failed to connect to $URL." | ||
return 1 | ||
fi | ||
|
||
# execute testing command | ||
eval "$@" | ||
kill "$puma_pid" || true | ||
trap - EXIT | ||
wait | ||
} | ||
|
||
for workers in $(seq $MIN_WORKERS $MAX_WORKERS); do | ||
for threads in $(seq $MIN_THREADS $MAX_THREADS); do | ||
with_puma \ | ||
run_concurrency_tests \ | ||
run_ab "$URL" | ||
done | ||
done |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# call with "GET /cpu/<d> HTTP/1.1\r\n\r\n", where <d> is the number of | ||
# milliseconds to spin CPU, returns process pid | ||
|
||
# configure `wait_for_less_busy_workers` based on ENV, default `true` | ||
wait_for_less_busy_worker ENV.fetch('WAIT_FOR_LESS_BUSY_WORKERS', 'true') == 'true' | ||
|
||
def cpu_threadtime | ||
# Not all OS kernels are supporting `Process::CLOCK_THREAD_CPUTIME_ID` | ||
# Refer: https://gitlab.com/gitlab-org/gitlab/issues/30567#note_221765627 | ||
return unless defined?(Process::CLOCK_THREAD_CPUTIME_ID) | ||
|
||
Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_second) | ||
end | ||
|
||
def rand_for(duration_s) | ||
end_time = cpu_threadtime + duration_s | ||
rand while cpu_threadtime < end_time | ||
end | ||
|
||
app do |env| | ||
duration_ms = (env['REQUEST_PATH'][/\/cpu\/(\d.*)/,1] || '1000').to_f | ||
|
||
# This simulates an interleaved workload | ||
# When thread is 50% free, and 50% busy with Ruby | ||
# | ||
# Another request might be picked during the `sleep` | ||
# But, then it would compete with another request during | ||
# `rand_for` execution | ||
|
||
start_time = cpu_threadtime | ||
expected_end_time = cpu_threadtime + duration_ms / 1000.0 | ||
while cpu_threadtime < expected_end_time do | ||
sleep(0.01) | ||
rand_for(0.01) | ||
end | ||
end_time = cpu_threadtime - start_time | ||
|
||
[200, {"Content-Type" => "text/plain"}, ["Run for #{end_time} #{Process.pid}"]] | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters