655 lines
21 KiB
Bash
Executable File
655 lines
21 KiB
Bash
Executable File
#!/bin/bash
|
||
# Examples:
|
||
# ./exp-35.sh
|
||
# UE_MIN=2 UE_MAX=12 UE_PEAK_HOUR=14 UE_NOISE_FRAC=0.15 ./exp-35.sh
|
||
# TEST_UE=oai-nr-ue1 TEST_PROFILE=browsing TEST_DURATION_SEC=120 ./exp-35.sh
|
||
|
||
set -euo pipefail
|
||
|
||
|
||
# ---------------------------[ Random Burst Mode ]----------------------------
|
||
# UE scaler mode: 'random' (rapid random targets) or 'diurnal' (legacy cosine)
|
||
UE_SCALER_MODE="${UE_SCALER_MODE:-random}"
|
||
# Interval (sec) for scaler ticks
|
||
UE_SCALER_INTERVAL_SEC="${UE_SCALER_INTERVAL_SEC:-30}"
|
||
|
||
# Rapid/random system-level bursts: every few minutes trigger a short, global burst
|
||
# that nudges all UEs to start/extend a session at once. UE-side script watches
|
||
# /tmp/system_burst and reacts (see traffic_runner --profile random).
|
||
RANDOM_BURST_MODE="${RANDOM_BURST_MODE:-1}" # 1=enabled, 0=disabled
|
||
BURST_MIN_GAP_SEC="${BURST_MIN_GAP_SEC:-180}" # 3 minutes
|
||
BURST_MAX_GAP_SEC="${BURST_MAX_GAP_SEC:-600}" # 10 minutes
|
||
BURST_MARK_FILE="${BURST_MARK_FILE:-/tmp/system_burst}"
|
||
# -------------------------------[ Config ]------------------------------------
|
||
|
||
ARTIFACT_DIR="/home/nakaolab-go/OAI/research-main/artifacts/"
|
||
mkdir -p "$ARTIFACT_DIR"
|
||
|
||
POWER_LOG_FILE="${POWER_LOG_FILE:-$ARTIFACT_DIR/power_usage.csv}"
|
||
CPU_PROXY_LOG_FILE="${CPU_PROXY_LOG_FILE:-$ARTIFACT_DIR/cpu_proxy.csv}"
|
||
RRC_SINCE_STATE="${RRC_SINCE_STATE:-$ARTIFACT_DIR/.rrc_since}"
|
||
|
||
# xApp (mac stats) container + path inside container
|
||
MAC_STATS_CONTAINER="${MAC_STATS_CONTAINER:-xapp-chatgpt}"
|
||
MAC_STATS_FILE_IN_CONTAINER="${MAC_STATS_FILE_IN_CONTAINER:-/tmp/mac_stats.log}"
|
||
|
||
# Intervals (seconds)
|
||
RRC_LOG_INTERVAL_SEC="${RRC_LOG_INTERVAL_SEC:-10}"
|
||
POWER_LOG_INTERVAL_SEC="${POWER_LOG_INTERVAL_SEC:-5}"
|
||
CPU_PROXY_INTERVAL_SEC="${CPU_PROXY_INTERVAL_SEC:-5}"
|
||
UE_SCALER_INTERVAL_SEC="${UE_SCALER_INTERVAL_SEC:-60}"
|
||
|
||
# Docker compose helper
|
||
DC_BIN=(docker compose)
|
||
dc() { command "${DC_BIN[@]}" "$@"; }
|
||
dc down -v
|
||
|
||
# Service names (override if your compose uses different ones)
|
||
AMF_SERVICE="${AMF_SERVICE:-oai-amf}"
|
||
UPF_SERVICE="${UPF_SERVICE:-oai-upf}"
|
||
GNB_SERVICE="${GNB_SERVICE:-oai-gnb}"
|
||
XAPP_SERVICE="${XAPP_SERVICE:-xapp-chatgpt}"
|
||
|
||
# UE service name pattern (for discovery)
|
||
UE_NAME_REGEX="${UE_NAME_REGEX:-^oai-nr-ue}"
|
||
|
||
# Test controls
|
||
TEST_UE="${TEST_UE:-}" # e.g. oai-nr-ue1
|
||
TEST_PROFILES="${TEST_PROFILES:-}" # profile name
|
||
TEST_DURATION_SEC="${TEST_DURATION_SEC:-0}" # 0 = run indefinitely
|
||
|
||
# Diurnal scaling controls (normal mode only; ignored if TEST_UE is set)
|
||
UE_MIN="${UE_MIN:-1}" # Minimum number of UEs to keep connected
|
||
# UE_MAX="${UE_MAX:-}" # Max UEs; default = total UEs in compose
|
||
UE_MAX=10
|
||
UE_PEAK_HOUR="${UE_PEAK_HOUR:-17}" # Local hour with peak load (0..23)
|
||
UE_NOISE_FRAC="${UE_NOISE_FRAC:-0.15}" # ±fractional noise applied to target
|
||
|
||
# -------------------------[ Power Model (mW) ]--------------------------------
|
||
|
||
declare -A PWR_MW_PROFILE=(
|
||
[idle]=60 # screen on, modem camped, no transfers
|
||
[iot_chatty]=150 # tiny bursts, long sleeps
|
||
[browsing]=380 # short transfers with think-time
|
||
[voip]=520 # steady uplink/downlink small packets
|
||
[video]=850 # continuous DL; 720–1080p
|
||
[gaming]=950 # bursty DL + UL keepalives
|
||
[bulk_download]=1100 # sustained UL/DL at MCS ceiling
|
||
)
|
||
|
||
# -------------------------------[ Utils ]-------------------------------------
|
||
|
||
ts_now() { date +%s; }
|
||
ts_iso() { date -Is; }
|
||
ts_tag() { date +%Y%m%d_%H%M%S; }
|
||
log() { echo "[$(ts_iso)] $*"; }
|
||
|
||
exec_safe() {
|
||
local svc="$1"; shift
|
||
dc exec -T "$svc" bash -lc "$*"
|
||
}
|
||
|
||
# -----------------------------[ Discovery ]-----------------------------------
|
||
|
||
list_all_services() { dc config --services; }
|
||
|
||
get_all_ue_services() { list_all_services | grep -E "${UE_NAME_REGEX}" || true; }
|
||
|
||
get_core_services() {
|
||
# everything except UEs, gNB, xApp
|
||
list_all_services | grep -Ev "(${UE_NAME_REGEX}|^${GNB_SERVICE}$|^${XAPP_SERVICE}$)" || true
|
||
}
|
||
|
||
get_running_ues() {
|
||
# Running UEs by service name (include TEST_UE name if custom)
|
||
if [[ -n "${TEST_UE:-}" ]]; then
|
||
dc ps --services --filter "status=running" \
|
||
| grep -E "${UE_NAME_REGEX}|^${TEST_UE}$" || true
|
||
else
|
||
dc ps --services --filter "status=running" \
|
||
| grep -E "${UE_NAME_REGEX}" || true
|
||
fi
|
||
}
|
||
|
||
# -------------------------[ Readiness helpers ]-------------------------------
|
||
|
||
svc_cid() { dc ps -q "$1"; } # container id for a service
|
||
|
||
wait_healthy() {
|
||
local svc="$1" timeout="${2:-60}"
|
||
local cid; cid="$(svc_cid "$svc")"
|
||
if [[ -z "$cid" ]]; then
|
||
log "wait_healthy: service '$svc' not running."
|
||
return 1
|
||
fi
|
||
local health
|
||
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{end}}' "$cid" 2>/dev/null || true)"
|
||
if [[ -z "$health" ]]; then
|
||
log "wait_healthy: no healthcheck for '$svc'; skipping."
|
||
return 0
|
||
fi
|
||
local start; start="$(ts_now)"
|
||
while :; do
|
||
health="$(docker inspect -f '{{.State.Health.Status}}' "$cid" 2>/dev/null || echo "")"
|
||
if [[ "$health" == "healthy" ]]; then
|
||
log "$svc is healthy."
|
||
return 0
|
||
fi
|
||
if (( $(ts_now) - start > timeout )); then
|
||
log "wait_healthy timeout for '$svc' (last='$health')."
|
||
return 1
|
||
fi
|
||
sleep 1
|
||
done
|
||
}
|
||
|
||
wait_log() {
|
||
local svc="$1" pat="$2" timeout="${3:-60}"
|
||
local cid; cid="$(svc_cid "$svc")"
|
||
if [[ -z "$cid" ]]; then
|
||
log "wait_log: service '$svc' not running."
|
||
return 1
|
||
fi
|
||
local start; start="$(ts_now)"
|
||
while :; do
|
||
if dc logs "$svc" --since=2m 2>&1 | grep -Eiq -- "$pat"; then
|
||
log "wait_log: detected '$pat' in '$svc'."
|
||
return 0
|
||
fi
|
||
if (( $(ts_now) - start > timeout )); then
|
||
log "wait_log timeout for '$svc' waiting '$pat'."
|
||
return 1
|
||
fi
|
||
sleep 1
|
||
done
|
||
}
|
||
|
||
get_container_ip() {
|
||
local cid; cid="$(svc_cid "$1")"
|
||
[[ -z "$cid" ]] && return 1
|
||
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$cid"
|
||
}
|
||
|
||
# --------------------------[ Staged bring-up ]--------------------------------
|
||
|
||
start_services_list() {
|
||
[[ $# -eq 0 ]] && return 0
|
||
dc up -d "$@"
|
||
}
|
||
|
||
start_ue_list() {
|
||
[[ $# -eq 0 ]] && return 0
|
||
for svc in "$@"; do
|
||
log "Starting: $svc"
|
||
dc up -d "$svc"
|
||
sleep 10
|
||
done
|
||
}
|
||
|
||
start_in_stages() {
|
||
# 1) CORE
|
||
mapfile -t core_svcs < <(get_core_services)
|
||
if [[ ${#core_svcs[@]} -gt 0 ]]; then
|
||
start_services_list "${core_svcs[@]}"
|
||
else
|
||
log "No CORE services to start."
|
||
exit 1
|
||
fi
|
||
|
||
# Ensure AMF is ready
|
||
wait_healthy "${AMF_SERVICE}" 15
|
||
wait_healthy "${UPF_SERVICE}" 15
|
||
exec_safe "${UPF_SERVICE}" 'bash -lc "
|
||
GW=192.168.72.129
|
||
DEV=\$(ip -4 route get \$GW 2>/dev/null | awk '\''/ dev /{for(i=1;i<=NF;i++) if(\$i==\"dev\"){print \$(i+1); exit}}'\''); DEV=\${DEV:-eth0}
|
||
ip -4 route replace \$GW/32 dev \$DEV
|
||
ip -4 route replace default via \$GW dev \$DEV onlink
|
||
"'
|
||
wait_healthy oai-flexric 15
|
||
|
||
# 2) gNB
|
||
start_services_list "${GNB_SERVICE}"
|
||
wait_log "${GNB_SERVICE}" "Received NGSetupResponse from AMF" 20
|
||
|
||
wait_log "${GNB_SERVICE}" "Transaction ID E2 SETUP-REQUEST 0 E2 SETUP-RESPONSE 0" 15
|
||
|
||
# 3) xApp
|
||
start_services_list "${XAPP_SERVICE}"
|
||
wait_healthy "${XAPP_SERVICE}" 15
|
||
|
||
# 4) UE(s)
|
||
# if [[ -n "${TEST_UE:-}" ]]; then
|
||
# # Allow multiple UEs in TEST_UE (comma and/or space separated)
|
||
# IFS=', ' read -r -a _ue_list <<< "${TEST_UE}"
|
||
# start_services_list "${_ue_list[@]}"
|
||
# for ue in "${_ue_list[@]}"; do
|
||
# [[ -z "$ue" ]] && continue
|
||
# if wait_oai_tun "$ue" 20; then
|
||
# exec_safe "$ue" 'ip route replace default dev oaitun_ue1' || true
|
||
# run_traffic_profile "$ue" "random" "0" || true
|
||
# else
|
||
# exit 1
|
||
# fi
|
||
# done
|
||
# else
|
||
# # Initial diurnal target, then enforce UE count
|
||
# local target; if [[ "${UE_SCALER_MODE}" == "diurnal" ]]; then target="$(calc_target_ue_count)"; else target="$(random_target_ue_count)"; fi
|
||
# log "Initial diurnal target UE count: ${target}"
|
||
# ensure_ue_count "$target"
|
||
# fi
|
||
}
|
||
|
||
# -------------------------[ Diurnal UE scaling ]------------------------------
|
||
|
||
calc_target_ue_count() {
|
||
local total_ues="${UE_MAX:-}"
|
||
if [[ -z "$total_ues" ]]; then
|
||
total_ues="$(get_all_ue_services | wc -l | tr -d ' ')"
|
||
fi
|
||
local min="${UE_MIN:-1}"
|
||
if (( total_ues < min )); then total_ues="$min"; fi
|
||
|
||
local H M
|
||
H="$(date +%H)"; M="$(date +%M)"
|
||
|
||
# Compute target via cosine wave (peak at UE_PEAK_HOUR), then add noise and clamp
|
||
awk -v min="$min" -v max="$total_ues" -v peak="${UE_PEAK_HOUR:-14}" \
|
||
-v noise="${UE_NOISE_FRAC:-0.15}" -v H="$H" -v M="$M" 'BEGIN{
|
||
# time-of-day in days [0,1)
|
||
t = (H*60.0 + M)/1440.0
|
||
ph = peak/24.0
|
||
norm = (cos(2*3.141592653589793*(t - ph)) + 1)/2 # 0..1
|
||
base = min + norm*(max-min)
|
||
|
||
srand(); u = (rand()*2.0 - 1.0) * noise # [-noise, +noise]
|
||
val = base * (1.0 + u)
|
||
|
||
if (val < min) val = min
|
||
if (val > max) val = max
|
||
|
||
# round to nearest integer
|
||
printf("%.0f\n", val)
|
||
}'
|
||
}
|
||
|
||
wait_oaitun() {
|
||
local ue="$1" timeout="${2:-20}"
|
||
for ((i=1; i<=timeout; i++)); do
|
||
if exec_safe "$ue" 'ip link show oaitun_ue1 >/dev/null 2>&1'; then
|
||
return 0
|
||
fi
|
||
sleep 1
|
||
done
|
||
return 1
|
||
}
|
||
|
||
ensure_ue_count() {
|
||
local target="$1"
|
||
|
||
# Build arrays
|
||
mapfile -t all_ues < <(get_all_ue_services)
|
||
mapfile -t running < <(get_running_ues)
|
||
|
||
# Current and desired counts
|
||
local cur="${#running[@]}"
|
||
|
||
if (( cur == target )); then
|
||
log "UE scaler: already at target (${target})."
|
||
return 0
|
||
fi
|
||
|
||
# Set helpers for membership tests
|
||
declare -A IS_RUNNING=()
|
||
for u in "${running[@]}"; do IS_RUNNING["$u"]=1; done
|
||
|
||
# Determine non-running UEs
|
||
declare -a not_running=()
|
||
for u in "${all_ues[@]}"; do
|
||
[[ -z "${IS_RUNNING[$u]:-}" ]] && not_running+=("$u")
|
||
done
|
||
|
||
# Start more UEs if needed
|
||
if (( cur < target )); then
|
||
local need=$(( target - cur ))
|
||
log "UE scaler: need +${need} UEs (cur=${cur}, target=${target})."
|
||
# Choose candidates (prefer random if shuf available)
|
||
local to_start=()
|
||
if command -v shuf >/dev/null 2>&1; then
|
||
mapfile -t to_start < <(printf '%s\n' "${not_running[@]}" | shuf -n "$need" || true)
|
||
else
|
||
to_start=("${not_running[@]:0:$need}")
|
||
fi
|
||
if (( ${#to_start[@]} > 0 )); then
|
||
start_ue_list "${to_start[@]}"
|
||
# kick traffic + (best-effort) readiness
|
||
for ue in "${to_start[@]}"; do
|
||
if wait_oaitun "$ue" 20; then
|
||
exec_safe "$ue" 'ip route replace default dev oaitun_ue1'
|
||
run_traffic_profile "$ue" "random" "0"
|
||
else
|
||
dc restart "$ue"
|
||
to_start+="$ue"
|
||
fi
|
||
done
|
||
fi
|
||
return 0
|
||
fi
|
||
|
||
# Otherwise stop some UEs to shrink
|
||
local drop=$(( cur - target ))
|
||
log "UE scaler: need -${drop} UEs (cur=${cur}, target=${target})."
|
||
local to_stop=()
|
||
if command -v shuf >/dev/null 2>&1; then
|
||
mapfile -t to_stop < <(printf '%s\n' "${running[@]}" | shuf -n "$drop" || true)
|
||
else
|
||
to_stop=("${running[@]:0:$drop}")
|
||
fi
|
||
for ue in "${to_stop[@]}"; do
|
||
log "Stopping UE ${ue}"
|
||
dc stop "$ue" || true
|
||
dc rm -f "$ue" || true
|
||
done
|
||
}
|
||
|
||
# --------------------------[ UE traffic helpers ]-----------------------------
|
||
|
||
pick_profile() {
|
||
local r=$((RANDOM % 100))
|
||
if (( r < 30 )); then echo browsing # 45%
|
||
elif (( r < 70 )); then echo video # 35%
|
||
elif (( r < 90 )); then echo bulk_download # 12%
|
||
else echo iot_chatty # 8%
|
||
fi
|
||
}
|
||
|
||
run_traffic_profile() {
|
||
local ue="$1" profile="$2" duration="${3:-0}"
|
||
exec_safe "$ue" "echo 'traffic runner ready for $ue. profile: $profile'"
|
||
exec_safe "$ue" "bash -lc '
|
||
if [ ! -f /tmp/traffic_profile.lock ]; then
|
||
prof=\$1; dur=\$2
|
||
/usr/local/bin/traffic_runner --profile \$prof --duration \$dur >/tmp/traffic_runner.log 2>&1 &
|
||
printf \"%s %s\n\" \"\$prof\" \"\$dur\" > /tmp/traffic_profile.lock
|
||
fi
|
||
' -- $profile $duration"
|
||
}
|
||
|
||
# --------------------------[ RRC / MAC scraping ]-----------------------------
|
||
|
||
rotate_mac_capture() {
|
||
local tag="${1:-}"
|
||
[[ -z "$tag" ]] && tag="$(ts_tag)"
|
||
if dc ps --services --filter "status=running" | grep -q "^${MAC_STATS_CONTAINER}$"; then
|
||
log "Rotating mac_stats from ${MAC_STATS_CONTAINER}"
|
||
dc cp "${MAC_STATS_CONTAINER}:${MAC_STATS_FILE_IN_CONTAINER}" \
|
||
"${ARTIFACT_DIR}/mac_stats_${tag}.log" || true
|
||
exec_safe "$MAC_STATS_CONTAINER" "> '${MAC_STATS_FILE_IN_CONTAINER}'"
|
||
else
|
||
log "mac_stats container ${MAC_STATS_CONTAINER} not running; skipping rotate"
|
||
fi
|
||
}
|
||
|
||
_since_arg_from_file() {
|
||
local f="$1"
|
||
local val
|
||
val="$(tr -d '[]\r\n' < "$f" 2>/dev/null || true)"
|
||
if [[ -n "$val" ]]; then
|
||
printf -- '--since=%s' "$val"
|
||
else
|
||
printf -- '--since=2m'
|
||
fi
|
||
}
|
||
|
||
dump_gnb_rrc_logs() {
|
||
local tag="$1"
|
||
dc logs "${GNB_SERVICE}" 2>&1 | grep -E "NR_RRC" \
|
||
> "${ARTIFACT_DIR}/gnb_rrc_${tag}.log" || true
|
||
}
|
||
|
||
# -----------------------------[ Power / CPU ]---------------------------------
|
||
|
||
init_power_logs() {
|
||
if [[ ! -s "$POWER_LOG_FILE" ]]; then
|
||
echo "epoch,ue,profile,power_mW,dt_s,energy_mJ" > "$POWER_LOG_FILE"
|
||
fi
|
||
if [[ ! -s "$CPU_PROXY_LOG_FILE" ]]; then
|
||
echo "epoch,container,cpu_percent,mem_usage" > "$CPU_PROXY_LOG_FILE"
|
||
fi
|
||
}
|
||
|
||
profile_total_mw() {
|
||
local profile="$1"
|
||
echo "${PWR_MW_PROFILE[$profile]:-0}"
|
||
}
|
||
|
||
sample_power_once() {
|
||
local epoch="$(ts_now)"
|
||
local dt_s="${POWER_LOG_INTERVAL_SEC}"
|
||
|
||
while IFS= read -r ue; do
|
||
[[ -z "$ue" ]] && continue
|
||
local lock
|
||
lock="$(exec_safe "$ue" 'bash -lc "cat /tmp/traffic_profile.lock 2>/dev/null || true"')"
|
||
local picked_profile picked_duration
|
||
picked_profile="$(awk "{print \$1}" <<<"$lock")"
|
||
picked_duration="$(awk "{print \$2}" <<<"$lock")"
|
||
|
||
if [[ -z "$picked_profile" ]]; then
|
||
picked_profile="${TEST_PROFILE:-idle}"
|
||
fi
|
||
|
||
while [[ "$picked_profile" == -* ]]; do
|
||
picked_profile="${picked_profile#-}"
|
||
done
|
||
picked_profile="${picked_profile,,}"
|
||
|
||
local p_mw
|
||
p_mw="$(profile_total_mw "$picked_profile")"
|
||
: "${p_mw:=0}"
|
||
|
||
local energy_mJ=$(( p_mw * dt_s ))
|
||
|
||
echo "${epoch},${ue},${picked_profile},${p_mw},${dt_s},${energy_mJ}" >> "$POWER_LOG_FILE"
|
||
done < <(get_running_ues)
|
||
}
|
||
|
||
# --- CPU proxy: query docker stats by container ID and map back to service name ---
|
||
cpu_proxy_once() {
|
||
local epoch="$(ts_now)"
|
||
|
||
# Build list of UE service names (same as container names)
|
||
declare -a SVC_LIST=()
|
||
while IFS= read -r svc; do
|
||
[[ -z "$svc" ]] && continue
|
||
SVC_LIST+=("$svc")
|
||
done < <(get_running_ues)
|
||
|
||
# Query docker stats by NAME and key 'seen' by NAME
|
||
local fmt='{{.Name}},{{.CPUPerc}},{{.MemUsage}}'
|
||
declare -A SEEN=()
|
||
|
||
while IFS=',' read -r name cpu mem; do
|
||
[[ -z "$name" ]] && continue
|
||
name="${name#/}"
|
||
SEEN["$name"]=1
|
||
cpu="${cpu%%%}" # drop trailing '%'
|
||
echo "${epoch},${name},${cpu},${mem}" >> "$CPU_PROXY_LOG_FILE"
|
||
done < <(docker stats --no-stream --format "$fmt" "${SVC_LIST[@]}" 2>/dev/null || true)
|
||
|
||
# Any container absent from docker stats output → record zeros once
|
||
local svc
|
||
for svc in "${SVC_LIST[@]}"; do
|
||
if [[ -z "${SEEN[$svc]:-}" ]]; then
|
||
echo "${epoch},${svc},0,0" >> "$CPU_PROXY_LOG_FILE"
|
||
fi
|
||
done
|
||
}
|
||
|
||
rotate_csv_if_any() {
|
||
local src="$1" prefix="$2" tag="${3:-}"
|
||
[[ -z "$tag" ]] && tag="$(ts_tag)"
|
||
if [[ -s "$src" ]]; then
|
||
mv "$src" "${ARTIFACT_DIR}/${prefix}_${tag}.csv" || true
|
||
case "$prefix" in
|
||
power_usage) echo "epoch,ue,profile,power_mW,dt_s,energy_mJ" > "$src" ;;
|
||
cpu_proxy) echo "epoch,container,cpu_percent,mem_usage" > "$src" ;;
|
||
esac
|
||
fi
|
||
}
|
||
|
||
finalize_and_rotate_artifacts() {
|
||
local tag; tag="$(ts_tag)"
|
||
rotate_mac_capture "$tag" || true
|
||
dump_gnb_rrc_logs "$tag" || true
|
||
rotate_csv_if_any "$POWER_LOG_FILE" "power_usage" "$tag"
|
||
rotate_csv_if_any "$CPU_PROXY_LOG_FILE" "cpu_proxy" "$tag"
|
||
}
|
||
|
||
# -------------------------------[ Signals ]-----------------------------------
|
||
|
||
__SHUTTING_DOWN=0
|
||
on_interrupt() {
|
||
if [[ "${__SHUTTING_DOWN}" -eq 1 ]]; then return; fi
|
||
__SHUTTING_DOWN=1
|
||
log "Signal received (INT/TERM); taking final samples and rotating artifacts…"
|
||
# One last sample tick
|
||
sample_power_once || true
|
||
cpu_proxy_once || true
|
||
# Rotate artifacts and clean up
|
||
finalize_and_rotate_artifacts
|
||
cleanup
|
||
# 130 is the conventional exit code for SIGINT
|
||
exit 130
|
||
}
|
||
|
||
# --------------------------------[ Cleanup ]----------------------------------
|
||
|
||
cleanup() {
|
||
# Default: stop only UEs; keep infra up for inspection
|
||
log "Cleanup: stopping UE containers only"
|
||
while IFS= read -r ue; do
|
||
[[ -z "$ue" ]] && continue
|
||
dc stop "$ue" || true
|
||
dc rm -f "$ue" || true
|
||
done < <(dc ps --services | grep -E "${UE_NAME_REGEX}" || true)
|
||
}
|
||
|
||
# ---------------------------------[ Main ]------------------------------------
|
||
|
||
|
||
trigger_system_burst() {
|
||
mapfile -t running < <(get_running_ues)
|
||
if (( ${#running[@]} == 0 )); then
|
||
return 0
|
||
fi
|
||
log "Triggering system-wide burst for ${#running[@]} UE(s)…"
|
||
for ue in "${running[@]}"; do
|
||
exec_safe "$ue" "bash -lc 'date +%s > ${BURST_MARK_FILE}'" || true
|
||
done
|
||
}
|
||
|
||
random_target_ue_count() {
|
||
local total_ues="${UE_MAX:-}"
|
||
if [[ -z "$total_ues" ]]; then
|
||
total_ues="$(get_all_ue_services | wc -l | tr -d ' ')"
|
||
fi
|
||
local min="${UE_MIN:-1}"
|
||
if (( total_ues < min )); then total_ues="$min"; fi
|
||
local val
|
||
if command -v shuf >/dev/null 2>&1; then
|
||
val="$(shuf -i "${min}-${total_ues}" -n 1)"
|
||
else
|
||
local span=$(( total_ues - min + 1 ))
|
||
val=$(( min + (RANDOM % span) ))
|
||
fi
|
||
echo "$val"
|
||
}
|
||
main() {
|
||
trap on_interrupt INT TERM
|
||
|
||
init_power_logs
|
||
|
||
# Staged bring-up with readiness checks for both normal and TEST modes
|
||
start_in_stages
|
||
|
||
# Initialize next system-burst time
|
||
if (( RANDOM_BURST_MODE )); then
|
||
local next_burst_ts
|
||
next_burst_ts="$(($(ts_now) + BURST_MIN_GAP_SEC + RANDOM % (BURST_MAX_GAP_SEC - BURST_MIN_GAP_SEC + 1)))"
|
||
fi
|
||
|
||
# Kick off traffic workload(s)
|
||
if [[ -n "${TEST_UE:-}" ]]; then
|
||
log "TEST UE(S) ENABLED"
|
||
# Support multiple UEs (comma/space/newline separated)
|
||
read -r -a _ue_list <<< "$(tr ',\n' ' ' <<< "${TEST_UE}")"
|
||
read -r -a _ue_profiles <<< "$(tr ',\n' ' ' <<< "${TEST_PROFILES}")"
|
||
start_ue_list "${_ue_list[@]}"
|
||
for i in "${!_ue_list[@]}"; do
|
||
ue="${_ue_list[$i]}"
|
||
prof="${_ue_profiles[$i]}"
|
||
[[ -z "$ue" ]] && continue
|
||
if wait_oaitun "$ue" 20; then
|
||
exec_safe "$ue" "ip route replace default dev oaitun_ue1"
|
||
run_traffic_profile "$ue" "$prof"
|
||
else
|
||
exit 1
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# Optional finite duration for TEST mode
|
||
if [[ -n "${TEST_UE:-}" && "${TEST_DURATION_SEC:-0}" -gt 0 ]]; then
|
||
RUN_STOP_TS=$(( $(ts_now) + TEST_DURATION_SEC ))
|
||
fi
|
||
|
||
# Main sampling & scaling loop
|
||
local last_rrc="$(( $(ts_now) - RRC_LOG_INTERVAL_SEC ))"
|
||
local last_power="$(( $(ts_now) - POWER_LOG_INTERVAL_SEC ))"
|
||
local last_cpu="$(( $(ts_now) - CPU_PROXY_INTERVAL_SEC ))"
|
||
local last_scale="$(( $(ts_now) - UE_SCALER_INTERVAL_SEC ))"
|
||
|
||
log "Entering main loop (sampling + diurnal scaler)…"
|
||
while :; do
|
||
local now; now="$(ts_now)"
|
||
# System-wide random burst scheduler
|
||
if (( RANDOM_BURST_MODE )); then
|
||
if (( now >= next_burst_ts )); then
|
||
trigger_system_burst || true
|
||
next_burst_ts="$(( now + BURST_MIN_GAP_SEC + RANDOM % (BURST_MAX_GAP_SEC - BURST_MIN_GAP_SEC + 1) ))"
|
||
fi
|
||
fi
|
||
|
||
|
||
if (( now - last_power >= POWER_LOG_INTERVAL_SEC )); then
|
||
sample_power_once || true
|
||
last_power="$now"
|
||
fi
|
||
|
||
if (( now - last_cpu >= CPU_PROXY_INTERVAL_SEC )); then
|
||
cpu_proxy_once || true
|
||
last_cpu="$now"
|
||
fi
|
||
|
||
# Re-evaluate diurnal target (normal mode only)
|
||
if [[ -z "${TEST_UE:-}" ]] && (( now - last_scale >= UE_SCALER_INTERVAL_SEC )); then
|
||
local target; if [[ "${UE_SCALER_MODE}" == "diurnal" ]]; then target="$(calc_target_ue_count)"; else target="$(random_target_ue_count)"; fi
|
||
log "Diurnal scaler tick: target = ${target}"
|
||
ensure_ue_count "$target"
|
||
last_scale="$now"
|
||
fi
|
||
|
||
# TEST duration stop gate
|
||
if [[ -n "${RUN_STOP_TS:-}" && "$now" -ge "${RUN_STOP_TS}" ]]; then
|
||
log "TEST run reached duration (${TEST_DURATION_SEC}s); finalizing artifacts…"
|
||
finalize_and_rotate_artifacts
|
||
cleanup
|
||
exit 0
|
||
fi
|
||
|
||
sleep 1
|
||
done
|
||
}
|
||
|
||
main "$@"
|