208 lines
5.7 KiB
Bash
Executable File
208 lines
5.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
|
|
MODE=${1:-compose}
|
|
OUTPUT=${2:-}
|
|
RPC_EXPRESSION='
|
|
IO.puts(
|
|
JSON.encode!(%{
|
|
node: to_string(node()),
|
|
port_count: :erlang.system_info(:port_count),
|
|
port_limit: :erlang.system_info(:port_limit),
|
|
process_count: :erlang.system_info(:process_count),
|
|
memory_total: :erlang.memory(:total),
|
|
memory_system: :erlang.memory(:system),
|
|
port_table:
|
|
:erlang.system_info(:allocated_areas)
|
|
|> Keyword.fetch!(:port_table)
|
|
})
|
|
)
|
|
'
|
|
|
|
temporary=$(mktemp "${TMPDIR:-/tmp}/wnh-beam-runtime.XXXXXX")
|
|
trap 'rm -f "$temporary"' EXIT HUP INT TERM
|
|
|
|
record_probe() {
|
|
local target=$1
|
|
local component=$2
|
|
local expected=$3
|
|
local runtime_json=$4
|
|
local cgroup_memory=$5
|
|
local rss_kb=$6
|
|
|
|
jq -cn \
|
|
--arg target "$target" \
|
|
--arg component "$component" \
|
|
--argjson expected_port_limit "$expected" \
|
|
--argjson cgroup_memory "$cgroup_memory" \
|
|
--argjson rss_kb "$rss_kb" \
|
|
--argjson runtime "$runtime_json" \
|
|
'{
|
|
target: $target,
|
|
component: $component,
|
|
expected_port_limit: $expected_port_limit,
|
|
cgroup_memory: $cgroup_memory,
|
|
rss_kb: $rss_kb
|
|
} + $runtime' >>"$temporary"
|
|
}
|
|
|
|
case "$MODE" in
|
|
compose)
|
|
mapfile -t targets < <(
|
|
docker compose -p "${COMPOSE_PROJECT_NAME:-who_need_help}" \
|
|
ps -q web worker |
|
|
sort -u
|
|
)
|
|
|
|
if [[ "${#targets[@]}" -eq 0 ]]; then
|
|
echo "No running Compose web or worker replicas were found." >&2
|
|
exit 1
|
|
fi
|
|
|
|
live_flags=$(
|
|
for target in "${targets[@]}"; do
|
|
docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' \
|
|
"$target" |
|
|
sed -n 's/^ERL_ZFLAGS=//p'
|
|
done |
|
|
sort -u
|
|
)
|
|
|
|
if [[ ! "$live_flags" =~ ^\+Q[[:space:]]+([0-9]+)$ ]]; then
|
|
echo "The live Compose replicas do not expose one unambiguous +Q limit." >&2
|
|
exit 1
|
|
fi
|
|
|
|
expected=${BASH_REMATCH[1]}
|
|
|
|
for target in "${targets[@]}"; do
|
|
component=$(
|
|
docker inspect --format \
|
|
'{{index .Config.Labels "com.docker.compose.service"}}' "$target"
|
|
)
|
|
runtime_json=$(
|
|
docker exec "$target" /app/bin/who_need_help rpc "$RPC_EXPRESSION" |
|
|
tail -n 1
|
|
)
|
|
cgroup_memory=$(docker exec "$target" cat /sys/fs/cgroup/memory.current)
|
|
rss_kb=$(
|
|
docker exec "$target" awk "/^VmRSS:/ {print \$2}" /proc/1/status
|
|
)
|
|
record_probe \
|
|
"$target" "$component" "$expected" "$runtime_json" \
|
|
"$cgroup_memory" "$rss_kb"
|
|
done
|
|
;;
|
|
|
|
kind)
|
|
KUBECTL="$ROOT/.tools/bin/kubectl"
|
|
CONTEXT=kind-who-need-help
|
|
NAMESPACE=who-need-help
|
|
|
|
if [[ ! -x "$KUBECTL" ]]; then
|
|
echo "The project-owned kubectl is missing." >&2
|
|
exit 1
|
|
fi
|
|
|
|
live_flags=$(
|
|
"$KUBECTL" --context "$CONTEXT" --namespace "$NAMESPACE" \
|
|
get deployment \
|
|
who-need-help-who-need-help-web \
|
|
who-need-help-who-need-help-worker \
|
|
-o json |
|
|
jq -r '
|
|
[
|
|
.items[].spec.template.spec.containers[].env[]
|
|
| select(.name == "ERL_ZFLAGS")
|
|
| .value
|
|
]
|
|
| unique
|
|
| if length == 1 then .[0] else empty end
|
|
'
|
|
)
|
|
|
|
if [[ ! "$live_flags" =~ (^|[[:space:]])\+Q[[:space:]]+([0-9]+)($|[[:space:]]) ]]; then
|
|
echo "The live deployments do not expose one unambiguous +Q limit." >&2
|
|
exit 1
|
|
fi
|
|
|
|
expected=${BASH_REMATCH[2]}
|
|
mapfile -t pods < <(
|
|
"$KUBECTL" --context "$CONTEXT" --namespace "$NAMESPACE" \
|
|
get pods \
|
|
-l app.kubernetes.io/name=who-need-help \
|
|
-o json |
|
|
jq -r '
|
|
.items[]
|
|
| .metadata.labels["app.kubernetes.io/component"] as $component
|
|
| select(
|
|
($component == "web" or $component == "worker") and
|
|
.status.phase == "Running" and
|
|
([.status.containerStatuses[]?.ready] | all)
|
|
)
|
|
| [.metadata.name, $component]
|
|
| @tsv
|
|
'
|
|
)
|
|
|
|
desired=$(
|
|
"$KUBECTL" --context "$CONTEXT" --namespace "$NAMESPACE" \
|
|
get deployment \
|
|
who-need-help-who-need-help-web \
|
|
who-need-help-who-need-help-worker \
|
|
-o json |
|
|
jq '[.items[].spec.replicas] | add'
|
|
)
|
|
|
|
if [[ "${#pods[@]}" -ne "$desired" ]]; then
|
|
echo "Expected $desired Ready application pods; observed ${#pods[@]}." >&2
|
|
exit 1
|
|
fi
|
|
|
|
for entry in "${pods[@]}"; do
|
|
IFS=$'\t' read -r pod component <<<"$entry"
|
|
runtime_json=$(
|
|
"$KUBECTL" --context "$CONTEXT" --namespace "$NAMESPACE" \
|
|
exec "$pod" --container "$component" -- \
|
|
/app/bin/who_need_help rpc "$RPC_EXPRESSION" |
|
|
tail -n 1
|
|
)
|
|
cgroup_memory=$(
|
|
"$KUBECTL" --context "$CONTEXT" --namespace "$NAMESPACE" \
|
|
exec "$pod" --container "$component" -- \
|
|
cat /sys/fs/cgroup/memory.current
|
|
)
|
|
rss_kb=$(
|
|
"$KUBECTL" --context "$CONTEXT" --namespace "$NAMESPACE" \
|
|
exec "$pod" --container "$component" -- \
|
|
awk "/^VmRSS:/ {print \$2}" /proc/1/status
|
|
)
|
|
record_probe \
|
|
"$pod" "$component" "$expected" "$runtime_json" \
|
|
"$cgroup_memory" "$rss_kb"
|
|
done
|
|
;;
|
|
|
|
*)
|
|
echo "Usage: $0 [compose|kind] [output.json]" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
summary=$(jq -s 'sort_by(.component, .target)' "$temporary")
|
|
|
|
if ! printf '%s\n' "$summary" |
|
|
jq -e \
|
|
--argjson expected "$expected" \
|
|
'length > 0 and all(.port_limit == $expected)' >/dev/null; then
|
|
echo "A running BEAM instance does not use the configured port limit." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "$OUTPUT" ]]; then
|
|
printf '%s\n' "$summary" >"$OUTPUT"
|
|
fi
|
|
|
|
printf '%s\n' "$summary"
|