231 lines
6.6 KiB
Bash
Executable file
231 lines
6.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
|
|
|
require_cmd() {
|
|
command -v "$1" >/dev/null 2>&1 || {
|
|
echo "missing required command: $1" >&2
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
for cmd in curl qemu-io; do
|
|
require_cmd "${cmd}"
|
|
done
|
|
|
|
if ! command -v jq >/dev/null 2>&1 && ! command -v python3 >/dev/null 2>&1; then
|
|
echo "missing required command: jq or python3" >&2
|
|
exit 1
|
|
fi
|
|
|
|
json_get() {
|
|
local query="$1"
|
|
if command -v jq >/dev/null 2>&1; then
|
|
jq -r "${query}"
|
|
else
|
|
python3 -c 'import json,sys
|
|
data=json.load(sys.stdin)
|
|
value=data
|
|
for part in sys.argv[1].split("."):
|
|
if not part:
|
|
continue
|
|
value=value.get(part) if isinstance(value, dict) else None
|
|
if value is None:
|
|
break
|
|
print("" if value is None else value)
|
|
' "${query}"
|
|
fi
|
|
}
|
|
|
|
RUN_ID="${CORONAFS_BENCH_RUN_ID:-$$}"
|
|
LISTEN_PORT="${CORONAFS_BENCH_PORT:-$((25088 + (RUN_ID % 1000)))}"
|
|
EXPORT_BASE_PORT="${CORONAFS_BENCH_EXPORT_BASE_PORT:-$((26100 + (RUN_ID % 1000)))}"
|
|
VOLUME_ID="${CORONAFS_BENCH_VOLUME_ID:-local-bench-${RUN_ID}}"
|
|
SIZE_MIB="${CORONAFS_BENCH_SIZE_MIB:-${CORONAFS_BENCH_SIZE_MB:-512}}"
|
|
SIZE_BYTES="${CORONAFS_BENCH_SIZE_BYTES:-$((SIZE_MIB * 1024 * 1024))}"
|
|
WORKLOAD_MIB="${CORONAFS_BENCH_WORKLOAD_MIB:-${CORONAFS_BENCH_WORKLOAD_MB:-256}}"
|
|
EXPORT_CACHE_MODE="${CORONAFS_BENCH_EXPORT_CACHE_MODE:-none}"
|
|
EXPORT_AIO_MODE="${CORONAFS_BENCH_EXPORT_AIO_MODE:-threads}"
|
|
EXPORT_DISCARD_MODE="${CORONAFS_BENCH_EXPORT_DISCARD_MODE:-ignore}"
|
|
EXPORT_DETECT_ZEROES_MODE="${CORONAFS_BENCH_EXPORT_DETECT_ZEROES_MODE:-off}"
|
|
SERVER_BIN="${CORONAFS_SERVER_BIN:-}"
|
|
|
|
if (( WORKLOAD_MIB > SIZE_MIB )); then
|
|
echo "workload ${WORKLOAD_MIB} MiB exceeds volume size ${SIZE_MIB} MiB" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "${SERVER_BIN}" ]]; then
|
|
SERVER_CMD=(
|
|
cargo run
|
|
--manifest-path "${REPO_ROOT}/coronafs/Cargo.toml"
|
|
-p coronafs-server
|
|
--
|
|
)
|
|
else
|
|
SERVER_CMD=("${SERVER_BIN}")
|
|
fi
|
|
|
|
TMP_DIR="$(mktemp -d)"
|
|
CONFIG_PATH="${TMP_DIR}/coronafs.toml"
|
|
SERVER_LOG="${TMP_DIR}/coronafs.log"
|
|
SERVER_PID=""
|
|
|
|
show_server_log() {
|
|
if [[ -f "${SERVER_LOG}" ]]; then
|
|
echo "--- coronafs server log ---" >&2
|
|
tail -n 200 "${SERVER_LOG}" >&2 || true
|
|
echo "--- end coronafs server log ---" >&2
|
|
fi
|
|
}
|
|
|
|
delete_volume_if_present() {
|
|
curl -fsS -X DELETE "http://127.0.0.1:${LISTEN_PORT}/v1/volumes/${VOLUME_ID}" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
cleanup() {
|
|
delete_volume_if_present
|
|
local pid_file="${TMP_DIR}/data/pids/${VOLUME_ID}.pid"
|
|
if [[ -f "${pid_file}" ]]; then
|
|
local export_pid=""
|
|
export_pid="$(tr -d '\n' <"${pid_file}" 2>/dev/null || true)"
|
|
if [[ -n "${export_pid}" ]] && kill -0 "${export_pid}" 2>/dev/null; then
|
|
kill "${export_pid}" >/dev/null 2>&1 || true
|
|
wait "${export_pid}" >/dev/null 2>&1 || true
|
|
fi
|
|
rm -f "${pid_file}"
|
|
fi
|
|
if [[ -n "${SERVER_PID}" ]] && kill -0 "${SERVER_PID}" 2>/dev/null; then
|
|
kill "${SERVER_PID}" >/dev/null 2>&1 || true
|
|
wait "${SERVER_PID}" >/dev/null 2>&1 || true
|
|
fi
|
|
rm -rf "${TMP_DIR}"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
cat >"${CONFIG_PATH}" <<EOF
|
|
listen_addr = "127.0.0.1:${LISTEN_PORT}"
|
|
advertise_host = "127.0.0.1"
|
|
data_dir = "${TMP_DIR}/data"
|
|
export_bind_addr = "127.0.0.1"
|
|
export_base_port = ${EXPORT_BASE_PORT}
|
|
export_port_count = 8
|
|
export_shared_clients = 32
|
|
export_cache_mode = "${EXPORT_CACHE_MODE}"
|
|
export_aio_mode = "${EXPORT_AIO_MODE}"
|
|
export_discard_mode = "${EXPORT_DISCARD_MODE}"
|
|
export_detect_zeroes_mode = "${EXPORT_DETECT_ZEROES_MODE}"
|
|
preallocate = false
|
|
sync_on_write = false
|
|
log_level = "info"
|
|
EOF
|
|
|
|
"${SERVER_CMD[@]}" --config "${CONFIG_PATH}" >"${SERVER_LOG}" 2>&1 &
|
|
SERVER_PID="$!"
|
|
|
|
deadline=$((SECONDS + 60))
|
|
until curl -fsS "http://127.0.0.1:${LISTEN_PORT}/healthz" >/dev/null 2>&1; do
|
|
if (( SECONDS >= deadline )); then
|
|
echo "timed out waiting for coronafs local bench server" >&2
|
|
tail -n 200 "${SERVER_LOG}" >&2 || true
|
|
exit 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
create_response_file="${TMP_DIR}/create-response.txt"
|
|
create_status="$(
|
|
curl -sS \
|
|
-o "${create_response_file}" \
|
|
-w '%{http_code}' \
|
|
-X PUT \
|
|
-H 'content-type: application/json' \
|
|
-d "{\"size_bytes\":${SIZE_BYTES}}" \
|
|
"http://127.0.0.1:${LISTEN_PORT}/v1/volumes/${VOLUME_ID}"
|
|
)"
|
|
if [[ "${create_status}" -lt 200 || "${create_status}" -ge 300 ]]; then
|
|
echo "failed to create CoronaFS benchmark volume: HTTP ${create_status}" >&2
|
|
cat "${create_response_file}" >&2 || true
|
|
show_server_log
|
|
exit 1
|
|
fi
|
|
|
|
export_response_file="${TMP_DIR}/export-response.txt"
|
|
export_status="$(
|
|
curl -sS \
|
|
-o "${export_response_file}" \
|
|
-w '%{http_code}' \
|
|
-X POST \
|
|
"http://127.0.0.1:${LISTEN_PORT}/v1/volumes/${VOLUME_ID}/export"
|
|
)"
|
|
if [[ "${export_status}" -lt 200 || "${export_status}" -ge 300 ]]; then
|
|
echo "failed to export CoronaFS benchmark volume: HTTP ${export_status}" >&2
|
|
cat "${export_response_file}" >&2 || true
|
|
show_server_log
|
|
exit 1
|
|
fi
|
|
EXPORT_JSON="$(cat "${export_response_file}")"
|
|
EXPORT_URI="$(printf '%s' "${EXPORT_JSON}" | json_get '.export.uri')"
|
|
[[ -n "${EXPORT_URI}" && "${EXPORT_URI}" != "null" ]] || {
|
|
echo "failed to obtain CoronaFS export URI" >&2
|
|
printf '%s\n' "${EXPORT_JSON}" >&2
|
|
show_server_log
|
|
exit 1
|
|
}
|
|
|
|
run_qemu_io() {
|
|
local extra=()
|
|
local start_ns end_ns elapsed_ns
|
|
local args=("$@")
|
|
local cmd=()
|
|
local qemu_cmd=""
|
|
|
|
if [[ "${#args[@]}" -eq 0 ]]; then
|
|
echo "run_qemu_io requires at least one qemu-io command" >&2
|
|
exit 1
|
|
fi
|
|
|
|
while [[ "${#args[@]}" -gt 0 && "${args[0]}" == --* ]]; do
|
|
extra+=("${args[0]}")
|
|
args=("${args[@]:1}")
|
|
done
|
|
|
|
cmd=(qemu-io -f raw "${extra[@]}")
|
|
for qemu_cmd in "${args[@]}"; do
|
|
cmd+=(-c "${qemu_cmd}")
|
|
done
|
|
cmd+=("${EXPORT_URI}")
|
|
|
|
start_ns="$(date +%s%N)"
|
|
"${cmd[@]}" >/dev/null
|
|
end_ns="$(date +%s%N)"
|
|
elapsed_ns="$((end_ns - start_ns))"
|
|
printf '%s\n' "${elapsed_ns}"
|
|
}
|
|
|
|
calc_mib_per_s() {
|
|
local bytes="$1"
|
|
local elapsed_ns="$2"
|
|
awk -v bytes="${bytes}" -v elapsed_ns="${elapsed_ns}" '
|
|
BEGIN {
|
|
if (elapsed_ns <= 0) {
|
|
print "0.00"
|
|
} else {
|
|
printf "%.2f", (bytes / 1048576.0) / (elapsed_ns / 1000000000.0)
|
|
}
|
|
}
|
|
'
|
|
}
|
|
|
|
BYTES="$((WORKLOAD_MIB * 1024 * 1024))"
|
|
WRITE_NS="$(run_qemu_io "write -P 0x5a 0 ${WORKLOAD_MIB}M" "flush")"
|
|
READ_NS="$(run_qemu_io "read -P 0x5a 0 ${WORKLOAD_MIB}M")"
|
|
WRITE_MIBPS="$(calc_mib_per_s "${BYTES}" "${WRITE_NS}")"
|
|
READ_MIBPS="$(calc_mib_per_s "${BYTES}" "${READ_NS}")"
|
|
|
|
printf 'CoronaFS local export bench: uri=%s cache=%s aio=%s write=%s MiB/s read=%s MiB/s size=%s MiB\n' \
|
|
"${EXPORT_URI}" "${EXPORT_CACHE_MODE}" "${EXPORT_AIO_MODE}" "${WRITE_MIBPS}" "${READ_MIBPS}" "${WORKLOAD_MIB}"
|
|
|
|
printf '%s\t%s\t%s\t%s\t%s\n' "${EXPORT_URI}" "${EXPORT_CACHE_MODE}" "${EXPORT_AIO_MODE}" "${WRITE_MIBPS}" "${READ_MIBPS}"
|