|
1
|
#!/bin/bash
|
|
2
|
|
|
3
|
# Measure client JVM memory + startup with and without AppCDS, for A/B comparison.
|
|
4
|
#
|
|
5
|
# For each mode it launches one or more clients (via client-terminal.sh by default),
|
|
6
|
# waits for the ClientDriver JVM(s) to appear, samples memory while they settle,
|
|
7
|
# then stops them and reports aggregate numbers:
|
|
8
|
# - "appcds" : default client.sh behavior (curated classpath + shared archive)
|
|
9
|
# - "no-appcds" : client.sh --no-appcds (full classpath discovery, no archive)
|
|
10
|
#
|
|
11
|
# Options (before an optional "--"; everything after "--" is passed to BOTH runs):
|
|
12
|
# --settle N seconds to sample each run (default 12)
|
|
13
|
# --clients K launch K concurrent clients (default 1)
|
|
14
|
# --launcher P entry point to launch (default ./client-terminal.sh)
|
|
15
|
#
|
|
16
|
# Examples:
|
|
17
|
# ./appcds_mem.sh -- -i 1 # single ChUI client
|
|
18
|
# ./appcds_mem.sh --clients 5 -- -i 1 # 5 clients: see cross-process archive sharing
|
|
19
|
# ./appcds_mem.sh --launcher ./client.sh -- --swing
|
|
20
|
#
|
|
21
|
# Why K>1 matters: a single client's CDS archive is a PRIVATE mapping (shows in
|
|
22
|
# PrivClean), so single-process RSS rarely drops. With several clients mapping the
|
|
23
|
# SAME .jsa, those pages are shared -> SharedClean rises and total Pss (which splits
|
|
24
|
# shared pages across mappers) drops. That sharing, plus faster startup (ToSettle),
|
|
25
|
# is AppCDS's actual win.
|
|
26
|
#
|
|
27
|
# NOTE: clients must actually start and reach idle for the numbers to mean anything
|
|
28
|
# (server running + any required connect args). Runs that don't come up are flagged.
|
|
29
|
|
|
30
|
srcpath="$(cd "$(dirname "$0")"; pwd)"
|
|
31
|
|
|
32
|
settle=12
|
|
33
|
clients=1
|
|
34
|
launcher="$srcpath/client-terminal.sh"
|
|
35
|
while [[ $# -gt 0 ]]; do
|
|
36
|
case "$1" in
|
|
37
|
--settle) settle="$2"; shift 2 ;;
|
|
38
|
--clients) clients="$2"; shift 2 ;;
|
|
39
|
--launcher) launcher="$2"; shift 2 ;;
|
|
40
|
--) shift; break ;;
|
|
41
|
*) break ;;
|
|
42
|
esac
|
|
43
|
done
|
|
44
|
passthrough=("$@")
|
|
45
|
|
|
46
|
now() { date +%s.%N; }
|
|
47
|
elapsed(){ awk -v a="$1" -v b="$2" 'BEGIN{ d=b-a; if(d<0)d=0; printf "%.1f", d }'; }
|
|
48
|
mb() { awk -v k="${1:-0}" 'BEGIN{ printf "%.1f", k/1024 }'; }
|
|
49
|
|
|
50
|
vmrss_kb(){ awk '/^VmRSS:/{print $2}' "/proc/$1/status" 2>/dev/null; }
|
|
51
|
|
|
52
|
# Sum VmRSS (kB) across all pids in "$@".
|
|
53
|
agg_vmrss(){ local p s=0 r; for p in "$@"; do r=$(vmrss_kb "$p"); [ -n "$r" ] && s=$((s+r)); done; echo "$s"; }
|
|
54
|
|
|
55
|
# Sum smaps_rollup fields across pids. Echoes: "rss pss shclean privclean privdirty" (kB).
|
|
56
|
agg_smaps(){
|
|
57
|
awk '
|
|
58
|
/^Rss:/{r+=$2} /^Pss:/{p+=$2} /^Shared_Clean:/{s+=$2}
|
|
59
|
/^Private_Clean:/{pc+=$2} /^Private_Dirty:/{d+=$2}
|
|
60
|
END{ printf "%d %d %d %d %d", r, p, s, pc, d }' \
|
|
61
|
$(for p in "$@"; do echo "/proc/$p/smaps_rollup"; done) 2>/dev/null
|
|
62
|
}
|
|
63
|
|
|
64
|
# Run one mode. $1=label, $2=extra client flag ("" or "--no-appcds").
|
|
65
|
# Echoes: "label nUp peakKB rssKB pssKB shKB pcKB pdKB archive toSettle status"
|
|
66
|
run_one()
|
|
67
|
{
|
|
68
|
local label="$1" flag="$2"
|
|
69
|
local before; before="$(pgrep -f 'com.goldencode.p2j.main.ClientDriver' 2>/dev/null | sort)"
|
|
70
|
|
|
71
|
# Launch $clients instances, each under a pty (script) with stdin held open so a
|
|
72
|
# native ChUI driver idles instead of seeing EOF; setsid gives each its own group.
|
|
73
|
local sids=() c log hold=$(( settle + 30 ))
|
|
74
|
local t0; t0="$(now)"
|
|
75
|
for c in $(seq 1 "$clients"); do
|
|
76
|
log="$(mktemp)"
|
|
77
|
setsid bash -c "sleep $hold | script -qfc \"$launcher $flag ${passthrough[*]}\" /dev/null" >"$log" 2>&1 &
|
|
78
|
sids+=("$!")
|
|
79
|
done
|
|
80
|
|
|
81
|
# Wait (up to ~30s) for all $clients new ClientDriver JVMs to appear.
|
|
82
|
local pids=() i
|
|
83
|
for i in $(seq 1 60); do
|
|
84
|
local now_pids; now_pids="$(pgrep -f 'com.goldencode.p2j.main.ClientDriver' 2>/dev/null | sort)"
|
|
85
|
mapfile -t pids < <(comm -13 <(printf '%s\n' "$before") <(printf '%s\n' "$now_pids"))
|
|
86
|
[ "${#pids[@]}" -ge "$clients" ] && break
|
|
87
|
sleep 0.5
|
|
88
|
done
|
|
89
|
|
|
90
|
local status="ok" peak=0 peak_t="$t0"
|
|
91
|
local nUp="${#pids[@]}"
|
|
92
|
[ "$nUp" -lt "$clients" ] && status="only ${nUp}/${clients} up"
|
|
93
|
if [ "$nUp" -eq 0 ]; then
|
|
94
|
status="no-jvm"
|
|
95
|
else
|
|
96
|
# Sample aggregate RSS; track peak and the time of the last peak increase
|
|
97
|
# (a proxy for "warmup done" -- RSS ramps during class loading, then plateaus).
|
|
98
|
local iters=$(( settle * 5 )) agg
|
|
99
|
for i in $(seq 1 "$iters"); do
|
|
100
|
agg="$(agg_vmrss "${pids[@]}")"
|
|
101
|
if [ "${agg:-0}" -gt "$peak" ]; then peak="$agg"; peak_t="$(now)"; fi
|
|
102
|
# stop early if everything died
|
|
103
|
local alive=0 p; for p in "${pids[@]}"; do [ -d "/proc/$p" ] && alive=1; done
|
|
104
|
[ "$alive" -eq 0 ] && { status="exited-early"; break; }
|
|
105
|
sleep 0.2
|
|
106
|
done
|
|
107
|
fi
|
|
108
|
local toSettle; toSettle="$(elapsed "$t0" "$peak_t")"
|
|
109
|
|
|
110
|
# Aggregate smaps + archive-mapped check (need at least one live pid).
|
|
111
|
local rss=0 pss=0 shclean=0 privclean=0 privdirty=0 archive="-"
|
|
112
|
local live=()
|
|
113
|
for p in "${pids[@]}"; do [ -d "/proc/$p" ] && live+=("$p"); done
|
|
114
|
if [ "${#live[@]}" -gt 0 ]; then
|
|
115
|
read -r rss pss shclean privclean privdirty < <(agg_smaps "${live[@]}")
|
|
116
|
if grep -q '/appcds/client/.*\.jsa' "/proc/${live[0]}/maps" 2>/dev/null; then
|
|
117
|
archive="mapped"
|
|
118
|
else
|
|
119
|
archive="absent"
|
|
120
|
fi
|
|
121
|
fi
|
|
122
|
|
|
123
|
# Stop every client's process group.
|
|
124
|
for c in "${sids[@]}"; do kill -TERM -"$c" 2>/dev/null; done
|
|
125
|
sleep 1
|
|
126
|
for c in "${sids[@]}"; do kill -KILL -"$c" 2>/dev/null; done
|
|
127
|
wait 2>/dev/null
|
|
128
|
|
|
129
|
printf '%s %s %s %s %s %s %s %s %s %s %s\n' \
|
|
130
|
"$label" "$nUp" "$peak" "$rss" "$pss" "$shclean" "$privclean" "$privdirty" \
|
|
131
|
"$archive" "$toSettle" "$status"
|
|
132
|
}
|
|
133
|
|
|
134
|
echo "Launcher: $launcher"
|
|
135
|
echo "Clients per run: $clients; settle: ${settle}s; client args: ${passthrough[*]:-(none)}"
|
|
136
|
echo
|
|
137
|
|
|
138
|
on_res="$(run_one appcds "")"
|
|
139
|
off_res="$(run_one no-appcds "--no-appcds")"
|
|
140
|
|
|
141
|
hdr='%-10s %4s %9s %9s %9s %10s %10s %10s %-7s %8s %s\n'
|
|
142
|
printf "$hdr" MODE "#Up" "PeakRSS" "Rss" "Pss" "SharedCln" "PrivClean" "PrivDirty" "ARCHIVE" "ToSettle" "STATUS"
|
|
143
|
printf "$hdr" "" "" "(MB)" "(MB)" "(MB)" "(MB)" "(MB)" "(MB)" "" "(s)" ""
|
|
144
|
for res in "$on_res" "$off_res"; do
|
|
145
|
read -r label nUp peak rss pss shc pc prd archive toSettle status <<< "$res"
|
|
146
|
printf "$hdr" "$label" "$nUp" "$(mb "$peak")" "$(mb "$rss")" "$(mb "$pss")" \
|
|
147
|
"$(mb "$shc")" "$(mb "$pc")" "$(mb "$prd")" "$archive" "$toSettle" "$status"
|
|
148
|
done
|
|
149
|
|
|
150
|
echo
|
|
151
|
echo "Reading the table:"
|
|
152
|
echo " - ARCHIVE=mapped means the .jsa is really mapped in; =absent means -Xshare:auto"
|
|
153
|
echo " silently skipped it (classpath mismatch) -- then 'appcds' isn't using CDS at all."
|
|
154
|
echo " - ToSettle is wall-time to the last RSS increase (warmup proxy). CDS should make"
|
|
155
|
echo " the 'appcds' value smaller -- that startup speedup is its main single-client win."
|
|
156
|
echo " - Pss splits shared pages across mappers. With --clients >1 and ARCHIVE=mapped,"
|
|
157
|
echo " compare total Pss (and SharedClean): appcds should win as K grows, because the"
|
|
158
|
echo " one archive is shared across all K JVMs. At K=1 appcds often ties or loses on RSS."
|
|
159
|
echo " - 'no-appcds' also loads the full library set, so part of any delta is footprint."
|