mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-21 23:11:01 +00:00
431 lines
12 KiB
Bash
Executable File
431 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
ANDROID_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
|
RESULTS_DIR="$ANDROID_DIR/benchmark/results"
|
|
|
|
PACKAGE="ai.openclaw.app"
|
|
ACTIVITY=".MainActivity"
|
|
DEVICE_SERIAL=""
|
|
INSTALL_APP="1"
|
|
LAUNCH_RUNS="4"
|
|
SCREEN_LOOPS="6"
|
|
CHAT_LOOPS="8"
|
|
POLL_ATTEMPTS="40"
|
|
POLL_INTERVAL_SECONDS="0.3"
|
|
SCREEN_MODE="transition"
|
|
CHAT_MODE="session-switch"
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
./scripts/perf-online-benchmark.sh [options]
|
|
|
|
Measures the fully-online Android app path on a connected device/emulator.
|
|
Assumes the app can reach a live gateway and will show "Connected" in the UI.
|
|
|
|
Options:
|
|
--device <serial> adb device serial
|
|
--package <pkg> package name (default: ai.openclaw.app)
|
|
--activity <activity> launch activity (default: .MainActivity)
|
|
--skip-install skip :app:installDebug
|
|
--launch-runs <n> launch-to-connected runs (default: 4)
|
|
--screen-loops <n> screen benchmark loops (default: 6)
|
|
--chat-loops <n> chat benchmark loops (default: 8)
|
|
--screen-mode <mode> transition | scroll (default: transition)
|
|
--chat-mode <mode> session-switch | scroll (default: session-switch)
|
|
-h, --help show help
|
|
EOF
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--device)
|
|
DEVICE_SERIAL="${2:-}"
|
|
shift 2
|
|
;;
|
|
--package)
|
|
PACKAGE="${2:-}"
|
|
shift 2
|
|
;;
|
|
--activity)
|
|
ACTIVITY="${2:-}"
|
|
shift 2
|
|
;;
|
|
--skip-install)
|
|
INSTALL_APP="0"
|
|
shift
|
|
;;
|
|
--launch-runs)
|
|
LAUNCH_RUNS="${2:-}"
|
|
shift 2
|
|
;;
|
|
--screen-loops)
|
|
SCREEN_LOOPS="${2:-}"
|
|
shift 2
|
|
;;
|
|
--chat-loops)
|
|
CHAT_LOOPS="${2:-}"
|
|
shift 2
|
|
;;
|
|
--screen-mode)
|
|
SCREEN_MODE="${2:-}"
|
|
shift 2
|
|
;;
|
|
--chat-mode)
|
|
CHAT_MODE="${2:-}"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown arg: $1" >&2
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
require_cmd() {
|
|
if ! command -v "$1" >/dev/null 2>&1; then
|
|
echo "$1 required but missing." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
require_cmd adb
|
|
require_cmd awk
|
|
require_cmd rg
|
|
require_cmd node
|
|
|
|
adb_cmd() {
|
|
if [[ -n "$DEVICE_SERIAL" ]]; then
|
|
adb -s "$DEVICE_SERIAL" "$@"
|
|
else
|
|
adb "$@"
|
|
fi
|
|
}
|
|
|
|
device_count="$(adb devices | awk 'NR>1 && $2=="device" {c+=1} END {print c+0}')"
|
|
if [[ -z "$DEVICE_SERIAL" && "$device_count" -lt 1 ]]; then
|
|
echo "No connected Android device (adb state=device)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "$DEVICE_SERIAL" && "$device_count" -gt 1 ]]; then
|
|
echo "Multiple adb devices found. Pass --device <serial>." >&2
|
|
adb devices -l >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$SCREEN_MODE" != "transition" && "$SCREEN_MODE" != "scroll" ]]; then
|
|
echo "Unsupported --screen-mode: $SCREEN_MODE" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ "$CHAT_MODE" != "session-switch" && "$CHAT_MODE" != "scroll" ]]; then
|
|
echo "Unsupported --chat-mode: $CHAT_MODE" >&2
|
|
exit 2
|
|
fi
|
|
|
|
mkdir -p "$RESULTS_DIR"
|
|
|
|
timestamp="$(date +%Y%m%d-%H%M%S)"
|
|
run_dir="$RESULTS_DIR/online-$timestamp"
|
|
mkdir -p "$run_dir"
|
|
|
|
cleanup() {
|
|
rm -f "$run_dir"/ui-*.xml
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
if [[ "$INSTALL_APP" == "1" ]]; then
|
|
(
|
|
cd "$ANDROID_DIR"
|
|
./gradlew :app:installDebug --console=plain >"$run_dir/install.log" 2>&1
|
|
)
|
|
fi
|
|
|
|
read -r display_width display_height <<<"$(
|
|
adb_cmd shell wm size \
|
|
| awk '/Physical size:/ { split($3, dims, "x"); print dims[1], dims[2]; exit }'
|
|
)"
|
|
|
|
if [[ -z "${display_width:-}" || -z "${display_height:-}" ]]; then
|
|
echo "Failed to read device display size." >&2
|
|
exit 1
|
|
fi
|
|
|
|
pct_of() {
|
|
local total="$1"
|
|
local pct="$2"
|
|
awk -v total="$total" -v pct="$pct" 'BEGIN { printf "%d", total * pct }'
|
|
}
|
|
|
|
tab_connect_x="$(pct_of "$display_width" "0.11")"
|
|
tab_chat_x="$(pct_of "$display_width" "0.31")"
|
|
tab_screen_x="$(pct_of "$display_width" "0.69")"
|
|
tab_y="$(pct_of "$display_height" "0.93")"
|
|
chat_session_y="$(pct_of "$display_height" "0.13")"
|
|
chat_session_left_x="$(pct_of "$display_width" "0.16")"
|
|
chat_session_right_x="$(pct_of "$display_width" "0.85")"
|
|
center_x="$(pct_of "$display_width" "0.50")"
|
|
screen_swipe_top_y="$(pct_of "$display_height" "0.27")"
|
|
screen_swipe_mid_y="$(pct_of "$display_height" "0.38")"
|
|
screen_swipe_low_y="$(pct_of "$display_height" "0.75")"
|
|
screen_swipe_bottom_y="$(pct_of "$display_height" "0.77")"
|
|
chat_swipe_top_y="$(pct_of "$display_height" "0.29")"
|
|
chat_swipe_mid_y="$(pct_of "$display_height" "0.38")"
|
|
chat_swipe_bottom_y="$(pct_of "$display_height" "0.71")"
|
|
|
|
dump_ui() {
|
|
local name="$1"
|
|
local file="$run_dir/ui-$name.xml"
|
|
adb_cmd shell uiautomator dump "/sdcard/$name.xml" >/dev/null 2>&1
|
|
adb_cmd shell cat "/sdcard/$name.xml" >"$file"
|
|
printf '%s\n' "$file"
|
|
}
|
|
|
|
ui_has() {
|
|
local pattern="$1"
|
|
local name="$2"
|
|
local file
|
|
file="$(dump_ui "$name")"
|
|
rg -q "$pattern" "$file"
|
|
}
|
|
|
|
wait_for_pattern() {
|
|
local pattern="$1"
|
|
local prefix="$2"
|
|
for attempt in $(seq 1 "$POLL_ATTEMPTS"); do
|
|
if ui_has "$pattern" "$prefix-$attempt"; then
|
|
return 0
|
|
fi
|
|
sleep "$POLL_INTERVAL_SECONDS"
|
|
done
|
|
return 1
|
|
}
|
|
|
|
ensure_connected() {
|
|
if ! wait_for_pattern 'text="Connected"' "connected"; then
|
|
echo "App never reached visible Connected state." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
ensure_screen_online() {
|
|
adb_cmd shell input tap "$tab_screen_x" "$tab_y" >/dev/null
|
|
sleep 2
|
|
if ! ui_has 'android\.webkit\.WebView' "screen"; then
|
|
echo "Screen benchmark expected a live WebView." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
ensure_chat_online() {
|
|
adb_cmd shell input tap "$tab_chat_x" "$tab_y" >/dev/null
|
|
sleep 2
|
|
if ! ui_has 'Type a message' "chat"; then
|
|
echo "Chat benchmark expected the live chat composer." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
capture_mem() {
|
|
local file="$1"
|
|
adb_cmd shell dumpsys meminfo "$PACKAGE" >"$file"
|
|
}
|
|
|
|
start_cpu_sampler() {
|
|
local file="$1"
|
|
local samples="$2"
|
|
: >"$file"
|
|
(
|
|
for _ in $(seq 1 "$samples"); do
|
|
adb_cmd shell top -b -n 1 \
|
|
| awk -v pkg="$PACKAGE" '$NF==pkg { print $9 }' >>"$file"
|
|
sleep 0.5
|
|
done
|
|
) &
|
|
CPU_SAMPLER_PID="$!"
|
|
}
|
|
|
|
summarize_cpu() {
|
|
local file="$1"
|
|
local prefix="$2"
|
|
local avg max median count
|
|
avg="$(awk '{sum+=$1; n++} END {if(n) printf "%.1f", sum/n; else print 0}' "$file")"
|
|
max="$(sort -n "$file" | tail -n 1)"
|
|
median="$(
|
|
sort -n "$file" \
|
|
| awk '{a[NR]=$1} END { if (NR==0) { print 0 } else if (NR%2==1) { printf "%.1f", a[(NR+1)/2] } else { printf "%.1f", (a[NR/2]+a[NR/2+1])/2 } }'
|
|
)"
|
|
count="$(wc -l <"$file" | tr -d ' ')"
|
|
printf '%s.cpu_avg_pct=%s\n' "$prefix" "$avg" >>"$run_dir/summary.txt"
|
|
printf '%s.cpu_median_pct=%s\n' "$prefix" "$median" >>"$run_dir/summary.txt"
|
|
printf '%s.cpu_peak_pct=%s\n' "$prefix" "$max" >>"$run_dir/summary.txt"
|
|
printf '%s.cpu_count=%s\n' "$prefix" "$count" >>"$run_dir/summary.txt"
|
|
}
|
|
|
|
summarize_mem() {
|
|
local file="$1"
|
|
local prefix="$2"
|
|
awk -v prefix="$prefix" '
|
|
/TOTAL PSS:/ { printf "%s.pss_kb=%s\n%s.rss_kb=%s\n", prefix, $3, prefix, $6 }
|
|
/Graphics:/ { printf "%s.graphics_kb=%s\n", prefix, $2 }
|
|
/WebViews:/ { printf "%s.webviews=%s\n", prefix, $NF }
|
|
' "$file" >>"$run_dir/summary.txt"
|
|
}
|
|
|
|
summarize_gfx() {
|
|
local file="$1"
|
|
local prefix="$2"
|
|
awk -v prefix="$prefix" '
|
|
/Total frames rendered:/ { printf "%s.frames=%s\n", prefix, $4 }
|
|
/Janky frames:/ && $4 ~ /\(/ {
|
|
pct=$4
|
|
gsub(/[()%]/, "", pct)
|
|
printf "%s.janky_frames=%s\n%s.janky_pct=%s\n", prefix, $3, prefix, pct
|
|
}
|
|
/50th percentile:/ { gsub(/ms/, "", $3); printf "%s.p50_ms=%s\n", prefix, $3 }
|
|
/90th percentile:/ { gsub(/ms/, "", $3); printf "%s.p90_ms=%s\n", prefix, $3 }
|
|
/95th percentile:/ { gsub(/ms/, "", $3); printf "%s.p95_ms=%s\n", prefix, $3 }
|
|
/99th percentile:/ { gsub(/ms/, "", $3); printf "%s.p99_ms=%s\n", prefix, $3 }
|
|
' "$file" >>"$run_dir/summary.txt"
|
|
}
|
|
|
|
measure_launch() {
|
|
: >"$run_dir/launch-runs.txt"
|
|
for run in $(seq 1 "$LAUNCH_RUNS"); do
|
|
adb_cmd shell am force-stop "$PACKAGE" >/dev/null
|
|
sleep 1
|
|
start_ms="$(node -e 'console.log(Date.now())')"
|
|
am_out="$(adb_cmd shell am start -W -n "$PACKAGE/$ACTIVITY")"
|
|
total_time="$(printf '%s\n' "$am_out" | awk -F: '/TotalTime:/{gsub(/ /, "", $2); print $2}')"
|
|
connected_ms="timeout"
|
|
for _ in $(seq 1 "$POLL_ATTEMPTS"); do
|
|
if ui_has 'text="Connected"' "launch-run-$run"; then
|
|
now_ms="$(node -e 'console.log(Date.now())')"
|
|
connected_ms="$((now_ms - start_ms))"
|
|
break
|
|
fi
|
|
sleep "$POLL_INTERVAL_SECONDS"
|
|
done
|
|
printf 'run=%s total_time_ms=%s connected_ms=%s\n' "$run" "${total_time:-na}" "$connected_ms" \
|
|
| tee -a "$run_dir/launch-runs.txt"
|
|
done
|
|
|
|
awk -F'[ =]' '
|
|
/total_time_ms=[0-9]+/ {
|
|
value=$4
|
|
sum+=value
|
|
count+=1
|
|
if (min==0 || value<min) min=value
|
|
if (value>max) max=value
|
|
}
|
|
END {
|
|
if (count==0) exit
|
|
printf "launch.total_time_avg_ms=%.1f\nlaunch.total_time_min_ms=%d\nlaunch.total_time_max_ms=%d\n", sum/count, min, max
|
|
}
|
|
' "$run_dir/launch-runs.txt" >>"$run_dir/summary.txt"
|
|
|
|
awk -F'[ =]' '
|
|
/connected_ms=[0-9]+/ {
|
|
value=$6
|
|
sum+=value
|
|
count+=1
|
|
if (min==0 || value<min) min=value
|
|
if (value>max) max=value
|
|
}
|
|
END {
|
|
if (count==0) exit
|
|
printf "launch.connected_avg_ms=%.1f\nlaunch.connected_min_ms=%d\nlaunch.connected_max_ms=%d\n", sum/count, min, max
|
|
}
|
|
' "$run_dir/launch-runs.txt" >>"$run_dir/summary.txt"
|
|
}
|
|
|
|
run_screen_benchmark() {
|
|
ensure_screen_online
|
|
capture_mem "$run_dir/screen-mem-before.txt"
|
|
adb_cmd shell dumpsys gfxinfo "$PACKAGE" reset >/dev/null
|
|
start_cpu_sampler "$run_dir/screen-cpu.txt" 18
|
|
|
|
if [[ "$SCREEN_MODE" == "transition" ]]; then
|
|
for _ in $(seq 1 "$SCREEN_LOOPS"); do
|
|
adb_cmd shell input tap "$tab_screen_x" "$tab_y" >/dev/null
|
|
sleep 1.0
|
|
adb_cmd shell input tap "$tab_chat_x" "$tab_y" >/dev/null
|
|
sleep 0.8
|
|
done
|
|
else
|
|
adb_cmd shell input tap "$tab_screen_x" "$tab_y" >/dev/null
|
|
sleep 1.5
|
|
for _ in $(seq 1 "$SCREEN_LOOPS"); do
|
|
adb_cmd shell input swipe "$center_x" "$screen_swipe_bottom_y" "$center_x" "$screen_swipe_top_y" 250 >/dev/null
|
|
sleep 0.35
|
|
adb_cmd shell input swipe "$center_x" "$screen_swipe_mid_y" "$center_x" "$screen_swipe_low_y" 250 >/dev/null
|
|
sleep 0.35
|
|
done
|
|
fi
|
|
|
|
wait "$CPU_SAMPLER_PID"
|
|
adb_cmd shell dumpsys gfxinfo "$PACKAGE" >"$run_dir/screen-gfx.txt"
|
|
capture_mem "$run_dir/screen-mem-after.txt"
|
|
summarize_gfx "$run_dir/screen-gfx.txt" "screen"
|
|
summarize_cpu "$run_dir/screen-cpu.txt" "screen"
|
|
summarize_mem "$run_dir/screen-mem-before.txt" "screen.before"
|
|
summarize_mem "$run_dir/screen-mem-after.txt" "screen.after"
|
|
}
|
|
|
|
run_chat_benchmark() {
|
|
ensure_chat_online
|
|
capture_mem "$run_dir/chat-mem-before.txt"
|
|
adb_cmd shell dumpsys gfxinfo "$PACKAGE" reset >/dev/null
|
|
start_cpu_sampler "$run_dir/chat-cpu.txt" 18
|
|
|
|
if [[ "$CHAT_MODE" == "session-switch" ]]; then
|
|
for _ in $(seq 1 "$CHAT_LOOPS"); do
|
|
adb_cmd shell input tap "$chat_session_left_x" "$chat_session_y" >/dev/null
|
|
sleep 0.8
|
|
adb_cmd shell input tap "$chat_session_right_x" "$chat_session_y" >/dev/null
|
|
sleep 0.8
|
|
done
|
|
else
|
|
for _ in $(seq 1 "$CHAT_LOOPS"); do
|
|
adb_cmd shell input swipe "$center_x" "$chat_swipe_bottom_y" "$center_x" "$chat_swipe_top_y" 250 >/dev/null
|
|
sleep 0.35
|
|
adb_cmd shell input swipe "$center_x" "$chat_swipe_mid_y" "$center_x" "$chat_swipe_bottom_y" 250 >/dev/null
|
|
sleep 0.35
|
|
done
|
|
fi
|
|
|
|
wait "$CPU_SAMPLER_PID"
|
|
adb_cmd shell dumpsys gfxinfo "$PACKAGE" >"$run_dir/chat-gfx.txt"
|
|
capture_mem "$run_dir/chat-mem-after.txt"
|
|
summarize_gfx "$run_dir/chat-gfx.txt" "chat"
|
|
summarize_cpu "$run_dir/chat-cpu.txt" "chat"
|
|
summarize_mem "$run_dir/chat-mem-before.txt" "chat.before"
|
|
summarize_mem "$run_dir/chat-mem-after.txt" "chat.after"
|
|
}
|
|
|
|
printf 'device.serial=%s\n' "${DEVICE_SERIAL:-default}" >"$run_dir/summary.txt"
|
|
printf 'device.display=%sx%s\n' "$display_width" "$display_height" >>"$run_dir/summary.txt"
|
|
printf 'config.launch_runs=%s\n' "$LAUNCH_RUNS" >>"$run_dir/summary.txt"
|
|
printf 'config.screen_loops=%s\n' "$SCREEN_LOOPS" >>"$run_dir/summary.txt"
|
|
printf 'config.chat_loops=%s\n' "$CHAT_LOOPS" >>"$run_dir/summary.txt"
|
|
printf 'config.screen_mode=%s\n' "$SCREEN_MODE" >>"$run_dir/summary.txt"
|
|
printf 'config.chat_mode=%s\n' "$CHAT_MODE" >>"$run_dir/summary.txt"
|
|
|
|
ensure_connected
|
|
measure_launch
|
|
ensure_connected
|
|
run_screen_benchmark
|
|
ensure_connected
|
|
run_chat_benchmark
|
|
|
|
printf 'results_dir=%s\n' "$run_dir"
|
|
cat "$run_dir/summary.txt"
|