Development: Check bean instantiations on startup
#28
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Check Bean Instantiations on Startup | |
| on: | |
| push: | |
| branches: [ develop ] | |
| paths: | |
| - src/main/java/** | |
| pull_request: | |
| # TODO revert this before merging | |
| # paths: | |
| # - src/main/java/** | |
| jobs: | |
| build: | |
| name: Check Bean Instantiations on Startup | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out code | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '21' | |
| - name: Build application | |
| run: ./gradlew clean bootJar -x test -x webapp | |
| - name: Start Spring Boot app | |
| run: | | |
| PROFILES=dev,localci,lti,aeolus,theia,iris,localvc,artemis,scheduling,buildagent,core,ldap | |
| JAR=$(ls build/libs/Artemis*.jar | head -n1) | |
| nohup java -jar $JAR \ | |
| --spring.profiles.active=$PROFILES \ | |
| --artemis.user-management.passkey.enabled=true \ | |
| --artemis.user-management.use-external=false \ | |
| --artemis.iris.url=http://iris.fake \ | |
| --artemis.iris.secret-token=token \ | |
| --spring.liquibase.enabled=false \ | |
| --spring.datasource.url="jdbc:h2:mem:mydb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE" \ | |
| --spring.datasource.username=sa \ | |
| --spring.datasource.password= \ | |
| --eureka.client.enabled=false \ | |
| --aeolus.url=http://aeolus.fake \ | |
| > app.log 2>&1 & | |
| echo $! > app.pid | |
| - name: Wait for the application to start | |
| run: | | |
| # Configuration variables | |
| RUNNING_MESSAGE="'Artemis' is running!" | |
| STARTUP_TIMEOUT_ATTEMPTS=30 | |
| STARTUP_RETRY_INTERVAL=2 | |
| STARTUP_TOTAL_TIMEOUT=$((STARTUP_TIMEOUT_ATTEMPTS * STARTUP_RETRY_INTERVAL)) | |
| LOG_FILE="app.log" | |
| echo "Waiting up to ${STARTUP_TOTAL_TIMEOUT}s for $RUNNING_MESSAGE to appear in $LOG_FILE..." | |
| isAppRunning=false | |
| for i in $(seq 1 $STARTUP_TIMEOUT_ATTEMPTS); do | |
| if grep -q "$RUNNING_MESSAGE" "$LOG_FILE"; then | |
| isAppRunning=true | |
| echo "✅ Found $RUNNING_MESSAGE in $LOG_FILE after $i attempts" | |
| break | |
| fi | |
| echo " attempt $i/$STARTUP_TIMEOUT_ATTEMPTS: not found yet" | |
| sleep $STARTUP_RETRY_INTERVAL | |
| done | |
| if [ "$isAppRunning" = false ]; then | |
| echo "❌ Timeout: $RUNNING_MESSAGE not found in $LOG_FILE" | |
| cat $LOG_FILE | |
| exit 1 | |
| fi | |
| - name: Extract and validate startup bean instantiation metrics | |
| run: | | |
| STARTUP_LOG_PATTERN="Bean instantiation graph exported to startupBeans\.dot \(([0-9]+) edges, longest dependency chain length: ([0-9]+)\)" | |
| MIN_INSTANTIATED_BEANS=20 | |
| MAX_INSTANTIATED_BEANS=94 | |
| MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH=9 | |
| LOG_FILE="app.log" | |
| # Extract search text from pattern (everything before opening parenthesis) | |
| STARTUP_SEARCH=$(echo "$STARTUP_LOG_PATTERN" | sed 's/ \\(.*$//') | |
| LINE=$(grep -E "$STARTUP_SEARCH" $LOG_FILE) \ | |
| || { echo "❌ No startup metrics line found"; cat $LOG_FILE; exit 1; } | |
| if [[ "$LINE" =~ $STARTUP_LOG_PATTERN ]]; then | |
| INSTANTIATED_BEANS=${BASH_REMATCH[1]} | |
| LONGEST_CHAIN_LENGTH=${BASH_REMATCH[2]} | |
| else | |
| echo "❌ Failed to parse startup metrics from: $LINE" | |
| exit 1 | |
| fi | |
| echo "• Number of instantiated beans = $INSTANTIATED_BEANS" | |
| echo "• Longest dependency chain length = $LONGEST_CHAIN_LENGTH" | |
| echo "Validating against thresholds: MIN_INSTANTIATED_BEANS=$MIN_INSTANTIATED_BEANS (expected ≥ $MIN_INSTANTIATED_BEANS), MAX_INSTANTIATED_BEANS=$MAX_INSTANTIATED_BEANS (expected ≤ $MAX_INSTANTIATED_BEANS), MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH=$MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH (expected ≤ $MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH)" | |
| if (( INSTANTIATED_BEANS < MIN_INSTANTIATED_BEANS )); then | |
| echo "❌ $INSTANTIATED_BEANS < $MIN_INSTANTIATED_BEANS beans. Something seems to be wrong, as usually more beans are instantiated." | |
| exit 1 | |
| fi | |
| if (( INSTANTIATED_BEANS > MAX_INSTANTIATED_BEANS )); then | |
| echo "❌ $INSTANTIATED_BEANS > $MAX_INSTANTIATED_BEANS threshold" | |
| exit 1 | |
| fi | |
| if (( LONGEST_CHAIN_LENGTH > MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH )); then | |
| echo "❌ Longest dependency chain length $LONGEST_CHAIN_LENGTH > $MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH threshold" | |
| exit 1 | |
| fi | |
| echo "✅ Final values → Beans: $INSTANTIATED_BEANS; Longest dependency chain length: $LONGEST_CHAIN_LENGTH" | |
| echo "🎉 Startup bean instantiation metrics within expected ranges" | |
| # Export variables for next step | |
| echo "LONGEST_CHAIN_LENGTH=$LONGEST_CHAIN_LENGTH" >> $GITHUB_ENV | |
| - name: Check for startup dependency chains exceeding threshold | |
| run: | | |
| STARTUP_DEEP_CHAIN_PATTERN="Startup long bean instantiation chain" | |
| MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH=9 | |
| LOG_FILE="app.log" | |
| echo "Checking for startup dependency chains exceeding threshold..." | |
| if (( LONGEST_CHAIN_LENGTH > MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH )); then | |
| echo "❌ Startup dependency chain length $LONGEST_CHAIN_LENGTH > $MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH threshold" | |
| echo "Chains exceeding threshold:" | |
| grep -E "$STARTUP_DEEP_CHAIN_PATTERN" $LOG_FILE || echo " (No detailed chains found in log)" | |
| echo "🔧 These chains violate the threshold MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH. Please refactor them to break these chains." | |
| exit 1 | |
| else | |
| echo "✅ No startup dependency chains exceed the threshold of $MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH" | |
| fi | |
| - name: Extract and validate deferred eager bean chain length | |
| run: | | |
| DEFERRED_BEAN_PATTERN="Maximum dependency chain length during deferred eager init: ([0-9]+)" | |
| DEFERRED_TIMEOUT_ATTEMPTS=10 | |
| DEFERRED_RETRY_INTERVAL=3 | |
| DEFERRED_TOTAL_TIMEOUT=$((DEFERRED_TIMEOUT_ATTEMPTS * DEFERRED_RETRY_INTERVAL)) | |
| MIN_DEFERRED_CHAIN_LENGTH=1 | |
| MAX_DEFERRED_CHAIN_LENGTH=15 | |
| LOG_FILE="app.log" | |
| LOG_TAIL_LINES=50 | |
| # Extract search text from pattern (everything before :) | |
| DEFERRED_BEAN_SEARCH=$(echo "$DEFERRED_BEAN_PATTERN" | sed 's/:.*$//') | |
| echo "Waiting for deferred eager bean initialization to complete..." | |
| DEFERRED_LINE="" | |
| for i in $(seq 1 $DEFERRED_TIMEOUT_ATTEMPTS); do | |
| DEFERRED_LINE=$(grep -E "$DEFERRED_BEAN_SEARCH" $LOG_FILE 2>/dev/null || true) | |
| if [ -n "$DEFERRED_LINE" ]; then | |
| echo "✅ Found deferred eager bean initialization log after $i attempts" | |
| break | |
| fi | |
| echo " attempt $i/$DEFERRED_TIMEOUT_ATTEMPTS: deferred eager bean initialization not completed yet" | |
| sleep $DEFERRED_RETRY_INTERVAL | |
| done | |
| if [ -z "$DEFERRED_LINE" ]; then | |
| echo "❌ No deferred eager bean chain length line found after ${DEFERRED_TOTAL_TIMEOUT}s" | |
| echo "Last $LOG_TAIL_LINES lines of $LOG_FILE:" | |
| cat $LOG_FILE | |
| exit 1 | |
| fi | |
| if [[ "$DEFERRED_LINE" =~ $DEFERRED_BEAN_PATTERN ]]; then | |
| DEFERRED_CHAIN_LENGTH=${BASH_REMATCH[1]} | |
| else | |
| echo "❌ Failed to parse deferred eager bean chain length from: $DEFERRED_LINE" | |
| exit 1 | |
| fi | |
| echo "• Maximum dependency chain length during deferred eager bean initialization = $DEFERRED_CHAIN_LENGTH" | |
| echo "Validating against thresholds: MIN_DEFERRED_CHAIN_LENGTH=$MIN_DEFERRED_CHAIN_LENGTH (expected ≥ $MIN_DEFERRED_CHAIN_LENGTH), MAX_DEFERRED_CHAIN_LENGTH=$MAX_DEFERRED_CHAIN_LENGTH (expected ≤ $MAX_DEFERRED_CHAIN_LENGTH)" | |
| if (( DEFERRED_CHAIN_LENGTH < MIN_DEFERRED_CHAIN_LENGTH )); then | |
| echo "❌ Deferred chain length $DEFERRED_CHAIN_LENGTH < $MIN_DEFERRED_CHAIN_LENGTH. Something seems to be wrong, as usually some deferred beans are instantiated." | |
| exit 1 | |
| fi | |
| if (( DEFERRED_CHAIN_LENGTH > MAX_DEFERRED_CHAIN_LENGTH )); then | |
| echo "❌ Deferred chain length $DEFERRED_CHAIN_LENGTH > $MAX_DEFERRED_CHAIN_LENGTH threshold" | |
| exit 1 | |
| fi | |
| echo "✅ Deferred eager bean chain length: $DEFERRED_CHAIN_LENGTH" | |
| echo "🎉 Deferred eager bean dependency chain length within expected ranges" | |
| # Export variable for next step | |
| echo "DEFERRED_CHAIN_LENGTH=$DEFERRED_CHAIN_LENGTH" >> $GITHUB_ENV | |
| - name: Check for deferred dependency chains exceeding threshold | |
| run: | | |
| # Configuration variables | |
| DEFERRED_DEEP_CHAIN_PATTERN="Deferred long bean instantiation chain" | |
| MAX_DEFERRED_DEPENDENCY_CHAIN_LENGTH=15 | |
| LOG_FILE="app.log" | |
| echo "Checking for deferred dependency chains exceeding threshold..." | |
| if (( DEFERRED_CHAIN_LENGTH > MAX_DEFERRED_DEPENDENCY_CHAIN_LENGTH )); then | |
| echo "❌ Deferred dependency chain length $DEFERRED_CHAIN_LENGTH > $MAX_DEFERRED_DEPENDENCY_CHAIN_LENGTH threshold" | |
| echo "Chains exceeding threshold:" | |
| grep -E "$DEFERRED_DEEP_CHAIN_PATTERN" $LOG_FILE || echo " (No detailed chains found in log)" | |
| echo "🔧 These chains violate the threshold MAX_DEFERRED_DEPENDENCY_CHAIN_LENGTH. Please refactor them to break these chains." | |
| exit 1 | |
| else | |
| echo "✅ No deferred dependency chains exceed the threshold of $MAX_DEFERRED_DEPENDENCY_CHAIN_LENGTH" | |
| fi | |
| - name: Stop application | |
| if: always() | |
| run: kill $(<app.pid) || true |