#!/usr/bin/env bash
#
# bench-ltp-xlsa: reliable (RED) LTP over the generic xlsa link service
# with its shared-memory backend (xlso/xlsi over a POSIX /dev/shm ring).
#
# This is a configuration demonstration and a delivery smoke test: two
# nodes (engine ids 2 and 3) on one host exchange a moderate, fixed
# number of bundles over the shm ring, and delivery is confirmed within a
# bounded wait.  It is NOT a throughput benchmark -- shared-memory
# loopback goodput is not representative of any real link.

set -u

KB="000"; MB="${KB}000"; GB="${MB}000"

#----------------------------------------------------------------------
# Tunables
#----------------------------------------------------------------------
BASE_RATE=2${GB}                          # matched contact/link rate, bytes/sec
SENDER_DIR=2.bench.xlsa
RECEIVER_DIR=3.bench.xlsa

BUNDLES=10                                # moderate, fixed exchange
WAIT_SECS=60                              # bounded delivery wait

# Scale ring depth so each per-direction shm segment stays around
# 64 MB.  Each slot's payload is XLSA_BUFSZ from ltp/xlsa/xlsa.h
# (currently 1 MB), NOT the LTP segment size -- a smaller LTP segment
# doesn't shrink the in-ring buffer.  Track XLSA_BUFSZ here when it
# changes.
XLSA_SLOT_BYTES=1048576

XLSA_SLOTS_DEFAULT=$((67108864 / XLSA_SLOT_BYTES))   # 64 by default

# Constrained-shm fallback: many container defaults cap /dev/shm at
# 64 MB.  Two xlsa rings AND ION's own sem files (one per engine
# semaphore, ~40 of them) all live in the tmpfs, so target each ring
# at a quarter of the available space.  In a 64 MB shm that gives
# 16 slots * 1 MB * 2 rings = 32 MB used, 32 MB free for ION.
# No-op on hosts with a normal-sized /dev/shm or where shm is opaque
# (macOS).
if [ -d /dev/shm ]; then
    shm_kb=$(df -k /dev/shm 2>/dev/null | awk 'NR==2 {print $4}')
    if [ -n "$shm_kb" ]; then
        max_per_ring=$((shm_kb * 1024 / 4 / XLSA_SLOT_BYTES))
        if [ "$max_per_ring" -gt 0 ] && \
           [ "$max_per_ring" -lt "$XLSA_SLOTS_DEFAULT" ]; then
            XLSA_SLOTS_DEFAULT=$max_per_ring
        fi
    fi
fi

[ "$XLSA_SLOTS_DEFAULT" -lt 8 ]    && XLSA_SLOTS_DEFAULT=8
[ "$XLSA_SLOTS_DEFAULT" -gt 1024 ] && XLSA_SLOTS_DEFAULT=1024
XLSA_SLOTS_VALUE=${XLSA_SLOTS:-$XLSA_SLOTS_DEFAULT}

RETVAL=0

#----------------------------------------------------------------------
# Regenerate global.ionrc with the contact rate -- this is the only way
# to set ION's contact-plan rate, since ionadmin bakes it into the SDR
# at start.  The contact rate MUST match XLSA_RATE_BPS (see
# ltp/xlsa/doc/DESIGN.md §3.5).
#----------------------------------------------------------------------
write_global_ionrc() {
    cat > global.ionrc <<EOF
## Contact plan for the xlsa demonstration.
##
## Two nodes (engine ids 2 and 3) on the same host, exchanging bundles
## over LTP carried by the generic xlsa link service (xlso/xlsi) with the
## shared-memory backend.
##
## IMPORTANT: the contact rate set here MUST match XLSA_RATE_BPS exported
## in dotest.  See ltp/xlsa/doc/DESIGN.md §3.5 for why all three rates
## (BP Throttle nominalRate, LTP localXmitRate, XLSA_RATE_BPS) must agree.

m horizon  +0
a range    +0 +600        2 3   1
a contact  +0 +420        2 3   $1
a contact  +0 +600        3 2   $1
EOF
}

#----------------------------------------------------------------------
# Header
#----------------------------------------------------------------------
echo "########################################################################"
echo "bench-ltp-xlsa: LTP over the xlsa shared-memory adapter"
echo "########################################################################"
echo "Date         : $(date)"
echo "Host         : $(hostname)"
echo "Uname        : $(uname -a)"
echo
echo "PURPOSE: demonstrate reliable (RED) LTP running over the generic xlsa
    link service with its shared-memory backend (xlso/xlsi over a POSIX
    /dev/shm ring) and confirm end-to-end bundle delivery.  Two nodes
    (engine ids 2 and 3) on the same host exchange a moderate, fixed
    number of bundles; node 2 sends and node 3 receives over the shm ring.
    This is a configuration demonstration and a delivery smoke test -- not
    a throughput measurement (shared-memory loopback goodput is not
    representative of any real link)."
echo

# Auto-PATH: if xlso/xlsi aren't installed, fall back to the libtool-
# wrapped copies in the source-tree root (../..).  We symlink ONLY those
# two -- prepending the whole source tree would shadow the installed
# bpsource/bpsink with their libtool-wrapped counterparts that need
# a full relink-on-first-run.
if ! command -v xlso >/dev/null 2>&1; then
    SRC_ROOT="$(cd ../.. 2>/dev/null && pwd)"
    if [ -x "${SRC_ROOT}/xlso" ] && [ -x "${SRC_ROOT}/xlsi" ]; then
        mkdir -p .xlsabin
        ln -sf "${SRC_ROOT}/xlso" .xlsabin/xlso
        ln -sf "${SRC_ROOT}/xlsi" .xlsabin/xlsi
        export PATH="${PWD}/.xlsabin:${PATH}"
        echo "Using uninstalled xlso/xlsi from ${SRC_ROOT}"
    fi
fi

./pretest-script || exit 1

#----------------------------------------------------------------------
# Bring the two nodes up over the xlsa shm ring, no impairment.
#----------------------------------------------------------------------
export XLSA_RATE_BPS=$BASE_RATE XLSA_SLOTS=$XLSA_SLOTS_VALUE
export XLSA_DELAY_US=0 XLSA_JITTER_US=0 XLSA_DROP_PPM=0

write_global_ionrc $BASE_RATE
./cleanup
sleep 1

export ION_NODE_LIST_DIR=$PWD
rm -f ./ion_nodes

(cd $RECEIVER_DIR; ./ionstart >/dev/null 2>&1)
(cd $SENDER_DIR;   ./ionstart >/dev/null 2>&1)
sleep 2

ROOT=$PWD
SENDFILE="$ROOT/$SENDER_DIR/benchsend.txt"
RECEIVEFILE="$ROOT/$RECEIVER_DIR/ionreceivefile.txt"
echo "This is a bench-ltp-xlsa demonstration bundle." > "$SENDFILE"
rm -f "$RECEIVEFILE"

echo
echo "Sending $BUNDLES bundles from node 2 to node 3 over the xlsa shm ring."

# Receiver on node 3.
cd "$ROOT/$RECEIVER_DIR"
bpsink ipn:3.1 > "$RECEIVEFILE" &
BPSINKPID=$!
cd "$ROOT"
sleep 1

# Send from node 2; RED LTP over the shm ring delivers reliably.
cd "$ROOT/$SENDER_DIR"
i=1
while [ "$i" -le "$BUNDLES" ]
do
    bpsource ipn:3.1 < "$SENDFILE" 1>/dev/null 2>/dev/null
    i=$((i + 1))
done
cd "$ROOT"

# Wait (bounded) for delivery at node 3.  RED LTP is reliable, so a
# shortfall means a real failure -- fail fast here rather than hanging
# until the harness timeout.
echo "Waiting up to ${WAIT_SECS}s for delivery at node 3..."
elapsed=0
got=0
while [ "$elapsed" -lt "$WAIT_SECS" ]
do
    got=$(grep -c "^ION event: Payload delivered\." "$RECEIVEFILE" 2>/dev/null)
    [ -z "$got" ] && got=0
    if [ "$got" -ge "$BUNDLES" ]
    then
        break
    fi
    sleep 2
    elapsed=$((elapsed + 2))
done

if [ "$got" -ge "$BUNDLES" ]
then
    echo "SUCCESS: $got of $BUNDLES bundles delivered to node 3 over xlsa shm."
    RETVAL=0
else
    echo "FAILURE: only $got of $BUNDLES bundles delivered after ${WAIT_SECS}s."
    RETVAL=1
fi

# Stop the receiver.
kill -2 "$BPSINKPID" >/dev/null 2>&1
sleep 1
kill -9 "$BPSINKPID" >/dev/null 2>&1

# Bounded teardown -- a clean cooperative shutdown takes ~10s per node;
# 30s is the upper bound that lets a normal shutdown finish but caps any
# pathological hang.  killall is a final safety net for an xlso/xlsi that
# survives both ionstop and the timeout.
echo "Stopping ION..."
timeout 30 sh -c "cd $SENDER_DIR && ./ionstop" >/dev/null 2>&1
timeout 30 sh -c "cd $RECEIVER_DIR && ./ionstop" >/dev/null 2>&1
killall -9 xlso xlsi 2>/dev/null || true
echo "...demonstration terminated."

exit $RETVAL
