Spring Boot startup time: bean-by-bean diagnosis, and one directory per post
Adds spring-boot-startup-time/, the companion project for BLOG-618: a runnable Spring Boot 4.1.1 application on JDK 25 that installs BufferingApplicationStartup and FlightRecorderApplicationStartup behind a system property, and a /diag/startup endpoint that computes step self time -- the number /actuator/startup does not give you and the one that names the actual culprits. Captured under docs/output/: the step tree sorted both ways, the same startup as JFR events, a +5000-class experiment putting 0.11 ms per scanned class on the classpath scan tax, the silent truncation a 2048-step buffer performs, and JDK 25 AOT cache timings (6.93 s to 4.82 s). Post body and metadata live in post/. Moves the existing Actuator project into actuator-in-production/ so the repository holds one directory per article; the root README is now an index.
This commit is contained in:
25
spring-boot-startup-time/scripts/demo-buffer-overflow.sh
Executable file
25
spring-boot-startup-time/scripts/demo-buffer-overflow.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# What a too-small buffer does. Every guide picks 2048; nobody says what happens when
|
||||
# an application produces more steps than that.
|
||||
set -uo pipefail
|
||||
source "$(dirname "$0")/env.sh"
|
||||
|
||||
for cap in 2048 16384; do
|
||||
"$(dirname "$0")/stop.sh"
|
||||
"$JAVA_HOME/bin/java" -Dstartup.tracking=buffering -Dstartup.buffer=$cap \
|
||||
-jar "$JAR" > /tmp/buf.log 2>&1 &
|
||||
PID=$!
|
||||
for _ in $(seq 1 240); do curl -fs -o /dev/null localhost:8080/actuator/health && break; sleep 0.5; done
|
||||
ok=$?
|
||||
echo "--- capacity $cap ---"
|
||||
if grep -q 'Started StartupDiagnosisApplication' /tmp/buf.log; then
|
||||
echo -n " started, recorded steps: "
|
||||
curl -s 'localhost:8080/diag/startup?top=1' \
|
||||
| python3 -c "import sys,json;print(json.load(sys.stdin)['recordedSteps'])"
|
||||
else
|
||||
echo " application did not start. Last lines of the log:"
|
||||
grep -E 'ERROR|Exception|Caused by' /tmp/buf.log | head -5 | sed 's/^/ /'
|
||||
fi
|
||||
kill -9 $PID 2>/dev/null; wait $PID 2>/dev/null
|
||||
done
|
||||
true
|
||||
69
spring-boot-startup-time/scripts/demo-jfr.sh
Executable file
69
spring-boot-startup-time/scripts/demo-jfr.sh
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
# Records the same startup as JFR events instead of an in-memory buffer, then reads the
|
||||
# recording with the JDK's own `jfr` tool -- no Mission Control, no extra dependency.
|
||||
set -uo pipefail
|
||||
source "$(dirname "$0")/env.sh"
|
||||
REC=/tmp/startup.jfr
|
||||
rm -f "$REC"
|
||||
|
||||
"$(dirname "$0")/stop.sh"
|
||||
"$JAVA_HOME/bin/java" \
|
||||
-XX:StartFlightRecording=filename=$REC,settings=profile,dumponexit=true \
|
||||
-Dstartup.tracking=jfr -jar "$JAR" > /tmp/startup-app.log 2>&1 &
|
||||
PID=$!
|
||||
for _ in $(seq 1 180); do curl -fs -o /dev/null localhost:8080/actuator/health && break; sleep 0.5; done
|
||||
grep -h 'Started StartupDiagnosisApplication' /tmp/startup-app.log | sed 's/^.*: //'
|
||||
kill -TERM $PID 2>/dev/null; wait $PID 2>/dev/null
|
||||
sleep 2
|
||||
|
||||
echo
|
||||
echo "=== jfr summary (Spring rows only) ==="
|
||||
"$JAVA_HOME/bin/jfr" summary "$REC" | head -3
|
||||
"$JAVA_HOME/bin/jfr" summary "$REC" | grep -i -E 'spring|Event Count|=====' | head -10
|
||||
|
||||
echo
|
||||
echo "=== the event type, in full ==="
|
||||
"$JAVA_HOME/bin/jfr" metadata "$REC" | grep -A22 '@Name("org.springframework' | head -24
|
||||
|
||||
echo
|
||||
echo "=== the selector matters: the event is named by its FQCN, not 'StartupEvent' ==="
|
||||
echo -n " --events StartupEvent -> "
|
||||
"$JAVA_HOME/bin/jfr" print --events StartupEvent --json "$REC" \
|
||||
| python3 -c "import sys,json;print(len(json.load(sys.stdin)['recording']['events']),'events')"
|
||||
echo -n " --events 'org.springframework.core.*' -> "
|
||||
"$JAVA_HOME/bin/jfr" print --events 'org.springframework.core.metrics.jfr.FlightRecorderStartupEvent' --json "$REC" \
|
||||
| python3 -c "import sys,json;print(len(json.load(sys.stdin)['recording']['events']),'events')"
|
||||
|
||||
echo
|
||||
echo "=== 8 slowest startup steps, straight out of the recording ==="
|
||||
"$JAVA_HOME/bin/jfr" print --events 'org.springframework.core.metrics.jfr.FlightRecorderStartupEvent' \
|
||||
--json "$REC" > /tmp/startup-jfr.json 2>/dev/null
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
d = json.load(open('/tmp/startup-jfr.json'))
|
||||
ev = d['recording']['events']
|
||||
print(f"{len(ev)} StartupEvent records in the recording\n")
|
||||
rows = []
|
||||
for e in ev:
|
||||
v = e['values']
|
||||
rows.append((v.get('duration', 0), v.get('name'), v.get('tags') or ''))
|
||||
rows.sort(reverse=True)
|
||||
print(f"{'duration':>14} name / tags")
|
||||
for dur, name, tags in rows[:8]:
|
||||
print(f"{dur!s:>14} {name} {tags}")
|
||||
PY
|
||||
|
||||
echo
|
||||
echo "=== what JFR gives you that the buffer does not: JVM context in the same file ==="
|
||||
"$JAVA_HOME/bin/jfr" summary "$REC" | grep -E 'GCPhasePause|ClassLoad|JavaMonitorEnter|ExecutionSample|Compilation ' | head -6
|
||||
echo
|
||||
echo -n " total GC pause time during this startup: "
|
||||
"$JAVA_HOME/bin/jfr" print --events jdk.GCPhasePause --json "$REC" | python3 -c "
|
||||
import sys, json, re
|
||||
ev = json.load(sys.stdin)['recording']['events']
|
||||
tot = 0.0
|
||||
for e in ev:
|
||||
m = re.match(r'PT(?:(\d+)M)?([\d.]+)S', e['values']['duration'])
|
||||
tot += (int(m.group(1) or 0) * 60 + float(m.group(2)))
|
||||
print(f'{len(ev)} events, {tot*1000:.1f} ms')"
|
||||
true
|
||||
71
spring-boot-startup-time/scripts/demo-scan-tax.sh
Executable file
71
spring-boot-startup-time/scripts/demo-scan-tax.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
# Quantifies what component scanning costs, by adding classes to the scanned package
|
||||
# and changing nothing else.
|
||||
#
|
||||
# baseline : the application as committed
|
||||
# +5000 plain : 5000 classes with no annotations -- pure scan cost
|
||||
# +5000 @Component : the same 5000, now bean definitions -- scan + define + instantiate
|
||||
# +5000 @Component, lazy : lazy init removes the instantiation, not the scan
|
||||
#
|
||||
# Each variant is a full rebuild. Budget about ten minutes.
|
||||
set -uo pipefail
|
||||
source "$(dirname "$0")/env.sh"
|
||||
cd "$ROOT"
|
||||
RUNS="${RUNS:-3}"
|
||||
|
||||
measure() { # measure <label> <extra java args...>
|
||||
local label="$1"; shift
|
||||
local started=() parse="" inst="" steps=""
|
||||
for i in $(seq 1 "$RUNS"); do
|
||||
"$ROOT/scripts/stop.sh"
|
||||
"$JAVA_HOME/bin/java" -Dstartup.tracking=buffering "$@" -jar "$JAR" > /tmp/scan-app.log 2>&1 &
|
||||
local pid=$!
|
||||
for _ in $(seq 1 240); do curl -fs -o /dev/null localhost:8080/actuator/health && break; sleep 0.5; done
|
||||
started+=("$(grep -ho 'in [0-9.]* seconds' /tmp/scan-app.log | head -1 | awk '{print $2}')")
|
||||
if [ "$i" = "1" ]; then
|
||||
curl -s 'localhost:8080/diag/startup?top=1' > /tmp/scan-diag.json
|
||||
read -r parse inst steps <<<"$(python3 - <<'PY'
|
||||
import json
|
||||
d = json.load(open('/tmp/scan-diag.json'))
|
||||
p = {r['step']: r for r in d['phasesBySelfTime']}
|
||||
print(p.get('spring.context.config-classes.parse', {}).get('selfMs', 0),
|
||||
p.get('spring.beans.instantiate', {}).get('selfMs', 0),
|
||||
d['recordedSteps'])
|
||||
PY
|
||||
)"
|
||||
fi
|
||||
kill -9 $pid 2>/dev/null; wait $pid 2>/dev/null
|
||||
done
|
||||
printf '%-26s | %-18s | %9s | %9s | %6s\n' \
|
||||
"$label" "$(IFS=,; echo "${started[*]}")" "$parse" "$inst" "$steps"
|
||||
}
|
||||
|
||||
hdr() {
|
||||
printf '%-26s | %-18s | %9s | %9s | %6s\n' \
|
||||
"variant" "Started in (s)" "parse ms" "instMs" "steps"
|
||||
printf -- '---------------------------+--------------------+-----------+-----------+-------\n'
|
||||
}
|
||||
|
||||
echo "runs per variant: $RUNS (first run of each also queried for phase self times)"
|
||||
echo
|
||||
hdr
|
||||
|
||||
./scripts/gen-bulk.sh 0 plain >/dev/null 2>&1 || true
|
||||
rm -rf src/main/java/com/ankurm/startup/bulk
|
||||
mvn -B -q -DskipTests package > /tmp/scan-build.log 2>&1
|
||||
measure "baseline"
|
||||
|
||||
./scripts/gen-bulk.sh 5000 plain > /dev/null
|
||||
mvn -B -q -DskipTests package > /tmp/scan-build.log 2>&1
|
||||
measure "+5000 plain classes"
|
||||
|
||||
./scripts/gen-bulk.sh 5000 component > /dev/null
|
||||
mvn -B -q -DskipTests package > /tmp/scan-build.log 2>&1
|
||||
measure "+5000 @Component"
|
||||
measure "+5000 @Component, lazy" -Dspring.profiles.active=lazy
|
||||
|
||||
rm -rf src/main/java/com/ankurm/startup/bulk
|
||||
mvn -B -q -DskipTests package > /tmp/scan-build.log 2>&1
|
||||
echo
|
||||
echo "bulk package removed; jar rebuilt at baseline."
|
||||
true
|
||||
47
spring-boot-startup-time/scripts/demo-startup-tree.sh
Executable file
47
spring-boot-startup-time/scripts/demo-startup-tree.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
# Boots with BufferingApplicationStartup, then dumps the step tree three ways:
|
||||
# self time by phase, slowest steps by self time, and the same list by total time
|
||||
# (which is what /actuator/startup would have you sort by).
|
||||
set -uo pipefail
|
||||
source "$(dirname "$0")/env.sh"
|
||||
|
||||
"$(dirname "$0")/stop.sh"
|
||||
"$JAVA_HOME/bin/java" -Dstartup.tracking=buffering -jar "$JAR" > /tmp/startup-app.log 2>&1 &
|
||||
PID=$!
|
||||
for _ in $(seq 1 180); do curl -fs -o /dev/null localhost:8080/actuator/health && break; sleep 0.5; done
|
||||
|
||||
echo "=== Boot's own startup line ==="
|
||||
grep -h 'Started StartupDiagnosisApplication' /tmp/startup-app.log | sed 's/^.*: //'
|
||||
echo
|
||||
echo "=== /diag/startup ==="
|
||||
curl -s 'localhost:8080/diag/startup?top=12' > /tmp/diag.json
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
d = json.load(open('/tmp/diag.json'))
|
||||
print(f"recorded steps: {d['recordedSteps']}\n")
|
||||
print("-- self time by step name (self = duration minus direct children) --")
|
||||
print(f"{'self ms':>9} {'count':>5} step")
|
||||
for r in d['phasesBySelfTime']:
|
||||
print(f"{r['selfMs']:9.2f} {r['count']:5d} {r['step']}")
|
||||
def bean(t): return t.get('beanName') or t.get('classNames') or ''
|
||||
print("\n-- slowest individual steps by SELF time (the culprits) --")
|
||||
print(f"{'self ms':>9} {'total ms':>9} step / tags")
|
||||
for r in d['slowestBySelfTime']:
|
||||
print(f"{r['selfMs']:9.2f} {r['totalMs']:9.2f} {r['name']} {bean(r['tags'])}")
|
||||
print("\n-- slowest individual steps by TOTAL time (the containers) --")
|
||||
print(f"{'total ms':>9} {'self ms':>9} step / tags")
|
||||
for r in d['slowestByTotalTime']:
|
||||
print(f"{r['totalMs']:9.2f} {r['selfMs']:9.2f} {r['name']} {bean(r['tags'])}")
|
||||
PY
|
||||
|
||||
echo
|
||||
echo "=== actuator's own endpoint: GET peeks, POST drains ==="
|
||||
for verb in GET POST POST GET; do
|
||||
n=$(curl -s -X $verb localhost:8080/actuator/startup \
|
||||
| python3 -c "import sys,json;print(len(json.load(sys.stdin).get('timeline',{}).get('events',[])))" 2>/dev/null || echo "-")
|
||||
echo " $verb /actuator/startup -> events in response: $n"
|
||||
done
|
||||
|
||||
kill -9 $PID 2>/dev/null
|
||||
wait $PID 2>/dev/null
|
||||
true
|
||||
16
spring-boot-startup-time/scripts/demo-versions.sh
Executable file
16
spring-boot-startup-time/scripts/demo-versions.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# Resolved versions, from the build rather than from the documentation.
|
||||
set -uo pipefail
|
||||
source "$(dirname "$0")/env.sh"
|
||||
cd "$ROOT"
|
||||
echo "=== java -version ==="
|
||||
"$JAVA_HOME/bin/java" -version 2>&1 | grep -v 'JAVA_TOOL_OPTIONS\|Picked up'
|
||||
echo
|
||||
echo "=== mvn -version ==="
|
||||
mvn -version 2>&1 | head -1
|
||||
echo
|
||||
echo "=== mvn dependency:list (selected) ==="
|
||||
mvn -B dependency:list -DoutputFile=/dev/stdout -DincludeScope=runtime 2>/dev/null \
|
||||
| grep -E 'spring-boot:|spring-core|spring-context|spring-beans|spring-data-jpa|hibernate-core|micrometer-core|tomcat-embed-core|h2:' \
|
||||
| sed 's/^\[INFO\] *//' | sort -u
|
||||
true
|
||||
66
spring-boot-startup-time/scripts/demo-what-helps.sh
Executable file
66
spring-boot-startup-time/scripts/demo-what-helps.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
# The four things people reach for, measured on the same jar.
|
||||
#
|
||||
# no tracking : baseline, no ApplicationStartup installed
|
||||
# buffering : BufferingApplicationStartup(16384) -- what it costs to measure
|
||||
# jfr : FlightRecorderApplicationStartup + an active recording
|
||||
# lazy : spring.main.lazy-initialization=true
|
||||
# AOT cache : JDK 25 ahead-of-time cache (JEP 483/515), trained on this app
|
||||
set -uo pipefail
|
||||
source "$(dirname "$0")/env.sh"
|
||||
RUNS="${RUNS:-4}"
|
||||
|
||||
run_variant() { # run_variant <label> <extra args...>
|
||||
local label="$1"; shift
|
||||
local out=()
|
||||
for _ in $(seq 1 "$RUNS"); do
|
||||
"$ROOT/scripts/stop.sh"
|
||||
"$JAVA_HOME/bin/java" "$@" -jar "$JAR" > /tmp/helps.log 2>&1 &
|
||||
local pid=$!
|
||||
for _ in $(seq 1 240); do curl -fs -o /dev/null localhost:8080/actuator/health && break; sleep 0.5; done
|
||||
out+=("$(grep -ho 'in [0-9.]* seconds' /tmp/helps.log | head -1 | awk '{print $2}')")
|
||||
kill -9 $pid 2>/dev/null; wait $pid 2>/dev/null
|
||||
done
|
||||
# median of the runs
|
||||
local med
|
||||
med=$(printf '%s\n' "${out[@]}" | sort -n | awk '{a[NR]=$1} END {print (NR%2)? a[(NR+1)/2] : (a[NR/2]+a[NR/2+1])/2}')
|
||||
printf '%-34s | %-26s | %8s\n' "$label" "$(IFS=,; echo "${out[*]}")" "$med"
|
||||
}
|
||||
|
||||
printf '%-34s | %-26s | %8s\n' "variant" "Started in (s), each run" "median"
|
||||
printf -- '-----------------------------------+----------------------------+---------\n'
|
||||
|
||||
run_variant "no tracking" -Dstartup.tracking=none
|
||||
run_variant "BufferingApplicationStartup" -Dstartup.tracking=buffering
|
||||
run_variant "FlightRecorder + recording" -Dstartup.tracking=jfr \
|
||||
-XX:StartFlightRecording=filename=/tmp/helps.jfr,settings=profile,dumponexit=true
|
||||
run_variant "lazy-initialization" -Dstartup.tracking=none -Dspring.profiles.active=lazy
|
||||
|
||||
echo
|
||||
echo "=== JDK 25 AOT cache (Project Leyden) ==="
|
||||
"$JAVA_HOME/bin/java" -version 2>&1 | head -1
|
||||
rm -f /tmp/app.aotconf /tmp/app.aot
|
||||
|
||||
echo "-- training run (-XX:AOTMode=record) --"
|
||||
"$JAVA_HOME/bin/java" -XX:AOTMode=record -XX:AOTConfiguration=/tmp/app.aotconf \
|
||||
-Dstartup.tracking=none -jar "$JAR" > /tmp/aot-train.log 2>&1 &
|
||||
PID=$!
|
||||
for _ in $(seq 1 240); do curl -fs -o /dev/null localhost:8080/actuator/health && break; sleep 0.5; done
|
||||
curl -fs -o /dev/null localhost:8080/orders/summary || true
|
||||
kill -TERM $PID 2>/dev/null; wait $PID 2>/dev/null
|
||||
sleep 2
|
||||
ls -la /tmp/app.aotconf 2>/dev/null || { echo "no AOT configuration produced:"; tail -5 /tmp/aot-train.log; }
|
||||
|
||||
echo "-- assembly run (-XX:AOTMode=create) --"
|
||||
"$JAVA_HOME/bin/java" -XX:AOTMode=create -XX:AOTConfiguration=/tmp/app.aotconf \
|
||||
-XX:AOTCache=/tmp/app.aot -jar "$JAR" > /tmp/aot-create.log 2>&1
|
||||
tail -4 /tmp/aot-create.log
|
||||
ls -la /tmp/app.aot 2>/dev/null || echo "no cache produced"
|
||||
|
||||
echo
|
||||
if [ -f /tmp/app.aot ]; then
|
||||
run_variant "AOT cache (-XX:AOTCache)" -XX:AOTCache=/tmp/app.aot -Dstartup.tracking=none
|
||||
run_variant "AOT cache + lazy" -XX:AOTCache=/tmp/app.aot -Dstartup.tracking=none -Dspring.profiles.active=lazy
|
||||
fi
|
||||
"$ROOT/scripts/stop.sh"
|
||||
true
|
||||
7
spring-boot-startup-time/scripts/env.sh
Executable file
7
spring-boot-startup-time/scripts/env.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
# Shared environment. Override JAVA_HOME to point at your own JDK 25.
|
||||
: "${JAVA_HOME:?set JAVA_HOME to a JDK 25 installation}"
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
JAR="$ROOT/target/startup-diagnosis-1.0.0.jar"
|
||||
OUT="$ROOT/docs/output"
|
||||
MAIN=com.ankurm.startup.StartupDiagnosisApplication
|
||||
22
spring-boot-startup-time/scripts/gen-bulk.sh
Executable file
22
spring-boot-startup-time/scripts/gen-bulk.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
# gen-bulk.sh <count> <plain|component>
|
||||
# Writes <count> classes into the package that @SpringBootApplication already scans.
|
||||
#
|
||||
# 'plain' classes carry no stereotype annotation at all. They are still opened, read and
|
||||
# have their annotation metadata parsed by the scanner -- which is the point: it separates
|
||||
# the cost of *scanning* from the cost of *creating beans*.
|
||||
set -euo pipefail
|
||||
COUNT="${1:-5000}"
|
||||
KIND="${2:-plain}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DIR="$ROOT/src/main/java/com/ankurm/startup/bulk"
|
||||
|
||||
rm -rf "$DIR"; mkdir -p "$DIR"
|
||||
for i in $(seq 1 "$COUNT"); do
|
||||
if [ "$KIND" = "component" ]; then
|
||||
printf 'package com.ankurm.startup.bulk;\nimport org.springframework.stereotype.Component;\n@Component\npublic class Bulk%d { public int id() { return %d; } }\n' "$i" "$i" > "$DIR/Bulk$i.java"
|
||||
else
|
||||
printf 'package com.ankurm.startup.bulk;\npublic class Bulk%d { public int id() { return %d; } }\n' "$i" "$i" > "$DIR/Bulk$i.java"
|
||||
fi
|
||||
done
|
||||
echo "generated $COUNT $KIND classes in src/main/java/com/ankurm/startup/bulk"
|
||||
24
spring-boot-startup-time/scripts/measure-startup.sh
Executable file
24
spring-boot-startup-time/scripts/measure-startup.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# measure-startup.sh <runs> [-- extra java args...]
|
||||
# Starts the jar, waits for the "Started ... in Ns" line, records it, stops, repeats.
|
||||
set -uo pipefail
|
||||
source "$(dirname "$0")/env.sh"
|
||||
|
||||
RUNS="${1:-5}"; shift || true
|
||||
[ "${1:-}" = "--" ] && shift
|
||||
|
||||
for i in $(seq 1 "$RUNS"); do
|
||||
LOG=$(mktemp)
|
||||
"$JAVA_HOME/bin/java" "$@" -jar "$JAR" > "$LOG" 2>&1 &
|
||||
PID=$!
|
||||
for _ in $(seq 1 120); do
|
||||
grep -q 'Started StartupDiagnosisApplication' "$LOG" && break
|
||||
kill -0 $PID 2>/dev/null || break
|
||||
sleep 0.25
|
||||
done
|
||||
grep -h 'Started StartupDiagnosisApplication' "$LOG" | sed 's/.*Started/Started/'
|
||||
kill -9 $PID 2>/dev/null || true
|
||||
wait $PID 2>/dev/null
|
||||
rm -f "$LOG"
|
||||
sleep 1
|
||||
done
|
||||
24
spring-boot-startup-time/scripts/run-all.sh
Executable file
24
spring-boot-startup-time/scripts/run-all.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regenerate every file in docs/output/. Budget ~20 minutes; the scan-tax demo rebuilds
|
||||
# the project four times and the AOT demo builds a 118 MB cache.
|
||||
set -uo pipefail
|
||||
source "$(dirname "$0")/env.sh"
|
||||
cd "$ROOT"
|
||||
|
||||
mvn -B -q -DskipTests package || exit 1
|
||||
|
||||
./scripts/demo-versions.sh > docs/output/00-versions.txt 2>&1
|
||||
# 01-api-corrections.txt is javap and compiler output, kept by hand; see docs/02.
|
||||
./scripts/demo-startup-tree.sh > docs/output/02-startup-tree.txt 2>&1
|
||||
./scripts/demo-jfr.sh > docs/output/03-jfr.txt 2>&1
|
||||
RUNS=3 ./scripts/demo-scan-tax.sh > docs/output/04-scan-tax.txt 2>&1
|
||||
RUNS=4 ./scripts/demo-what-helps.sh > docs/output/05-what-helps.txt 2>&1
|
||||
|
||||
./scripts/gen-bulk.sh 5000 component > /dev/null
|
||||
mvn -B -q -DskipTests package || exit 1
|
||||
./scripts/demo-buffer-overflow.sh > docs/output/06-buffer-overflow.txt 2>&1
|
||||
rm -rf src/main/java/com/ankurm/startup/bulk
|
||||
mvn -B -q -DskipTests package
|
||||
|
||||
./scripts/stop.sh
|
||||
echo "docs/output regenerated."
|
||||
17
spring-boot-startup-time/scripts/run.sh
Executable file
17
spring-boot-startup-time/scripts/run.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# run.sh [profiles] [tracking] e.g. ./scripts/run.sh lazy buffering
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/env.sh"
|
||||
"$(dirname "$0")/stop.sh"
|
||||
|
||||
PROFILES="${1:-}"
|
||||
TRACKING="${2:-buffering}"
|
||||
ARGS=(-Dstartup.tracking="$TRACKING")
|
||||
[ -n "$PROFILES" ] && ARGS+=(-Dspring.profiles.active="$PROFILES")
|
||||
|
||||
setsid nohup "$JAVA_HOME/bin/java" "${ARGS[@]}" -jar "$JAR" > /tmp/startup-app.log 2>&1 < /dev/null &
|
||||
for _ in $(seq 1 120); do
|
||||
curl -fs -o /dev/null http://localhost:8080/actuator/health && break
|
||||
sleep 0.5
|
||||
done
|
||||
echo "up: profiles='${PROFILES}' tracking='${TRACKING}' (log: /tmp/startup-app.log)"
|
||||
10
spring-boot-startup-time/scripts/stop.sh
Executable file
10
spring-boot-startup-time/scripts/stop.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stop the demo application.
|
||||
#
|
||||
# Do NOT match on the main class name with `pkill -f`. Any shell whose command line
|
||||
# happens to contain that string -- including the one that wrote this script -- matches
|
||||
# too, and gets killed. Match on the executable being java AND the jar name instead.
|
||||
for p in $(ps -eo pid=,comm=,args= | awk '$2 == "java" && /startup-diagnosis-1\.0\.0\.jar/ { print $1 }'); do
|
||||
kill -9 "$p" 2>/dev/null || true
|
||||
done
|
||||
sleep 1
|
||||
Reference in New Issue
Block a user