mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-07 08:43:35 +08:00
The Windows DACL stamps in run-tests-parallel.sh, soak-test.sh and
memlab.sh pass the current user to icacls by bare name. Git Bash resolves
whoami to coreutils, which prints an unqualified name, and icacls resolves
an unqualified name against the machine before the user. On a host whose
name equals the user's (COMPUTERNAME=BUILD with user build) the grant
lands on an empty principal:
BUILD\:(OI)(CI)(F)
Combined with the /inheritance:r on the same invocation, the directory
ends up granting no usable account. run-tests-parallel.sh then cannot
write its own log directory and the wave dies with:
build/c/test-logs/results.txt: Permission denied
FAIL: test-runner --list-suites exited nonzero
Prefix the name with USERDOMAIN when it is set, which makes the account
unambiguous, and fall back to the bare name otherwise. SYSTEM and
Administrators in the same invocations already use SID form and are
unaffected.
Signed-off-by: PETRO YAKOVYSHYN <petroyakovyshyn@gmail.com>
147 lines
6.0 KiB
Bash
147 lines
6.0 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# memlab.sh — deterministic memory-attribution run for #581.
|
|
#
|
|
# The soak answers "did memory grow over eight minutes"; this answers "which
|
|
# allocation sites retained it", in about a minute, with a fixed request count
|
|
# so two runs are directly comparable. Everything that made soak numbers hard
|
|
# to compare — wall-clock duration, background reindexes, crash-recovery
|
|
# phases, throughput differences between builds — is deliberately absent.
|
|
#
|
|
# Usage: scripts/memlab.sh <binary> [requests] [label]
|
|
#
|
|
# Output: memlab-<label>.jsonl (profiler records, one block per sample)
|
|
# memlab-<label>.log (daemon log with mem.census lines)
|
|
# Analyse with: scripts/memlab-report.py memlab-<label>.jsonl --census memlab-<label>.log
|
|
set -u
|
|
|
|
BINARY="${1:?usage: memlab.sh <binary> [requests] [label]}"
|
|
REQUESTS="${2:-200}"
|
|
LABEL="${3:-$(uname -s | tr '[:upper:]' '[:lower:]')}"
|
|
|
|
if [ ! -x "$BINARY" ]; then
|
|
echo "FAIL: $BINARY is not executable" >&2
|
|
exit 2
|
|
fi
|
|
|
|
WORK=$(mktemp -d 2>/dev/null || mktemp -d -t memlab)
|
|
|
|
# Native Windows: the server walks the full ancestor chain of both the binary
|
|
# and the cache dir and refuses to start when any ancestor grants mutation to
|
|
# untrusted SIDs. The repo checkout and the msys /tmp tree both carry such ACEs,
|
|
# so the run has to happen from a stamped root under USERPROFILE — the same
|
|
# reason soak-test.sh and the guard suites copy their binaries there.
|
|
if [[ "$BINARY" == *.exe ]] && command -v cygpath >/dev/null 2>&1 &&
|
|
! command -v winepath >/dev/null 2>&1; then
|
|
WIN_ROOT=$(mktemp -d "$(cygpath "$USERPROFILE")/cbm-memlab.XXXXXX")
|
|
WIN_ROOT_W="$(cygpath -w "$WIN_ROOT")"
|
|
# Qualify with the domain: a bare name from coreutils `whoami` resolves
|
|
# against the machine first, so on a host whose name equals the user's the
|
|
# grant lands on an empty principal.
|
|
ME="$(whoami | tr -d '\r')"
|
|
if [ -n "${USERDOMAIN:-}" ]; then
|
|
ME="${USERDOMAIN}\\${ME}"
|
|
fi
|
|
MSYS2_ARG_CONV_EXCL='*' icacls "$WIN_ROOT_W" /reset /Q >/dev/null 2>&1 || true
|
|
if ! MSYS2_ARG_CONV_EXCL='*' icacls "$WIN_ROOT_W" /inheritance:r \
|
|
/grant:r "${ME}:(OI)(CI)F" '*S-1-5-18:(OI)(CI)F' '*S-1-5-32-544:(OI)(CI)F' \
|
|
/Q >/dev/null 2>&1; then
|
|
echo "FAIL: could not stamp $WIN_ROOT_W" >&2
|
|
exit 1
|
|
fi
|
|
cp "$BINARY" "$WIN_ROOT/codebase-memory-mcp.exe"
|
|
BINARY="$WIN_ROOT/codebase-memory-mcp.exe"
|
|
WORK="$WIN_ROOT"
|
|
MSYS2_ARG_CONV_EXCL='*' icacls "${WIN_ROOT_W}\\*" /reset /T /C /Q >/dev/null 2>&1 || true
|
|
fi
|
|
PROFILE_OUT="$PWD/memlab-${LABEL}.jsonl"
|
|
RUN_LOG="$PWD/memlab-${LABEL}.log"
|
|
rm -f "$PROFILE_OUT" "$RUN_LOG"
|
|
|
|
cleanup() { rm -rf "$WORK" 2>/dev/null || true; rm -rf "${WIN_ROOT:-}" 2>/dev/null || true; }
|
|
trap cleanup EXIT
|
|
|
|
# A fixed corpus: same file count and content on every platform, so a
|
|
# difference in the output cannot come from a difference in the input.
|
|
CORPUS="$WORK/corpus"
|
|
mkdir -p "$CORPUS/src"
|
|
for i in $(seq 1 60); do
|
|
cat > "$CORPUS/src/module_$i.py" <<EOF
|
|
class Widget$i:
|
|
"""Fixed corpus file $i."""
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.parts = []
|
|
|
|
def attach(self, part):
|
|
self.parts.append(part)
|
|
return self
|
|
|
|
def render(self):
|
|
return "-".join(str(p) for p in self.parts)
|
|
|
|
def build_$i(count):
|
|
widget = Widget$i("w$i")
|
|
for index in range(count):
|
|
widget.attach(index)
|
|
return widget.render()
|
|
EOF
|
|
done
|
|
|
|
echo "=== memlab: binary=$BINARY requests=$REQUESTS label=$LABEL ==="
|
|
echo "corpus: $(find "$CORPUS" -name '*.py' | wc -l | tr -d ' ') files"
|
|
|
|
# The Windows binary needs a native path here; an msys /c/... path is not one.
|
|
if command -v cygpath >/dev/null 2>&1 && ! command -v winepath >/dev/null 2>&1; then
|
|
mkdir -p "$WORK/cache"
|
|
export CBM_CACHE_DIR="$(cygpath -w "$WORK/cache")"
|
|
else
|
|
export CBM_CACHE_DIR="$WORK/cache"
|
|
fi
|
|
export CBM_MEM_PROFILE=1
|
|
export CBM_MEM_PROFILE_OUT="$PROFILE_OUT"
|
|
export CBM_MEM_CENSUS=1
|
|
export CBM_LOG_LEVEL=info
|
|
export CBM_LOG_FORMAT=text
|
|
mkdir -p "$CBM_CACHE_DIR"
|
|
|
|
# Drive the MCP stdio path via the request/response driver (see its header for
|
|
# why batching into a closed stdin does not work).
|
|
# The VM's python3 is a NATIVE win32 build, so it cannot open msys paths like
|
|
# /c/Users/... . Hand it Windows paths, or it starts nothing and the failure
|
|
# looks like the server closing stdout.
|
|
DRIVE_BINARY="$BINARY"
|
|
DRIVE_CORPUS="$CORPUS"
|
|
DRIVE_STDERR="$WORK/server-stderr.log"
|
|
if command -v cygpath >/dev/null 2>&1 && ! command -v winepath >/dev/null 2>&1; then
|
|
DRIVE_BINARY="$(cygpath -w "$BINARY")"
|
|
DRIVE_CORPUS="$(cygpath -w "$CORPUS")"
|
|
DRIVE_STDERR="$(cygpath -w "$WORK/server-stderr.log")"
|
|
fi
|
|
python3 "$(dirname "$0")/memlab-drive.py" "$DRIVE_BINARY" "$DRIVE_CORPUS" "$REQUESTS" --tool "${MEMLAB_TOOL:-search_graph}" --idle-seconds "${MEMLAB_IDLE:-0}" --stderr "$DRIVE_STDERR" > "$WORK/drive.out" 2>&1
|
|
RC=$?
|
|
# With CBM_CACHE_DIR set the process logs to its own file rather than stderr,
|
|
# so fold that in or the census series is invisible.
|
|
cat "$WORK"/cache/logs/*.log >> "$RUN_LOG" 2>/dev/null || true
|
|
cat "$WORK/server-stderr.log" >> "$RUN_LOG" 2>/dev/null || true
|
|
RESPONSES=$(sed -n "s/.*served=\\([0-9]*\\).*/\\1/p" "$WORK/drive.out" | head -1); RESPONSES=${RESPONSES:-0}
|
|
CENSUS=$(grep -c "mem.census" "$RUN_LOG" 2>/dev/null | head -1); CENSUS=${CENSUS:-0}
|
|
SITES=$(grep -c '"site"' "$PROFILE_OUT" 2>/dev/null | head -1); SITES=${SITES:-0}
|
|
|
|
echo "exit=$RC responses=$RESPONSES census_samples=$CENSUS profile_records=$SITES"
|
|
if [ "$RESPONSES" -eq 0 ]; then
|
|
# A harness that hides why it failed is worse than no harness.
|
|
echo "--- driver output ---" >&2
|
|
cat "$WORK/drive.out" 2>/dev/null | tail -15 >&2
|
|
fi
|
|
if [ "$CENSUS" -eq 0 ]; then
|
|
echo "WARN: no census samples — check CBM_MEM_CENSUS wiring" >&2
|
|
fi
|
|
if [ "$SITES" -eq 0 ]; then
|
|
# Not fatal, but never silent: on macOS there is no --wrap, so the
|
|
# profiler legitimately has no observation point.
|
|
echo "WARN: no profiler records — expected on macOS (no --wrap); a gap anywhere else" >&2
|
|
fi
|
|
echo "profile: $PROFILE_OUT"
|
|
echo "log: $RUN_LOG"
|