#!/bin/bash

# Snapshot memory of an ALREADY-RUNNING client (e.g. a spawned webClient) by PID,
# and accumulate rows across runs so you can compare configs that each require a
# server bounce in between.
#
# Unlike appcds_mem.sh (which launches the client itself), this samples processes
# you point it at. Typical webClient workflow:
#
#   1. Configure directory.xml for the FIRST config (e.g. AppCDS on), start server.
#   2. Open https://localhost:8443/chui ; find the spawned client PID, e.g.:
#         pgrep -fa com.goldencode.p2j.main.ClientDriver
#   3. ./appcds_mem_pid.sh --reset appcds <pid>
#   4. Stop server, change directory.xml for the SECOND config (e.g. --no-appcds:
#      drop -XX:SharedArchiveFile from clientConfig/jvmArgs), restart, relaunch.
#   5. ./appcds_mem_pid.sh no-appcds <newpid>
#
# Each run samples the PID(s) for a settle window (tracking peak RSS), reads the
# smaps_rollup breakdown, checks whether the .jsa is mapped, appends a row to a
# small store file, and reprints the accumulated table.
#
# Usage:
#   ./appcds_mem_pid.sh [--settle N] [--reset] [--store FILE] <label> <pid> [pid...]
#     --settle N   seconds to sample (track peak); 0 = single snapshot   (default 8)
#     --reset      clear the store before adding this row (start a fresh comparison)
#     --store F    accumulation file                       (default <scriptdir>/.appcds_mem_pid.tsv)
#   Pass multiple PIDs to aggregate (e.g. several concurrent webClients).

srcpath="$(cd "$(dirname "$0")"; pwd)"
settle=8
reset=
store="$srcpath/.appcds_mem_pid.tsv"
args=()
while [[ $# -gt 0 ]]; do
   case "$1" in
      --settle) settle="$2"; shift 2 ;;
      --reset)  reset=1; shift ;;
      --store)  store="$2"; shift 2 ;;
      *)        args+=("$1"); shift ;;
   esac
done

label="${args[0]}"
pids=("${args[@]:1}")
if [ -z "$label" ] || [ "${#pids[@]}" -eq 0 ]; then
   echo "Usage: $0 [--settle N] [--reset] [--store FILE] <label> <pid> [pid...]" >&2
   exit 1
fi

mb()       { awk -v k="${1:-0}" 'BEGIN{ printf "%.1f", k/1024 }'; }
vmrss_kb() { awk '/^VmRSS:/{print $2}' "/proc/$1/status" 2>/dev/null; }
agg_vmrss(){ local p s=0 r; for p in "$@"; do r=$(vmrss_kb "$p"); [ -n "$r" ] && s=$((s+r)); done; echo "$s"; }
agg_smaps(){ # echoes "rss pss shclean privclean privdirty" (kB) summed over pids
   awk '
      /^Rss:/{r+=$2} /^Pss:/{p+=$2} /^Shared_Clean:/{s+=$2}
      /^Private_Clean:/{pc+=$2} /^Private_Dirty:/{d+=$2}
      END{ printf "%d %d %d %d %d", r, p, s, pc, d }' \
      $(for p in "$@"; do echo "/proc/$p/smaps_rollup"; done) 2>/dev/null
}

# Validate pids up front.
live=()
for p in "${pids[@]}"; do
   if [ -d "/proc/$p" ]; then live+=("$p"); else echo "warning: pid $p not present, skipping" >&2; fi
done
[ "${#live[@]}" -eq 0 ] && { echo "error: no live pids" >&2; exit 1; }

# Sample peak aggregate RSS over the settle window.
peak=0
iters=$(( settle * 5 )); [ "$iters" -lt 1 ] && iters=1
for ((i=0; i<iters; i++)); do
   agg="$(agg_vmrss "${live[@]}")"
   [ "${agg:-0}" -gt "$peak" ] && peak="$agg"
   [ "$settle" -gt 0 ] && sleep 0.2
done

# Re-collect live pids (some may have exited) for the smaps + archive snapshot.
snap=()
for p in "${live[@]}"; do [ -d "/proc/$p" ] && snap+=("$p"); done
rss=0 pss=0 shclean=0 privclean=0 privdirty=0 archive="-"
if [ "${#snap[@]}" -gt 0 ]; then
   read -r rss pss shclean privclean privdirty < <(agg_smaps "${snap[@]}")
   grep -q '/appcds/client/.*\.jsa' "/proc/${snap[0]}/maps" 2>/dev/null && archive="mapped" || archive="absent"
fi

# Persist the row, then reprint the accumulated table.
[ -n "$reset" ] && rm -f "$store"
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
   "$label" "${#snap[@]}" "$peak" "$rss" "$pss" "$shclean" "$privclean" "$privdirty" "$archive" >> "$store"

echo "Sampled '$label' pids=[${pids[*]}] (live ${#snap[@]}) over ${settle}s; appended to $store"
echo
hdr='%-12s %4s %9s %9s %9s %10s %10s %10s  %-7s\n'
printf "$hdr" MODE "#Up" "PeakRSS" "Rss" "Pss" "SharedCln" "PrivClean" "PrivDirty" "ARCHIVE"
printf "$hdr" ""   ""    "(MB)"    "(MB)" "(MB)" "(MB)"      "(MB)"      "(MB)"      ""
while IFS=$'\t' read -r l n pk r p s pc pd a; do
   [ -z "$l" ] && continue
   printf "$hdr" "$l" "$n" "$(mb "$pk")" "$(mb "$r")" "$(mb "$p")" "$(mb "$s")" "$(mb "$pc")" "$(mb "$pd")" "$a"
done < "$store"

echo
echo "Notes:"
echo " - Rows accumulate across runs (server bounces between configs). Use --reset on"
echo "   the FIRST run of a fresh comparison to clear old rows."
echo " - ARCHIVE=mapped confirms the .jsa is actually in use for that webClient."
echo " - No ToSettle here: the process was already running, so startup time can't be"
echo "   measured after the fact (use appcds_mem.sh for the command-line client to get that)."