Project

General

Profile

appcds_mem.sh

Roger Borrello, 06/17/2026 02:48 PM

Download (6.86 KB)

 
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."