diff --git a/.cursor/rules/112-java-maven-documentation.mdc b/.cursor/rules/112-java-maven-documentation.mdc index 9a0ce07a..d7a14f0e 100644 --- a/.cursor/rules/112-java-maven-documentation.mdc +++ b/.cursor/rules/112-java-maven-documentation.mdc @@ -28,7 +28,10 @@ When creating a README-DEV.md file for a Maven project, include ONLY the followi ./mvnw dependency:resolve ./mvnw clean validate -U +./mvnw buildplan:list-plugin ./mvnw buildplan:list-phase +./mvnw help:all-profiles +./mvnw help:active-profiles ./mvnw license:third-party-report # Clean the project diff --git a/.cursor/rules/templates/java-checklist-template.md b/.cursor/rules/templates/java-checklist-template.md index 432f129a..1f9e1a58 100644 --- a/.cursor/rules/templates/java-checklist-template.md +++ b/.cursor/rules/templates/java-checklist-template.md @@ -48,7 +48,7 @@ Use the following process to improve the java development in some areas if requi | Activity | Description | Prompt | Notes | |----------|------|--------|-------| -| [151-java-performance-jmeter](.cursor/rules/151-java-performance-jmeter.mdc) | Run a peformance test with Jmeter | `Add JMeter performance testing to this project using @151-java-performance-jmeter.mdc` | You could ask the model to create a JMeter based on a RestController/Resource | +| [151-java-performance-jmeter](.cursor/rules/151-java-performance-jmeter.mdc) | Run a peformance test with Jmeter | `Add JMeter performance testing to this project using @151-java-performance-jmeter.mdc` | You could ask the model to create a JMeter based on a RestController/Resource. Example: `Can you create a Jmeter file based on the restcontroller in the path src/test/resources/jmeter/load-test.jmx?` | ### Step 7: Profiling (Async profiler) diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..edbec052 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.java linguist-detectable=true diff --git a/.github/workflows/maven.yaml b/.github/workflows/maven.yaml index 1a1f1d92..67c9d397 100644 --- a/.github/workflows/maven.yaml +++ b/.github/workflows/maven.yaml @@ -18,17 +18,6 @@ jobs: run: cd examples/maven-demo && ./mvnw --batch-mode --no-transfer-progress verify --file pom.xml - name: Spring Boot build run: cd examples/spring-boot-demo/implementation && ./mvnw --batch-mode --no-transfer-progress verify -Pjacoco --file pom.xml - - name: Spring Boot startup and API test - run: | - cd examples/spring-boot-demo/implementation - - # Make sure the test script is executable - chmod +x test-api.sh - - # Run the comprehensive API test script - # Uses 'local' profile and default port 8080 - # Set CLEANUP_LOGS=true to remove log files after execution - CLEANUP_LOGS=true ./test-api.sh local 8080 - name: AWS Lambda build run: cd examples/aws-lambda-hello-world && ./mvnw --batch-mode --no-transfer-progress package --file pom.xml - name: Azure Function build diff --git a/README.md b/README.md index 6ba3b909..16c0af75 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Using the Cursor rules is straightforward: simply `drag and drop` the cursor rul | Activity | Description | Prompt | Notes | |----------|------|--------|-------| -| [151-java-performance-jmeter](.cursor/rules/151-java-performance-jmeter.mdc) | Run a peformance test with Jmeter | `Add JMeter performance testing to this project using @151-java-performance-jmeter.mdc` | You could ask the model to create a JMeter based on a RestController/Resource | +| [151-java-performance-jmeter](.cursor/rules/151-java-performance-jmeter.mdc) | Run a peformance test with Jmeter | `Add JMeter performance testing to this project using @151-java-performance-jmeter.mdc` | You could ask the model to create a JMeter based on a RestController/Resource. Example: `Can you create a Jmeter file based on the restcontroller in the path src/test/resources/jmeter/load-test.jmx?` | ### Profiling rules (Async profiler) diff --git a/examples/spring-boot-demo/implementation/JAVA-DEVELOPMENT-GUIDE.md b/examples/spring-boot-demo/implementation/JAVA-DEVELOPMENT-GUIDE.md deleted file mode 100644 index 1194e27c..00000000 --- a/examples/spring-boot-demo/implementation/JAVA-DEVELOPMENT-GUIDE.md +++ /dev/null @@ -1,92 +0,0 @@ -# Java Development Guide - -Use the following process to improve the java development in some areas if required using the following set of Java Cursor Rules. - -## Process Overview - -### Step 1: Review the build system (Maven) - -| Activity | Done | Prompt | Notes | -|----------|------|--------|-------| -| Review your pom.xml and Maven project | [ ] | `Help me to review the pom.xml following the best practices for dependency management and directory structure use the cursor rule @110-java-maven-best-practices` | Add in the context the `pom.xml` which you want to generate the documentation | -| Improve the Maven project with plugins & dependencies | [ ] | `Can you improve the pom.xml using the cursor rule @111-java-maven-deps-and-plugins` | Add in the context the `pom.xml` which you want to generate the documentation. Conversational approach | -| Create documentation about Maven`s usage | [ ] | `Generate developer documentation with essential Maven commands using @112-java-maven-documentation` | Add in the context the `pom.xml` which you want to generate the documentation | - -### Step 2: Design Principles - -| Activity | Done | Prompt | Notes | -|----------|------|--------|-------| -| Object-Oriented Design Review | [ ] | `Review my code for object-oriented design using the cursor rule @121-java-object-oriented-design` | | -| Type Design Review | [ ] | `Help me improve my type design using the cursor rule @122-java-type-design` | | - -### Step 3: Coding Guidelines - -| Activity | Done | Prompt | Notes | -|----------|------|--------|-------| -| General Java Guidelines | [ ] | `Review my code for general Java best practices using the cursor rule @123-java-general-guidelines` | | -| Secure Coding Review | [ ] | `Check my code for security issues using the cursor rule @124-java-secure-coding` | | -| Concurrency Review | [ ] | `Review my code for concurrency best practices using the cursor rule @125-java-concurrency` | | -| Logging Best Practices | [ ] | `Help me improve logging using the cursor rule @126-java-logging` | | - -### Step 4: Testing - -| Activity | Done | Prompt | Notes | -|----------|------|--------|-------| -| Unit Testing | [ ] | `Can improve the unit tests using the cursor rule @131-java-unit-testing` | | -| Integration Testing | [ ] | `Help me write integration tests using the cursor rule @312-frameworks-spring-boot-integration-testing` | If required | - -### Step 5: Refactoring - -| Activity | Done | Prompt | Notes | -|----------|------|--------|-------| -| Add Modern Java Features | [ ] | `Refactor my code to use modern Java features using the cursor rule @141-java-refactoring-with-modern-features` | | -| Functional Programming | [ ] | `Refactor my code to use functional programming using the cursor rule @142-java-functional-programming` | | -| Data Oriented Programming | [ ] | `Refactor my code to use data oriented programming using the cursor rule @143-java-data-oriented-programming` | | - -### Step 6: Database - -| Activity | Done | Prompt | Notes | -|----------|------|--------|-------| -| SQL Guidelines | [ ] | `Review my SQL code using the cursor rule @500-sql` | | - -## Reference Table: Java Cursor Rules - -| Rule Name | Cursor Rule | Description | -|-----------|-------------|-------------| -| Maven Best Practices | @110-java-maven-best-practices | Best practices for Maven dependency management and project structure | -| Maven Dependencies & Plugins | @111-java-maven-deps-and-plugins | Improve pom.xml with recommended plugins and dependencies | -| Maven Documentation | @112-java-maven-documentation | Generate developer documentation with essential Maven commands | -| Object Oriented Design | @121-java-object-oriented-design | Object-oriented design principles and review | -| Type Design | @122-java-type-design | Best practices for type design in Java | -| General Java Guidelines | @123-java-general-guidelines | General Java coding best practices | -| Secure Java Coding | @124-java-secure-coding | Secure coding practices for Java | -| Concurrency | @125-java-concurrency | Best practices for concurrency in Java | -| Logging Guidelines | @126-java-logging | Logging best practices for Java applications | -| Unit Testing | @131-java-unit-testing | Guidelines for writing unit tests in Java | -| Integration Testing | @312-frameworks-spring-boot-integration-testing | Guidelines for writing integration tests in Java | -| Modern Java Features | @141-java-refactoring-with-modern-features | Refactoring with modern Java (Java 8+) features | -| Functional Programming | @142-java-functional-programming | Applying functional programming in Java | -| Data Oriented Programming | @143-java-data-oriented-programming | Data-oriented programming style in Java | -| SQL Guidelines | @500-sql | SQL development best practices | - -## Tips & Best Practices - -- Use the checklists above to track your progress through each phase. -- Use the provided prompts directly in Cursor or your LLM-enabled IDE for best results. -- Review each rule's documentation for detailed examples and anti-patterns. -- Regularly update your dependencies and plugins for security and performance. -- Apply secure coding and logging practices throughout your codebase. -- Use modern Java features and refactor legacy code incrementally. - -## Progress Tracking - -- [ ] Step 1: Build System (Maven) -- [ ] Step 2: Design Principles -- [ ] Step 3: Coding Guidelines -- [ ] Step 4: Testing -- [ ] Step 5: Refactoring -- [ ] Step 6: Database - ---- - -**Note:** This guide is self-contained and portable. Copy it into any Java project to get started with Cursor Rules for Java development. \ No newline at end of file diff --git a/examples/spring-boot-demo/implementation/README-TESTING.md b/examples/spring-boot-demo/implementation/README-TESTING.md deleted file mode 100644 index 64cb0fc3..00000000 --- a/examples/spring-boot-demo/implementation/README-TESTING.md +++ /dev/null @@ -1,194 +0,0 @@ -# Spring Boot API Testing Guide - -This guide explains how to use the automated testing script for the Spring Boot Film Query API. - -## Test Script Overview - -The `test-api.sh` script provides comprehensive testing of the Spring Boot application including: - -- **Application Startup**: Verifies the application starts successfully -- **Health Checks**: Tests both actuator health endpoint and API availability -- **API Functionality**: Tests the Films API with various scenarios -- **Error Handling**: Validates proper HTTP error responses -- **Performance**: Ensures API response times meet requirements -- **Cleanup**: Properly stops the application and cleans up resources - -## Usage - -### Basic Usage - -```bash -# Run with default settings (local profile, port 8080) -./test-api.sh - -# Run with specific profile and port -./test-api.sh test 8081 - -# Run with log cleanup -CLEANUP_LOGS=true ./test-api.sh -``` - -### Parameters - -- `profile` (optional): Spring Boot profile to use (default: `local`) -- `port` (optional): Port to test on (default: `8080`) - -### Environment Variables - -- `CLEANUP_LOGS=true`: Remove log files after execution - -## Test Cases - -The script runs the following test cases: - -### Test 1: Basic API Functionality -- **URL**: `GET /api/v1/films?startsWith=A` -- **Expected**: HTTP 200 with JSON response containing films -- **Validation**: Checks JSON structure and required fields - -### Test 2: Error Handling -- **URL**: `GET /api/v1/films?startsWith=123` -- **Expected**: HTTP 400 for invalid parameter -- **Validation**: Ensures proper error responses - -### Test 3: Empty Results -- **URL**: `GET /api/v1/films?startsWith=X` -- **Expected**: HTTP 200 with empty results (count=0) -- **Validation**: Verifies graceful handling of no matches - -### Test 4: Performance -- **URL**: `GET /api/v1/films?startsWith=A` -- **Expected**: Response time < 2000ms -- **Validation**: Measures and validates response time - -## Prerequisites - -The script automatically checks for required tools: - -- **curl**: Required for HTTP requests -- **jq**: Optional for JSON validation (script continues without it) -- **mvnw**: Maven wrapper must be present in the current directory - -## Local Development - -```bash -# Navigate to the implementation directory -cd examples/spring-boot-demo/implementation - -# Run the test script -./test-api.sh - -# Run with test profile -./test-api.sh test - -# Run with custom port -./test-api.sh local 8081 -``` - -## CI/CD Integration - -The script is designed to work in CI/CD environments: - -```yaml -# GitHub Actions example -- name: Spring Boot API Test - run: | - cd examples/spring-boot-demo/implementation - chmod +x test-api.sh - CLEANUP_LOGS=true ./test-api.sh local 8080 -``` - -## Output - -The script provides colored output with clear indicators: - -- 🔵 **[INFO]**: Informational messages -- 🟢 **[SUCCESS]**: Successful operations -- 🟡 **[WARNING]**: Warnings (non-fatal) -- 🔴 **[ERROR]**: Error messages - -### Success Output Example - -``` -[INFO] Spring Boot API Test Script -[INFO] Profile: local, Port: 8080 - -[SUCCESS] Prerequisites check passed -[INFO] Starting Spring Boot application with profile: local -[INFO] Application started with PID: 12345 -[SUCCESS] Application is healthy and ready! -[INFO] Starting API tests... -[SUCCESS] ✅ Test 1 Passed: HTTP 200 -[SUCCESS] ✅ Test 2 Passed: HTTP 400 -[SUCCESS] ✅ Test 3 Passed: HTTP 200 -[SUCCESS] ✅ Performance Test Passed: Request completed in 150ms - -🎉 All API tests passed successfully! -✅ Application startup: OK -✅ Health check: OK -✅ Films API: OK -✅ Error handling: OK -✅ Empty results: OK -✅ Performance: OK -``` - -## Troubleshooting - -### Common Issues - -1. **Port Already in Use** - ```bash - # Use a different port - ./test-api.sh local 8081 - ``` - -2. **Application Fails to Start** - - Check application logs in `app-test.log` - - Verify database is available (if using external database) - - Ensure all dependencies are available - -3. **Tests Fail Due to Missing Data** - - Verify test data is properly loaded - - Check database schema is correct - - Ensure application profile is correctly configured - -4. **Performance Tests Fail** - - System might be under load - - Database might be slow - - Consider adjusting performance thresholds - -### Debug Mode - -For detailed debugging, you can: - -1. Check application logs: `cat app-test.log` -2. Run individual curl commands manually -3. Use the Spring Boot actuator endpoints for diagnostics - -## Integration with Common Test Base - -The script can be used alongside the `PostgreSQLTestBase` class for consistent testing: - -```bash -# The script will automatically work with TestContainers -# when the application is configured with the test profile -./test-api.sh test -``` - -## Best Practices - -1. **Always run tests before committing code** -2. **Use the test profile for integration testing** -3. **Clean up logs in CI/CD environments** (`CLEANUP_LOGS=true`) -4. **Verify both positive and negative test cases** -5. **Monitor performance metrics** over time - -## Contributing - -When modifying the test script: - -1. Maintain backward compatibility -2. Add new test cases for new API endpoints -3. Update documentation for new parameters -4. Test in both local and CI environments -5. Follow the existing error handling patterns \ No newline at end of file diff --git a/examples/spring-boot-demo/implementation/build-native.sh b/examples/spring-boot-demo/implementation/build-native.sh deleted file mode 100755 index a3743d43..00000000 --- a/examples/spring-boot-demo/implementation/build-native.sh +++ /dev/null @@ -1,177 +0,0 @@ -#!/bin/bash - -# Spring Boot Native Compilation Build Script -# This script demonstrates the various native compilation options available - -set -e - -echo "🚀 Spring Boot Native Compilation Build Script" -echo "================================================" - -# Function to print colored output -print_step() { - echo -e "\n\033[1;34m$1\033[0m" -} - -print_success() { - echo -e "\033[1;32m✅ $1\033[0m" -} - -print_error() { - echo -e "\033[1;31m❌ $1\033[0m" -} - -# Check if GraalVM is available -check_graalvm() { - print_step "Checking GraalVM installation..." - if command -v native-image &> /dev/null; then - print_success "GraalVM native-image found: $(native-image --version | head -n1)" - else - print_error "GraalVM native-image not found. Please install GraalVM." - echo "Install GraalVM: https://www.graalvm.org/downloads/" - echo "Or use SDKMAN: sdk install java 21.0.1-graal" - exit 1 - fi -} - -# Clean previous builds -clean_build() { - print_step "Cleaning previous builds..." - ./mvnw clean - print_success "Clean completed" -} - -# Run tests first -run_tests() { - print_step "Running tests to ensure application works..." - ./mvnw test - print_success "Tests passed" -} - -# Compile native executable -compile_native() { - print_step "Compiling native executable with AOT processing..." - echo "This may take several minutes..." - - # Set SPRING_PROFILES_ACTIVE for native compilation - export SPRING_PROFILES_ACTIVE=native - - ./mvnw -Pnative clean native:compile - print_success "Native executable compiled successfully" - - if [ -f target/sakila-api ]; then - echo "📁 Native executable location: target/sakila-api" - echo "📊 Executable size: $(du -h target/sakila-api | cut -f1)" - fi -} - -# Test native executable -test_native() { - print_step "Testing native executable..." - if [ -f target/sakila-api ]; then - echo "Starting native executable for quick test..." - timeout 10s ./target/sakila-api --server.port=8081 --spring.profiles.active=native,test > /dev/null 2>&1 & - PID=$! - sleep 5 - - if kill -0 $PID 2>/dev/null; then - print_success "Native executable started successfully" - kill $PID 2>/dev/null || true - else - print_error "Native executable failed to start" - fi - else - print_error "Native executable not found" - fi -} - -# Build container image with native binary -build_container() { - print_step "Building container image with native binary..." - echo "This will create a lightweight container with the native executable..." - - ./mvnw spring-boot:build-image -Pnative - print_success "Container image built successfully" - - echo "🐳 Container image: sakila-api:0.0.1-SNAPSHOT" - echo "Run with: docker run -p 8080:8080 sakila-api:0.0.1-SNAPSHOT" -} - -# Run native tests (if available) -run_native_tests() { - print_step "Running native tests..." - ./mvnw -Pnative test - print_success "Native tests completed" -} - -# Display performance comparison -show_performance_info() { - print_step "Performance Characteristics:" - echo " -🚀 Startup Time: 10-100x faster than JVM -💾 Memory Usage: Reduced footprint (especially for small apps) -⚡ Build Time: Longer compilation (2-10 minutes) -📦 Image Size: Larger executable, but no JVM needed -🎯 Runtime Perf: Different characteristics vs JIT compilation - -📋 Commands Summary: - ./mvnw -Pnative clean native:compile # Build native executable - ./mvnw -Pnative test # Run native tests - ./mvnw spring-boot:build-image -Pnative # Build container image - ./target/sakila-api # Run native executable -" -} - -# Main execution -main() { - case "${1:-help}" in - "check") - check_graalvm - ;; - "clean") - clean_build - ;; - "test") - run_tests - ;; - "compile") - check_graalvm - clean_build - compile_native - test_native - ;; - "container") - check_graalvm - build_container - ;; - "native-test") - check_graalvm - run_native_tests - ;; - "full") - check_graalvm - clean_build - run_tests - compile_native - test_native - show_performance_info - ;; - "help"|*) - echo "Usage: $0 [command]" - echo "" - echo "Commands:" - echo " check Check GraalVM installation" - echo " clean Clean previous builds" - echo " test Run application tests" - echo " compile Compile native executable" - echo " container Build container with native image" - echo " native-test Run native-specific tests" - echo " full Complete native build process" - echo " help Show this help message" - echo "" - show_performance_info - ;; - esac -} - -main "$@" \ No newline at end of file diff --git a/examples/spring-boot-demo/implementation/load-test.sh b/examples/spring-boot-demo/implementation/load-test.sh deleted file mode 100755 index 8e07557d..00000000 --- a/examples/spring-boot-demo/implementation/load-test.sh +++ /dev/null @@ -1,257 +0,0 @@ -#!/bin/bash - -# Load Test Script for Films API -# Makes 10,000 requests to http://localhost:8080/api/v1/films?startsWith=A - -set -e - -# Configuration -URL="http://localhost:8080/api/v1/films?startsWith=A" -TOTAL_REQUESTS=10000 -CONCURRENT_REQUESTS=10 -OUTPUT_DIR="load_test_results" -RESULTS_FILE="$OUTPUT_DIR/results_$(date +%Y%m%d_%H%M%S).log" -STATS_FILE="$OUTPUT_DIR/stats_$(date +%Y%m%d_%H%M%S).txt" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Create output directory -mkdir -p "$OUTPUT_DIR" - -# Function to print colored output -print_status() { - echo -e "${BLUE}[INFO]${NC} $1" -} - -print_success() { - echo -e "${GREEN}[SUCCESS]${NC} $1" -} - -print_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" -} - -print_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -# Function to make a single request -make_request() { - local request_id=$1 - local start_time=$(date +%s.%3N) - - # Make the request and capture response - response=$(curl -s -w "HTTPSTATUS:%{http_code};TIME:%{time_total};SIZE:%{size_download}" \ - -H "Accept: application/json" \ - -H "User-Agent: LoadTest/1.0" \ - "$URL" 2>/dev/null || echo "HTTPSTATUS:000;TIME:0;SIZE:0") - - local end_time=$(date +%s.%3N) - local duration=$(echo "$end_time - $start_time" | bc -l) - - # Parse response - local http_status=$(echo "$response" | grep -oP 'HTTPSTATUS:\K\d+' || echo "000") - local response_time=$(echo "$response" | grep -oP 'TIME:\K[\d.]+' || echo "0") - local response_size=$(echo "$response" | grep -oP 'SIZE:\K\d+' || echo "0") - - # Log result - echo "$request_id,$http_status,$response_time,$response_size,$duration" >> "$RESULTS_FILE" - - # Print progress every 100 requests - if [ $((request_id % 100)) -eq 0 ]; then - print_status "Completed $request_id/$TOTAL_REQUESTS requests" - fi -} - -# Function to run requests in parallel batches -run_load_test() { - print_status "Starting load test with $TOTAL_REQUESTS requests ($CONCURRENT_REQUESTS concurrent)" - print_status "Target URL: $URL" - print_status "Results will be saved to: $RESULTS_FILE" - - # Initialize results file with header - echo "request_id,http_status,response_time,response_size,total_duration" > "$RESULTS_FILE" - - local batch_size=$((TOTAL_REQUESTS / CONCURRENT_REQUESTS)) - local remaining_requests=$TOTAL_REQUESTS - local request_counter=1 - - # Start time - local test_start_time=$(date +%s) - - while [ $remaining_requests -gt 0 ]; do - local current_batch_size=$CONCURRENT_REQUESTS - if [ $remaining_requests -lt $CONCURRENT_REQUESTS ]; then - current_batch_size=$remaining_requests - fi - - # Start batch of concurrent requests - for ((i=0; i "$data_file" - - local total_requests=$(wc -l < "$data_file") - local successful_requests=$(awk -F',' '$2 == 200 { count++ } END { print count+0 }' "$data_file") - local failed_requests=$((total_requests - successful_requests)) - local success_rate=$(echo "scale=2; $successful_requests * 100 / $total_requests" | bc -l) - - # Response time statistics (using response_time column) - local avg_response_time=$(awk -F',' '{ sum += $3; count++ } END { print sum/count }' "$data_file") - local min_response_time=$(awk -F',' 'NR==1 { min=$3 } $3 < min { min=$3 } END { print min }' "$data_file") - local max_response_time=$(awk -F',' 'NR==1 { max=$3 } $3 > max { max=$3 } END { print max }' "$data_file") - - # HTTP status code distribution - local status_distribution=$(awk -F',' '{ status[$2]++ } END { for (s in status) print s ": " status[s] }' "$data_file" | sort -n) - - # Generate statistics report - { - echo "=== LOAD TEST STATISTICS ===" - echo "Date: $(date)" - echo "Target URL: $URL" - echo "Total Requests: $total_requests" - echo "Successful Requests (200): $successful_requests" - echo "Failed Requests: $failed_requests" - echo "Success Rate: ${success_rate}%" - echo "" - echo "=== RESPONSE TIME STATISTICS ===" - echo "Average Response Time: ${avg_response_time}s" - echo "Min Response Time: ${min_response_time}s" - echo "Max Response Time: ${max_response_time}s" - echo "" - echo "=== HTTP STATUS CODE DISTRIBUTION ===" - echo "$status_distribution" - echo "" - echo "=== FILES ===" - echo "Detailed Results: $RESULTS_FILE" - echo "Statistics Report: $STATS_FILE" - } | tee "$STATS_FILE" - - # Cleanup temporary file - rm -f "$data_file" - - print_success "Statistics saved to: $STATS_FILE" -} - -# Function to check if server is running -check_server() { - print_status "Checking if server is running..." - - if curl -s --max-time 5 "$URL" > /dev/null 2>&1; then - print_success "Server is responding" - return 0 - else - print_error "Server is not responding at $URL" - print_error "Please make sure the Spring Boot application is running" - return 1 - fi -} - -# Function to show usage -show_usage() { - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Options:" - echo " -u, --url URL Target URL (default: $URL)" - echo " -n, --requests NUMBER Total number of requests (default: $TOTAL_REQUESTS)" - echo " -c, --concurrent NUMBER Concurrent requests (default: $CONCURRENT_REQUESTS)" - echo " -h, --help Show this help message" - echo "" - echo "Examples:" - echo " $0 # Run with defaults" - echo " $0 -n 5000 -c 20 # 5000 requests, 20 concurrent" - echo " $0 -u http://example.com/api # Different URL" -} - -# Parse command line arguments -while [[ $# -gt 0 ]]; do - case $1 in - -u|--url) - URL="$2" - shift 2 - ;; - -n|--requests) - TOTAL_REQUESTS="$2" - shift 2 - ;; - -c|--concurrent) - CONCURRENT_REQUESTS="$2" - shift 2 - ;; - -h|--help) - show_usage - exit 0 - ;; - *) - print_error "Unknown option: $1" - show_usage - exit 1 - ;; - esac -done - -# Main execution -main() { - echo "=== Films API Load Test ===" - echo "" - - # Check dependencies - if ! command -v curl &> /dev/null; then - print_error "curl is required but not installed" - exit 1 - fi - - if ! command -v bc &> /dev/null; then - print_error "bc is required but not installed" - exit 1 - fi - - # Check if server is running - if ! check_server; then - exit 1 - fi - - # Run the load test - run_load_test - - # Generate statistics - generate_stats - - print_success "Load test completed successfully!" - print_status "Check the results in: $OUTPUT_DIR" -} - -# Run main function -main "$@" \ No newline at end of file diff --git a/examples/spring-boot-demo/implementation/run-jmeter.sh b/examples/spring-boot-demo/implementation/run-jmeter.sh new file mode 100755 index 00000000..97c8d999 --- /dev/null +++ b/examples/spring-boot-demo/implementation/run-jmeter.sh @@ -0,0 +1,251 @@ +#!/bin/bash + +# JMeter Load Test Script +# This script runs JMeter tests and generates HTML reports + +set -e + +# Default configuration (can be overridden via environment variables) +JMETER_LOOPS=${JMETER_LOOPS:-1000} +JMETER_THREADS=${JMETER_THREADS:-1} +JMETER_RAMP_UP=${JMETER_RAMP_UP:-1} +GUI_MODE=false + +# Project paths +PROJECT_DIR=$(pwd) +TEST_PLAN="$PROJECT_DIR/src/test/resources/jmeter/load-test.jmx" +RESULTS_FILE="$PROJECT_DIR/target/jmeter-results.jtl" +REPORT_DIR="$PROJECT_DIR/target/jmeter-report" +LOG_FILE="$PROJECT_DIR/jmeter.log" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Function to show usage +show_usage() { + echo "JMeter Load Test Script" + echo "=======================" + echo "" + echo "DESCRIPTION:" + echo " This script executes JMeter load tests in non-GUI mode and generates" + echo " comprehensive HTML reports. It provides flexible configuration options" + echo " and automatically opens the results in your browser." + echo "" + echo "USAGE:" + echo " $0 [OPTIONS]" + echo "" + echo "OPTIONS:" + echo " -l, --loops LOOPS Number of loops per thread (default: $JMETER_LOOPS)" + echo " -t, --threads THREADS Number of concurrent threads (default: $JMETER_THREADS)" + echo " -r, --ramp-up SECONDS Ramp-up period in seconds (default: $JMETER_RAMP_UP)" + echo " -g, --gui Open JMeter GUI instead of running in non-GUI mode" + echo " -h, --help Show this detailed help message" + echo "" + echo "ENVIRONMENT VARIABLES:" + echo " JMETER_LOOPS Override default number of loops" + echo " JMETER_THREADS Override default number of threads" + echo " JMETER_RAMP_UP Override default ramp-up period" + echo "" + echo "EXAMPLES:" + echo " $0 # Run with defaults (1000 loops, 1 thread, 1s ramp-up)" + echo " $0 -l 500 -t 10 -r 30 # 500 loops, 10 threads, 30s ramp-up" + echo " $0 --loops 100 --threads 5 # Long form options" + echo " $0 -g # Open JMeter GUI with test plan loaded" + echo " JMETER_THREADS=5 $0 # Using environment variables" + echo "" + echo "OUTPUT FILES:" + echo " target/jmeter-results.jtl # Raw test results (JTL format)" + echo " target/jmeter-report/index.html # HTML dashboard report" + echo " jmeter.log # JMeter execution log" + echo "" + echo "REQUIREMENTS:" + echo " - JMeter must be installed and available in PATH" + echo " - Test plan must exist at: src/test/resources/jmeter/load-test.jmx" + echo "" + echo "For more information about JMeter, visit: https://jmeter.apache.org/" +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -l|--loops) + JMETER_LOOPS="$2" + shift 2 + ;; + -t|--threads) + JMETER_THREADS="$2" + shift 2 + ;; + -r|--ramp-up) + JMETER_RAMP_UP="$2" + shift 2 + ;; + -g|--gui) + GUI_MODE=true + shift + ;; + -h|--help) + show_usage + exit 0 + ;; + *) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; + esac +done + +# Function to check if JMeter is installed +check_jmeter() { + if ! command -v jmeter &> /dev/null; then + print_error "JMeter is not installed or not in PATH" + print_info "Please install JMeter and ensure it's in your PATH" + print_info "Download from: https://jmeter.apache.org/download_jmeter.cgi" + exit 1 + fi + + JMETER_VERSION=$(jmeter -v 2>&1 | head -n 1 | grep -o 'Version [0-9.]*' | cut -d' ' -f2) + print_info "Found JMeter version: $JMETER_VERSION" +} + +# Function to check if test plan exists +check_test_plan() { + if [[ ! -f "$TEST_PLAN" ]]; then + print_error "JMeter test plan not found: $TEST_PLAN" + exit 1 + fi + print_info "Using test plan: $TEST_PLAN" +} + +# Function to create target directory +create_target_dir() { + mkdir -p "$(dirname "$RESULTS_FILE")" + mkdir -p "$REPORT_DIR" + print_info "Created target directories" +} + +# Function to clean previous results +clean_previous_results() { + if [[ -f "$RESULTS_FILE" ]]; then + rm "$RESULTS_FILE" + print_info "Cleaned previous results file" + fi + + if [[ -d "$REPORT_DIR" ]]; then + rm -rf "$REPORT_DIR" + print_info "Cleaned previous report directory" + fi +} + +# Function to run JMeter test in non-GUI mode +run_jmeter_test() { + print_info "Starting JMeter test..." + print_info "Configuration:" + print_info " - Loops: $JMETER_LOOPS" + print_info " - Threads: $JMETER_THREADS" + print_info " - Ramp-up: $JMETER_RAMP_UP seconds" + + # Run JMeter in non-GUI mode + jmeter -n \ + -t "$TEST_PLAN" \ + -l "$RESULTS_FILE" \ + -e \ + -o "$REPORT_DIR" \ + -Jloops="$JMETER_LOOPS" \ + -Jthreads="$JMETER_THREADS" \ + -Jrampup="$JMETER_RAMP_UP" \ + -j "$LOG_FILE" + + if [[ $? -eq 0 ]]; then + print_success "JMeter test completed successfully" + else + print_error "JMeter test failed" + exit 1 + fi +} + +# Function to run JMeter in GUI mode +run_jmeter_gui() { + print_info "Opening JMeter GUI..." + print_info "Test plan will be loaded: $TEST_PLAN" + print_info "You can configure and run tests manually in the GUI" + + # Run JMeter in GUI mode with test plan loaded + jmeter -t "$TEST_PLAN" \ + -Jloops="$JMETER_LOOPS" \ + -Jthreads="$JMETER_THREADS" \ + -Jrampup="$JMETER_RAMP_UP" + + print_success "JMeter GUI session completed" +} + +# Function to show results +show_results() { + print_success "Test Results:" + print_info "Results file: $RESULTS_FILE" + print_info "HTML report: $REPORT_DIR/index.html" + print_info "Log file: $LOG_FILE" + + if [[ -f "$RESULTS_FILE" ]]; then + TOTAL_SAMPLES=$(tail -n +2 "$RESULTS_FILE" | wc -l) + print_info "Total samples: $TOTAL_SAMPLES" + fi + + # Try to open the HTML report + if command -v open &> /dev/null; then + print_info "Opening HTML report in browser..." + open "$REPORT_DIR/index.html" + elif command -v xdg-open &> /dev/null; then + print_info "Opening HTML report in browser..." + xdg-open "$REPORT_DIR/index.html" + else + print_info "To view the HTML report, open: file://$REPORT_DIR/index.html" + fi +} + +# Main execution +main() { + print_info "JMeter Load Test Script" + print_info "=======================" + + check_jmeter + check_test_plan + + if [[ "$GUI_MODE" == "true" ]]; then + # GUI mode - just open JMeter with the test plan + run_jmeter_gui + print_success "JMeter GUI session completed successfully!" + else + # Non-GUI mode - run automated test and generate report + create_target_dir + clean_previous_results + run_jmeter_test + show_results + print_success "JMeter load test completed successfully!" + fi +} + +# Execute main function +main "$@" \ No newline at end of file diff --git a/examples/spring-boot-demo/implementation/src/test/resources/jmeter/load-test.jmx b/examples/spring-boot-demo/implementation/src/test/resources/jmeter/load-test.jmx new file mode 100644 index 00000000..85950242 --- /dev/null +++ b/examples/spring-boot-demo/implementation/src/test/resources/jmeter/load-test.jmx @@ -0,0 +1,292 @@ + + + + + Simple test plan for Film Service API + false + true + false + + + + + + + + + + + localhost + 8080 + http + + + 6 + 5000 + 10000 + + + + + + + + Accept + application/json + + + Content-Type + application/json + + + + + + + + continue + + false + 1 + + 1 + 1 + false + + + + + + + + + + + + + + /api/v1/films + GET + true + false + true + false + + + + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + $.count + 51 + true + false + false + false + + + + + + + + + + false + A + = + true + startsWith + + + + + + + + /api/v1/films + GET + true + false + true + false + + + + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + $.count + 46 + true + false + false + false + + + + + + + + + + false + AB + = + true + startsWith + + + + + + + + /api/v1/films + GET + true + false + true + false + + + + + + + + 400 + + Should return 400 for invalid parameter + Assertion.response_code + true + 1 + + + + + + + + continue + + false + 5 + + 5 + 10 + false + + + + + + + + + + false + A + = + true + startsWith + + + + + + + + /api/v1/films + GET + true + false + true + false + + + + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + 2000 + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + results/test-results.jtl + + + + + \ No newline at end of file diff --git a/examples/spring-boot-memory-leak-demo/JAVA-DEVELOPMENT-GUIDE.md b/examples/spring-boot-memory-leak-demo/JAVA-DEVELOPMENT-GUIDE.md deleted file mode 100644 index 154d54f1..00000000 --- a/examples/spring-boot-memory-leak-demo/JAVA-DEVELOPMENT-GUIDE.md +++ /dev/null @@ -1,90 +0,0 @@ -# Java Development Guide - -Use the following process to improve the java development in some areas if required using the following set of Java Cursor Rules. - -## Process Overview - -### Step 1: Review the build system (Maven) - -| Activity | Done | Prompt | Notes | -|----------|------|--------|-------| -| Review your pom.xml and Maven project | [ ] | `Help me to review the pom.xml following the best practices for dependency management and directory structure use the cursor rule @110-java-maven-best-practices` | Add in the context the `pom.xml` which you want to generate the documentation | -| Improve the Maven project with plugins & dependencies | [ ] | `Can you improve the pom.xml using the cursor rule @101-java-maven-deps-and-plugins.mdc` | Add in the context the `pom.xml` which you want to generate the documentation. Conversational approach | -| Create documentation about Maven`s usage | [ ] | `Generate developer documentation with essential Maven commands using @112-java-maven-documentation.mdc` | Add in the context the `pom.xml` which you want to generate the documentation | - -### Step 2: Design Principles - -| Activity | Done | Prompt | Notes | -|----------|------|--------|-------| -| Object-Oriented Design Review | [ ] | `Review my code for object-oriented design using the cursor rule @121-java-object-oriented-design` | | -| Type Design Review | [ ] | `Help me improve my type design using the cursor rule @122-java-type-design` | | - -### Step 3: Coding Guidelines - -| Activity | Done | Prompt | Notes | -|----------|------|--------|-------| -| General Java Guidelines | [ ] | `Review my code for general Java best practices using the cursor rule @123-java-general-guidelines` | | -| Secure Coding Review | [ ] | `Check my code for security issues using the cursor rule @124-java-secure-coding` | | -| Concurrency Review | [ ] | `Review my code for concurrency best practices using the cursor rule @125-java-concurrency` | | -| Logging Best Practices | [ ] | `Help me improve logging using the cursor rule @126-java-logging` | | - -### Step 4: Testing - -| Activity | Done | Prompt | Notes | -|----------|------|--------|-------| -| Unit Testing | [ ] | `Can improve the unit tests using the cursor rule @131-java-unit-testing` | | - -### Step 5: Refactoring - -| Activity | Done | Prompt | Notes | -|----------|------|--------|-------| -| Add Modern Java Features | [ ] | `Refactor my code to use modern Java features using the cursor rule @141-java-refactoring-with-modern-features` | | -| Functional Programming | [ ] | `Refactor my code to use functional programming using the cursor rule @142-java-functional-programming` | | -| Data Oriented Programming | [ ] | `Refactor my code to use data oriented programming using the cursor rule @143-java-data-oriented-programming` | | - -### Step 6: Profiling - -| Activity | Done | Prompt | Notes | -|----------|------|--------|-------| -| Java Application Profiling | [ ] | `Help me profile my Java application using async-profiler. I want to detect running Java processes, download the profiler for my OS, and generate flamegraphs and put the profiler folder in YOUR-DEVELOPMENT/profiler with the cursor rule @151-java-profiling.mdc` | Replace YOUR-DEVELOPMENT with your actual development path | - -## Reference Table: Java Cursor Rules - -| Rule Name | Cursor Rule | Description | -|-----------|-------------|-------------| -| Maven Best Practices | @110-java-maven-best-practices | Best practices for Maven dependency management and project structure | -| Maven Dependencies & Plugins | @111-java-maven-deps-and-plugins | Improve pom.xml with recommended plugins and dependencies | -| Object Oriented Design | @121-java-object-oriented-design | Object-oriented design principles and review | -| Type Design | @122-java-type-design | Best practices for type design in Java | -| General Java Guidelines | @123-java-general-guidelines | General Java coding best practices | -| Secure Java Coding | @124-java-secure-coding | Secure coding practices for Java | -| Concurrency | @125-java-concurrency | Best practices for concurrency in Java | -| Logging Guidelines | @126-java-logging | Logging best practices for Java applications | -| Unit Testing | @131-java-unit-testing | Guidelines for writing unit tests in Java | -| Modern Java Features | @141-java-refactoring-with-modern-features | Refactoring with modern Java (Java 8+) features | -| Functional Programming | @142-java-functional-programming | Applying functional programming in Java | -| Data Oriented Programming | @143-java-data-oriented-programming | Data-oriented programming style in Java | -| Java Profiling | @151-java-profiling | Java application profiling with async-profiler v4.0 | -| SQL Guidelines | @500-sql | SQL development best practices | - -## Tips & Best Practices - -- Use the checklists above to track your progress through each phase. -- Use the provided prompts directly in Cursor or your LLM-enabled IDE for best results. -- Review each rule's documentation for detailed examples and anti-patterns. -- Regularly update your dependencies and plugins for security and performance. -- Apply secure coding and logging practices throughout your codebase. -- Use modern Java features and refactor legacy code incrementally. - -## Progress Tracking - -- [ ] Step 1: Build System (Maven) -- [ ] Step 2: Design Principles -- [ ] Step 3: Coding Guidelines -- [ ] Step 4: Testing -- [ ] Step 5: Refactoring -- [ ] Step 6: Database - ---- - -**Note:** This guide is self-contained and portable. Copy it into any Java project to get started with Cursor Rules for Java development. \ No newline at end of file diff --git a/examples/spring-boot-memory-leak-demo/load-test.sh b/examples/spring-boot-memory-leak-demo/load-test.sh deleted file mode 100755 index f99bce7b..00000000 --- a/examples/spring-boot-memory-leak-demo/load-test.sh +++ /dev/null @@ -1,373 +0,0 @@ -#!/bin/bash - -# Load Test Script for Memory Leak Demo API -# Tests memory and thread leak endpoints to demonstrate performance issues - -set -e - -# Configuration -OBJECTS_URL="http://localhost:8080/api/v1/objects/create" -THREADS_URL="http://localhost:8080/api/v1/threads/create" -TOTAL_REQUESTS=1000 -CONCURRENT_REQUESTS=5 -OUTPUT_DIR="load_test_results" -RESULTS_FILE="$OUTPUT_DIR/results_$(date +%Y%m%d_%H%M%S).log" -STATS_FILE="$OUTPUT_DIR/stats_$(date +%Y%m%d_%H%M%S).txt" -ENDPOINT="objects" # Default endpoint to test (objects or threads) - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Create output directory -mkdir -p "$OUTPUT_DIR" - -# Function to print colored output -print_status() { - echo -e "${BLUE}[INFO]${NC} $1" -} - -print_success() { - echo -e "${GREEN}[SUCCESS]${NC} $1" -} - -print_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" -} - -print_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -# Function to get target URL based on endpoint -get_target_url() { - case "$ENDPOINT" in - "objects") - echo "$OBJECTS_URL" - ;; - "threads") - echo "$THREADS_URL" - ;; - *) - print_error "Invalid endpoint: $ENDPOINT. Use 'objects' or 'threads'" - exit 1 - ;; - esac -} - -# Function to make a single request -make_request() { - local request_id=$1 - local url=$2 - local start_time=$(date +%s.%3N) - - # Make the request and capture response - response=$(curl -s -w "HTTPSTATUS:%{http_code};TIME:%{time_total};SIZE:%{size_download}" \ - -H "Accept: application/json" \ - -H "User-Agent: MemoryLeakTest/1.0" \ - "$url" 2>/dev/null || echo "HTTPSTATUS:000;TIME:0;SIZE:0") - - local end_time=$(date +%s.%3N) - local duration=$(echo "$end_time - $start_time" | bc -l) - - # Parse response - local http_status=$(echo "$response" | grep -oP 'HTTPSTATUS:\K\d+' || echo "000") - local response_time=$(echo "$response" | grep -oP 'TIME:\K[\d.]+' || echo "0") - local response_size=$(echo "$response" | grep -oP 'SIZE:\K\d+' || echo "0") - - # Log result - echo "$request_id,$http_status,$response_time,$response_size,$duration,$ENDPOINT" >> "$RESULTS_FILE" - - # Print progress every 50 requests (more frequent for memory leak testing) - if [ $((request_id % 50)) -eq 0 ]; then - print_status "Completed $request_id/$TOTAL_REQUESTS requests" - if [ "$ENDPOINT" = "objects" ]; then - print_warning "Memory usage may be increasing significantly!" - elif [ "$ENDPOINT" = "threads" ]; then - print_warning "Thread count may be increasing significantly!" - fi - fi -} - -# Function to run requests in parallel batches -run_load_test() { - local url=$(get_target_url) - - print_status "Starting load test on $ENDPOINT endpoint with $TOTAL_REQUESTS requests ($CONCURRENT_REQUESTS concurrent)" - print_status "Target URL: $url" - print_status "Results will be saved to: $RESULTS_FILE" - - if [ "$ENDPOINT" = "objects" ]; then - print_warning "This test will create memory leaks by accumulating objects in memory!" - print_warning "Monitor memory usage with: jstat -gc 1s" - elif [ "$ENDPOINT" = "threads" ]; then - print_warning "This test will create thread leaks by not closing ExecutorService!" - print_warning "Monitor thread count with: jstack | grep Thread | wc -l" - fi - - # Initialize results file with header - echo "request_id,http_status,response_time,response_size,total_duration,endpoint" > "$RESULTS_FILE" - - local remaining_requests=$TOTAL_REQUESTS - local request_counter=1 - - # Start time - local test_start_time=$(date +%s) - - while [ $remaining_requests -gt 0 ]; do - local current_batch_size=$CONCURRENT_REQUESTS - if [ $remaining_requests -lt $CONCURRENT_REQUESTS ]; then - current_batch_size=$remaining_requests - fi - - # Start batch of concurrent requests - for ((i=0; i "$data_file" - - local total_requests=$(wc -l < "$data_file") - local successful_requests=$(awk -F',' '$2 == 200 { count++ } END { print count+0 }' "$data_file") - local failed_requests=$((total_requests - successful_requests)) - local success_rate=$(echo "scale=2; $successful_requests * 100 / $total_requests" | bc -l) - - # Response time statistics (using response_time column) - local avg_response_time=$(awk -F',' '{ sum += $3; count++ } END { print sum/count }' "$data_file") - local min_response_time=$(awk -F',' 'NR==1 { min=$3 } $3 < min { min=$3 } END { print min }' "$data_file") - local max_response_time=$(awk -F',' 'NR==1 { max=$3 } $3 > max { max=$3 } END { print max }' "$data_file") - - # HTTP status code distribution - local status_distribution=$(awk -F',' '{ status[$2]++ } END { for (s in status) print s ": " status[s] }' "$data_file" | sort -n) - - # Generate statistics report - { - echo "=== MEMORY LEAK DEMO LOAD TEST STATISTICS ===" - echo "Date: $(date)" - echo "Target Endpoint: $ENDPOINT" - echo "Target URL: $(get_target_url)" - echo "Total Requests: $total_requests" - echo "Successful Requests (200): $successful_requests" - echo "Failed Requests: $failed_requests" - echo "Success Rate: ${success_rate}%" - echo "" - echo "=== RESPONSE TIME STATISTICS ===" - echo "Average Response Time: ${avg_response_time}s" - echo "Min Response Time: ${min_response_time}s" - echo "Max Response Time: ${max_response_time}s" - echo "" - echo "=== HTTP STATUS CODE DISTRIBUTION ===" - echo "$status_distribution" - echo "" - echo "=== MEMORY LEAK ANALYSIS ===" - if [ "$ENDPOINT" = "objects" ]; then - echo "Endpoint tested: Objects Creation (Memory Leak)" - echo "Expected behavior: Memory usage should increase significantly" - echo "Objects created per request: 1000" - echo "Estimated memory impact: ~$(echo "$total_requests * 1000 * 50" | bc) bytes" - echo "Recommendation: Monitor with 'jstat -gc ' or 'jhsdb jmap'" - elif [ "$ENDPOINT" = "threads" ]; then - echo "Endpoint tested: Thread Creation (Thread Leak)" - echo "Expected behavior: Thread count should increase significantly" - echo "Threads created per request: 10" - echo "Estimated thread impact: ~$(echo "$total_requests * 10" | bc) threads" - echo "Recommendation: Monitor with 'jstack ' or thread dump analysis" - fi - echo "" - echo "=== FILES ===" - echo "Detailed Results: $RESULTS_FILE" - echo "Statistics Report: $STATS_FILE" - } | tee "$STATS_FILE" - - # Cleanup temporary file - rm -f "$data_file" - - print_success "Statistics saved to: $STATS_FILE" -} - -# Function to check if server is running -check_server() { - local url=$(get_target_url) - print_status "Checking if server is running..." - - if curl -s --max-time 5 "$url" > /dev/null 2>&1; then - print_success "Server is responding" - return 0 - else - print_error "Server is not responding at $url" - print_error "Please make sure the Spring Boot application is running" - print_error "Start with: ./mvnw spring-boot:run" - return 1 - fi -} - -# Function to run both endpoint tests -run_both_tests() { - print_status "Running comprehensive memory leak tests on both endpoints..." - - # Test objects endpoint - ENDPOINT="objects" - RESULTS_FILE="$OUTPUT_DIR/objects_results_$(date +%Y%m%d_%H%M%S).log" - STATS_FILE="$OUTPUT_DIR/objects_stats_$(date +%Y%m%d_%H%M%S).txt" - - print_status "=== Testing Objects Endpoint (Memory Leak) ===" - if check_server; then - run_load_test - generate_stats - fi - - print_status "Waiting 10 seconds before next test..." - sleep 10 - - # Test threads endpoint - ENDPOINT="threads" - RESULTS_FILE="$OUTPUT_DIR/threads_results_$(date +%Y%m%d_%H%M%S).log" - STATS_FILE="$OUTPUT_DIR/threads_stats_$(date +%Y%m%d_%H%M%S).txt" - - print_status "=== Testing Threads Endpoint (Thread Leak) ===" - if check_server; then - run_load_test - generate_stats - fi -} - -# Function to show usage -show_usage() { - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Memory Leak Demo Load Test - Tests endpoints that demonstrate memory and thread leaks" - echo "" - echo "Options:" - echo " -e, --endpoint ENDPOINT Endpoint to test: 'objects', 'threads', or 'both' (default: $ENDPOINT)" - echo " -n, --requests NUMBER Total number of requests (default: $TOTAL_REQUESTS)" - echo " -c, --concurrent NUMBER Concurrent requests (default: $CONCURRENT_REQUESTS)" - echo " -h, --help Show this help message" - echo "" - echo "Endpoints:" - echo " objects Test /api/v1/objects/create (memory leak)" - echo " threads Test /api/v1/threads/create (thread leak)" - echo " both Test both endpoints sequentially" - echo "" - echo "Examples:" - echo " $0 # Test objects endpoint with defaults" - echo " $0 -e threads # Test threads endpoint" - echo " $0 -e both # Test both endpoints" - echo " $0 -n 500 -c 3 # 500 requests, 3 concurrent" - echo " $0 -e objects -n 2000 -c 10 # Heavy memory leak test" - echo "" - echo "Monitoring:" - echo " Memory: jstat -gc 1s" - echo " Threads: jstack | grep Thread | wc -l" - echo " Process: jps | grep MainApplication" -} - -# Parse command line arguments -while [[ $# -gt 0 ]]; do - case $1 in - -e|--endpoint) - ENDPOINT="$2" - shift 2 - ;; - -n|--requests) - TOTAL_REQUESTS="$2" - shift 2 - ;; - -c|--concurrent) - CONCURRENT_REQUESTS="$2" - shift 2 - ;; - -h|--help) - show_usage - exit 0 - ;; - *) - print_error "Unknown option: $1" - show_usage - exit 1 - ;; - esac -done - -# Main execution -main() { - echo "=== Memory Leak Demo API Load Test ===" - echo "" - - # Check dependencies - if ! command -v curl &> /dev/null; then - print_error "curl is required but not installed" - exit 1 - fi - - if ! command -v bc &> /dev/null; then - print_error "bc is required but not installed" - exit 1 - fi - - # Handle special case for testing both endpoints - if [ "$ENDPOINT" = "both" ]; then - run_both_tests - else - # Validate endpoint - if [ "$ENDPOINT" != "objects" ] && [ "$ENDPOINT" != "threads" ]; then - print_error "Invalid endpoint: $ENDPOINT. Use 'objects', 'threads', or 'both'" - show_usage - exit 1 - fi - - # Check if server is running - if ! check_server; then - exit 1 - fi - - # Run the load test - run_load_test - - # Generate statistics - generate_stats - fi - - print_success "Memory leak demo load test completed successfully!" - print_status "Check the results in: $OUTPUT_DIR" - print_warning "Remember to restart the application to clear any accumulated leaks!" -} - -# Run main function -main "$@" \ No newline at end of file diff --git a/examples/spring-boot-memory-leak-demo/pom.xml b/examples/spring-boot-memory-leak-demo/pom.xml index a5e639f8..5708b370 100644 --- a/examples/spring-boot-memory-leak-demo/pom.xml +++ b/examples/spring-boot-memory-leak-demo/pom.xml @@ -203,85 +203,4 @@ - - - - jmeter-from-oas - - - - org.openapitools - openapi-generator-maven-plugin - ${maven-plugin-openapi-generator.version} - - - generate-jmeter-tests - generate-test-sources - - generate - - - ${jmeter.openapi.url} - jmeter - ${project.build.directory}/jmeter-tests - - info.jab.ms.tests - info.jab.ms.tests - true - true - - - - - - - - com.lazerycode.jmeter - jmeter-maven-plugin - ${maven-plugin-jmeter.version} - - - configuration - test - - configure - - - - jmeter-tests - integration-test - - jmeter - - - - jmeter-check-results - verify - - results - - - - - ${project.build.directory}/jmeter-tests - ${project.build.directory}/jmeter/results - ${project.build.directory}/jmeter/logs - - 10 - 30 - 300 - localhost - 8080 - 1 - http - - false - true - - - - - - - diff --git a/examples/spring-boot-memory-leak-demo/run-jmeter.sh b/examples/spring-boot-memory-leak-demo/run-jmeter.sh new file mode 100755 index 00000000..97c8d999 --- /dev/null +++ b/examples/spring-boot-memory-leak-demo/run-jmeter.sh @@ -0,0 +1,251 @@ +#!/bin/bash + +# JMeter Load Test Script +# This script runs JMeter tests and generates HTML reports + +set -e + +# Default configuration (can be overridden via environment variables) +JMETER_LOOPS=${JMETER_LOOPS:-1000} +JMETER_THREADS=${JMETER_THREADS:-1} +JMETER_RAMP_UP=${JMETER_RAMP_UP:-1} +GUI_MODE=false + +# Project paths +PROJECT_DIR=$(pwd) +TEST_PLAN="$PROJECT_DIR/src/test/resources/jmeter/load-test.jmx" +RESULTS_FILE="$PROJECT_DIR/target/jmeter-results.jtl" +REPORT_DIR="$PROJECT_DIR/target/jmeter-report" +LOG_FILE="$PROJECT_DIR/jmeter.log" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Function to show usage +show_usage() { + echo "JMeter Load Test Script" + echo "=======================" + echo "" + echo "DESCRIPTION:" + echo " This script executes JMeter load tests in non-GUI mode and generates" + echo " comprehensive HTML reports. It provides flexible configuration options" + echo " and automatically opens the results in your browser." + echo "" + echo "USAGE:" + echo " $0 [OPTIONS]" + echo "" + echo "OPTIONS:" + echo " -l, --loops LOOPS Number of loops per thread (default: $JMETER_LOOPS)" + echo " -t, --threads THREADS Number of concurrent threads (default: $JMETER_THREADS)" + echo " -r, --ramp-up SECONDS Ramp-up period in seconds (default: $JMETER_RAMP_UP)" + echo " -g, --gui Open JMeter GUI instead of running in non-GUI mode" + echo " -h, --help Show this detailed help message" + echo "" + echo "ENVIRONMENT VARIABLES:" + echo " JMETER_LOOPS Override default number of loops" + echo " JMETER_THREADS Override default number of threads" + echo " JMETER_RAMP_UP Override default ramp-up period" + echo "" + echo "EXAMPLES:" + echo " $0 # Run with defaults (1000 loops, 1 thread, 1s ramp-up)" + echo " $0 -l 500 -t 10 -r 30 # 500 loops, 10 threads, 30s ramp-up" + echo " $0 --loops 100 --threads 5 # Long form options" + echo " $0 -g # Open JMeter GUI with test plan loaded" + echo " JMETER_THREADS=5 $0 # Using environment variables" + echo "" + echo "OUTPUT FILES:" + echo " target/jmeter-results.jtl # Raw test results (JTL format)" + echo " target/jmeter-report/index.html # HTML dashboard report" + echo " jmeter.log # JMeter execution log" + echo "" + echo "REQUIREMENTS:" + echo " - JMeter must be installed and available in PATH" + echo " - Test plan must exist at: src/test/resources/jmeter/load-test.jmx" + echo "" + echo "For more information about JMeter, visit: https://jmeter.apache.org/" +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -l|--loops) + JMETER_LOOPS="$2" + shift 2 + ;; + -t|--threads) + JMETER_THREADS="$2" + shift 2 + ;; + -r|--ramp-up) + JMETER_RAMP_UP="$2" + shift 2 + ;; + -g|--gui) + GUI_MODE=true + shift + ;; + -h|--help) + show_usage + exit 0 + ;; + *) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; + esac +done + +# Function to check if JMeter is installed +check_jmeter() { + if ! command -v jmeter &> /dev/null; then + print_error "JMeter is not installed or not in PATH" + print_info "Please install JMeter and ensure it's in your PATH" + print_info "Download from: https://jmeter.apache.org/download_jmeter.cgi" + exit 1 + fi + + JMETER_VERSION=$(jmeter -v 2>&1 | head -n 1 | grep -o 'Version [0-9.]*' | cut -d' ' -f2) + print_info "Found JMeter version: $JMETER_VERSION" +} + +# Function to check if test plan exists +check_test_plan() { + if [[ ! -f "$TEST_PLAN" ]]; then + print_error "JMeter test plan not found: $TEST_PLAN" + exit 1 + fi + print_info "Using test plan: $TEST_PLAN" +} + +# Function to create target directory +create_target_dir() { + mkdir -p "$(dirname "$RESULTS_FILE")" + mkdir -p "$REPORT_DIR" + print_info "Created target directories" +} + +# Function to clean previous results +clean_previous_results() { + if [[ -f "$RESULTS_FILE" ]]; then + rm "$RESULTS_FILE" + print_info "Cleaned previous results file" + fi + + if [[ -d "$REPORT_DIR" ]]; then + rm -rf "$REPORT_DIR" + print_info "Cleaned previous report directory" + fi +} + +# Function to run JMeter test in non-GUI mode +run_jmeter_test() { + print_info "Starting JMeter test..." + print_info "Configuration:" + print_info " - Loops: $JMETER_LOOPS" + print_info " - Threads: $JMETER_THREADS" + print_info " - Ramp-up: $JMETER_RAMP_UP seconds" + + # Run JMeter in non-GUI mode + jmeter -n \ + -t "$TEST_PLAN" \ + -l "$RESULTS_FILE" \ + -e \ + -o "$REPORT_DIR" \ + -Jloops="$JMETER_LOOPS" \ + -Jthreads="$JMETER_THREADS" \ + -Jrampup="$JMETER_RAMP_UP" \ + -j "$LOG_FILE" + + if [[ $? -eq 0 ]]; then + print_success "JMeter test completed successfully" + else + print_error "JMeter test failed" + exit 1 + fi +} + +# Function to run JMeter in GUI mode +run_jmeter_gui() { + print_info "Opening JMeter GUI..." + print_info "Test plan will be loaded: $TEST_PLAN" + print_info "You can configure and run tests manually in the GUI" + + # Run JMeter in GUI mode with test plan loaded + jmeter -t "$TEST_PLAN" \ + -Jloops="$JMETER_LOOPS" \ + -Jthreads="$JMETER_THREADS" \ + -Jrampup="$JMETER_RAMP_UP" + + print_success "JMeter GUI session completed" +} + +# Function to show results +show_results() { + print_success "Test Results:" + print_info "Results file: $RESULTS_FILE" + print_info "HTML report: $REPORT_DIR/index.html" + print_info "Log file: $LOG_FILE" + + if [[ -f "$RESULTS_FILE" ]]; then + TOTAL_SAMPLES=$(tail -n +2 "$RESULTS_FILE" | wc -l) + print_info "Total samples: $TOTAL_SAMPLES" + fi + + # Try to open the HTML report + if command -v open &> /dev/null; then + print_info "Opening HTML report in browser..." + open "$REPORT_DIR/index.html" + elif command -v xdg-open &> /dev/null; then + print_info "Opening HTML report in browser..." + xdg-open "$REPORT_DIR/index.html" + else + print_info "To view the HTML report, open: file://$REPORT_DIR/index.html" + fi +} + +# Main execution +main() { + print_info "JMeter Load Test Script" + print_info "=======================" + + check_jmeter + check_test_plan + + if [[ "$GUI_MODE" == "true" ]]; then + # GUI mode - just open JMeter with the test plan + run_jmeter_gui + print_success "JMeter GUI session completed successfully!" + else + # Non-GUI mode - run automated test and generate report + create_target_dir + clean_previous_results + run_jmeter_test + show_results + print_success "JMeter load test completed successfully!" + fi +} + +# Execute main function +main "$@" \ No newline at end of file diff --git a/examples/spring-boot-memory-leak-demo/src/main/java/info/jab/ms/CocoController.java b/examples/spring-boot-memory-leak-demo/src/main/java/info/jab/ms/CocoController.java index f192df55..d27036b1 100644 --- a/examples/spring-boot-memory-leak-demo/src/main/java/info/jab/ms/CocoController.java +++ b/examples/spring-boot-memory-leak-demo/src/main/java/info/jab/ms/CocoController.java @@ -11,11 +11,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.scheduling.concurrent.CustomizableThreadFactory; import org.springframework.web.bind.annotation.GetMapping; -//import org.springframework.web.bind.annotation.RequestMapping; -//import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; -//@RestController -//@RequestMapping("/api/v1") +@RestController +@RequestMapping("/api/v1") public class CocoController { private static final Logger logger = LoggerFactory.getLogger(CocoController.class); diff --git a/examples/spring-boot-memory-leak-demo/src/test/resources/jmeter/load-test.jmx b/examples/spring-boot-memory-leak-demo/src/test/resources/jmeter/load-test.jmx new file mode 100644 index 00000000..ad34ed9d --- /dev/null +++ b/examples/spring-boot-memory-leak-demo/src/test/resources/jmeter/load-test.jmx @@ -0,0 +1,274 @@ + + + + + false + false + false + + + + BASE_URL + localhost + = + + + PORT + 8080 + = + + + + + + + continue + + ${__P(loops,10)} + false + + ${__P(threads,5)} + ${__P(rampup,5)} + false + false + + + + false + + + + localhost + 8080 + http + /api/v1/objects/create + GET + true + false + true + false + + 10000 + 30000 + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + Don't touch me even with a stick! + + + Assertion.response_data + false + 16 + + + + + + false + + + + localhost + 8080 + http + /api/v1/threads/create + GET + true + false + true + false + + 10000 + 30000 + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + Don't touch me even with a stick! + + + Assertion.response_data + false + 16 + + + + + + + false + + + + localhost + 8080 + http + /actuator/health + GET + true + false + true + false + + 10000 + 30000 + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + + + 100 + 500 + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + + \ No newline at end of file diff --git a/examples/spring-boot-performance-bottleneck-demo/README-DEV.md b/examples/spring-boot-performance-bottleneck-demo/README-DEV.md index c3943e90..5ff60796 100644 --- a/examples/spring-boot-performance-bottleneck-demo/README-DEV.md +++ b/examples/spring-boot-performance-bottleneck-demo/README-DEV.md @@ -37,13 +37,5 @@ http://localhost:8080/swagger-ui/index.html ./load-test.sh --help ./load-test.sh -e both -n 500000 -c 10 - - jwebserver -p 8005 -d "$(pwd)/examples/spring-boot-memory-leak-demo/profiler/results" - -My Java application has performance issues - help me set up comprehensive profiling process using @151-java-profiling-detect.mdc and use the location examples/spring-boot-memory-leak-demo/profiler - -Analyze the results located in examples/spring-boot-memory-leak-demo/profiler and use the cursor rule @152-java-profiling-analyze - -Review if the problems was solved with last refactoring using the reports located in @/results with the cursor rule 154-java-profiling-compare.mdc ``` \ No newline at end of file diff --git a/examples/spring-boot-performance-bottleneck-demo/load-test.sh b/examples/spring-boot-performance-bottleneck-demo/load-test.sh deleted file mode 100755 index 7ffb9620..00000000 --- a/examples/spring-boot-performance-bottleneck-demo/load-test.sh +++ /dev/null @@ -1,437 +0,0 @@ -#!/bin/bash - -# Load Test Script for Performance Bottleneck Demo API -# Tests inefficient search endpoints to demonstrate performance bottlenecks - -set -e - -# Configuration -BASE_URL="http://localhost:8080/api/search" -COLLEAGUES_URL="$BASE_URL/optimized/users-with-colleagues" -PERMISSIONS_URL="$BASE_URL/optimized/active-users-with-permissions" -SIMILAR_URL="$BASE_URL/optimized/similar-users" -TEAM_URL="$BASE_URL/optimized/team-formation" -TOTAL_REQUESTS=100 -CONCURRENT_REQUESTS=3 -OUTPUT_DIR="load_test_results" -RESULTS_FILE="$OUTPUT_DIR/results_$(date +%Y%m%d_%H%M%S).log" -STATS_FILE="$OUTPUT_DIR/stats_$(date +%Y%m%d_%H%M%S).txt" -ENDPOINT="colleagues" # Default endpoint to test (colleagues, permissions, similar, team, all) - -# Test parameters for different endpoints -DEPARTMENT_PARAM="Engineering" -ROLE_PARAM="developer" -KEYWORD_PARAM="john" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Create output directory -mkdir -p "$OUTPUT_DIR" - -# Function to print colored output -print_status() { - echo -e "${BLUE}[INFO]${NC} $1" -} - -print_success() { - echo -e "${GREEN}[SUCCESS]${NC} $1" -} - -print_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" -} - -print_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -# Function to get target URL and parameters based on endpoint -get_target_url_and_params() { - case "$ENDPOINT" in - "colleagues") - echo "$COLLEAGUES_URL?department=$DEPARTMENT_PARAM" - ;; - "permissions") - echo "$PERMISSIONS_URL?role=$ROLE_PARAM" - ;; - "similar") - echo "$SIMILAR_URL?keyword=$KEYWORD_PARAM" - ;; - "team") - echo "$TEAM_URL?department=$DEPARTMENT_PARAM" - ;; - *) - print_error "Invalid endpoint: $ENDPOINT. Use 'colleagues', 'permissions', 'similar', 'team', or 'all'" - exit 1 - ;; - esac -} - -# Function to get endpoint complexity description -get_endpoint_complexity() { - case "$ENDPOINT" in - "colleagues") - echo "O(n²) - Nested loops for finding users with colleagues" - ;; - "permissions") - echo "O(n²) - Cross-referencing users with permitted roles" - ;; - "similar") - echo "O(n²) - Duplicate detection with nested loops" - ;; - "team") - echo "O(n³) - Triple nested loops for team formation" - ;; - *) - echo "Unknown complexity" - ;; - esac -} - -# Function to make a single request -make_request() { - local request_id=$1 - local url=$2 - local start_time=$(date +%s.%3N) - - # Make the request and capture response - response=$(curl -s -w "HTTPSTATUS:%{http_code};TIME:%{time_total};SIZE:%{size_download}" \ - -H "Accept: application/json" \ - -H "User-Agent: PerformanceBottleneckTest/1.0" \ - "$url" 2>/dev/null || echo "HTTPSTATUS:000;TIME:0;SIZE:0") - - local end_time=$(date +%s.%3N) - local duration=$(echo "$end_time - $start_time" | bc -l) - - # Parse response - local http_status=$(echo "$response" | grep -oP 'HTTPSTATUS:\K\d+' || echo "000") - local response_time=$(echo "$response" | grep -oP 'TIME:\K[\d.]+' || echo "0") - local response_size=$(echo "$response" | grep -oP 'SIZE:\K\d+' || echo "0") - - # Log result - echo "$request_id,$http_status,$response_time,$response_size,$duration,$ENDPOINT" >> "$RESULTS_FILE" - - # Print progress every 25 requests for performance testing - if [ $((request_id % 25)) -eq 0 ]; then - print_status "Completed $request_id/$TOTAL_REQUESTS requests" - local complexity=$(get_endpoint_complexity) - print_warning "Testing $complexity - Response time may increase significantly!" - fi -} - -# Function to run requests in parallel batches -run_load_test() { - local url=$(get_target_url_and_params) - local complexity=$(get_endpoint_complexity) - - print_status "Starting performance bottleneck test on $ENDPOINT endpoint with $TOTAL_REQUESTS requests ($CONCURRENT_REQUESTS concurrent)" - print_status "Target URL: $url" - print_status "Algorithm Complexity: $complexity" - print_status "Results will be saved to: $RESULTS_FILE" - - print_warning "This test will demonstrate performance bottlenecks with inefficient algorithms!" - print_warning "Monitor CPU usage and response times - they may increase dramatically!" - - # Initialize results file with header - echo "request_id,http_status,response_time,response_size,total_duration,endpoint" > "$RESULTS_FILE" - - local remaining_requests=$TOTAL_REQUESTS - local request_counter=1 - - # Start time - local test_start_time=$(date +%s) - - while [ $remaining_requests -gt 0 ]; do - local current_batch_size=$CONCURRENT_REQUESTS - if [ $remaining_requests -lt $CONCURRENT_REQUESTS ]; then - current_batch_size=$remaining_requests - fi - - # Start batch of concurrent requests - for ((i=0; i "$data_file" - - local total_requests=$(wc -l < "$data_file") - local successful_requests=$(awk -F',' '$2 == 200 { count++ } END { print count+0 }' "$data_file") - local failed_requests=$((total_requests - successful_requests)) - local success_rate=$(echo "scale=2; $successful_requests * 100 / $total_requests" | bc -l) - - # Response time statistics (using response_time column) - local avg_response_time=$(awk -F',' '{ sum += $3; count++ } END { print sum/count }' "$data_file") - local min_response_time=$(awk -F',' 'NR==1 { min=$3 } $3 < min { min=$3 } END { print min }' "$data_file") - local max_response_time=$(awk -F',' 'NR==1 { max=$3 } $3 > max { max=$3 } END { print max }' "$data_file") - - # HTTP status code distribution - local status_distribution=$(awk -F',' '{ status[$2]++ } END { for (s in status) print s ": " status[s] }' "$data_file" | sort -n) - - # Performance degradation analysis - local first_quarter_avg=$(head -n $((total_requests/4)) "$data_file" | awk -F',' '{ sum += $3; count++ } END { print sum/count }') - local last_quarter_avg=$(tail -n $((total_requests/4)) "$data_file" | awk -F',' '{ sum += $3; count++ } END { print sum/count }') - local performance_degradation=$(echo "scale=2; ($last_quarter_avg - $first_quarter_avg) * 100 / $first_quarter_avg" | bc -l) - - local complexity=$(get_endpoint_complexity) - - # Generate statistics report - { - echo "=== PERFORMANCE BOTTLENECK DEMO LOAD TEST STATISTICS ===" - echo "Date: $(date)" - echo "Target Endpoint: $ENDPOINT" - echo "Target URL: $(get_target_url_and_params)" - echo "Algorithm Complexity: $complexity" - echo "Total Requests: $total_requests" - echo "Successful Requests (200): $successful_requests" - echo "Failed Requests: $failed_requests" - echo "Success Rate: ${success_rate}%" - echo "" - echo "=== RESPONSE TIME STATISTICS ===" - echo "Average Response Time: ${avg_response_time}s" - echo "Min Response Time: ${min_response_time}s" - echo "Max Response Time: ${max_response_time}s" - echo "First Quarter Avg: ${first_quarter_avg}s" - echo "Last Quarter Avg: ${last_quarter_avg}s" - echo "Performance Degradation: ${performance_degradation}%" - echo "" - echo "=== HTTP STATUS CODE DISTRIBUTION ===" - echo "$status_distribution" - echo "" - echo "=== PERFORMANCE BOTTLENECK ANALYSIS ===" - case "$ENDPOINT" in - "colleagues") - echo "Endpoint tested: Users with Colleagues (O(n²) Nested Loops)" - echo "Expected behavior: Response time increases quadratically with data size" - echo "Bottleneck: Nested loops for finding users with matching department" - echo "Optimization: Use HashMaps or Set data structures for O(1) lookups" - ;; - "permissions") - echo "Endpoint tested: Active Users with Permissions (O(n²) Cross-Reference)" - echo "Expected behavior: Response time increases quadratically with data size" - echo "Bottleneck: Linear search through roles for each user" - echo "Optimization: Pre-build role index or use Set for O(1) role lookup" - ;; - "similar") - echo "Endpoint tested: Similar Users (O(n²) Duplicate Detection)" - echo "Expected behavior: Response time increases quadratically with matching users" - echo "Bottleneck: Nested loops with linear duplicate checking" - echo "Optimization: Use HashSet for O(1) duplicate detection" - ;; - "team") - echo "Endpoint tested: Team Formation (O(n³) Triple Nested)" - echo "Expected behavior: Response time increases cubically with data size" - echo "Bottleneck: Triple nested loops for finding complete teams" - echo "Optimization: Group users by role and department first, then combine" - ;; - esac - echo "" - echo "=== RECOMMENDATIONS ===" - echo "1. Implement efficient data structures (HashMap, HashSet)" - echo "2. Reduce nested loops with pre-filtering and indexing" - echo "3. Use database queries with proper indexing instead of in-memory processing" - echo "4. Consider caching frequently accessed data" - echo "5. Implement pagination for large result sets" - echo "" - echo "=== FILES ===" - echo "Detailed Results: $RESULTS_FILE" - echo "Statistics Report: $STATS_FILE" - } | tee "$STATS_FILE" - - # Cleanup temporary file - rm -f "$data_file" - - print_success "Statistics saved to: $STATS_FILE" -} - -# Function to check if server is running -check_server() { - local url=$(get_target_url_and_params) - print_status "Checking if server is running..." - - if curl -s --max-time 10 "$url" > /dev/null 2>&1; then - print_success "Server is responding" - return 0 - else - print_error "Server is not responding at $url" - print_error "Please make sure the Spring Boot application is running" - print_error "Start with: ./mvnw spring-boot:run" - return 1 - fi -} - -# Function to run all endpoint tests -run_all_tests() { - print_status "Running comprehensive performance bottleneck tests on all endpoints..." - - local endpoints=("colleagues" "permissions" "similar" "team") - - for endpoint in "${endpoints[@]}"; do - ENDPOINT="$endpoint" - RESULTS_FILE="$OUTPUT_DIR/${endpoint}_results_$(date +%Y%m%d_%H%M%S).log" - STATS_FILE="$OUTPUT_DIR/${endpoint}_stats_$(date +%Y%m%d_%H%M%S).txt" - - print_status "=== Testing $endpoint Endpoint ($(get_endpoint_complexity)) ===" - if check_server; then - run_load_test - generate_stats - fi - - if [ "$endpoint" != "team" ]; then - print_status "Waiting 5 seconds before next test..." - sleep 5 - fi - done -} - -# Function to show usage -show_usage() { - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Performance Bottleneck Demo Load Test - Tests inefficient search endpoints" - echo "" - echo "Options:" - echo " -e, --endpoint ENDPOINT Endpoint to test: 'colleagues', 'permissions', 'similar', 'team', or 'all' (default: $ENDPOINT)" - echo " -n, --requests NUMBER Total number of requests (default: $TOTAL_REQUESTS)" - echo " -c, --concurrent NUMBER Concurrent requests (default: $CONCURRENT_REQUESTS)" - echo " -d, --department NAME Department parameter (default: $DEPARTMENT_PARAM)" - echo " -r, --role NAME Role parameter (default: $ROLE_PARAM)" - echo " -k, --keyword NAME Keyword parameter (default: $KEYWORD_PARAM)" - echo " -h, --help Show this help message" - echo "" - echo "Endpoints:" - echo " colleagues Test /api/search/bad/users-with-colleagues (O(n²) nested loops)" - echo " permissions Test /api/search/bad/active-users-with-permissions (O(n²) cross-reference)" - echo " similar Test /api/search/bad/similar-users (O(n²) duplicate detection)" - echo " team Test /api/search/bad/team-formation (O(n³) triple nested)" - echo " all Test all endpoints sequentially" - echo "" - echo "Examples:" - echo " $0 # Test colleagues endpoint with defaults" - echo " $0 -e team # Test team formation endpoint (O(n³))" - echo " $0 -e all # Test all endpoints" - echo " $0 -n 200 -c 5 # 200 requests, 5 concurrent" - echo " $0 -e colleagues -d 'Marketing' # Test with Marketing department" - echo "" - echo "Performance Analysis:" - echo " Response times should increase significantly due to inefficient algorithms" - echo " O(n²) endpoints: Quadratic time complexity" - echo " O(n³) endpoints: Cubic time complexity" -} - -# Parse command line arguments -while [[ $# -gt 0 ]]; do - case $1 in - -e|--endpoint) - ENDPOINT="$2" - shift 2 - ;; - -n|--requests) - TOTAL_REQUESTS="$2" - shift 2 - ;; - -c|--concurrent) - CONCURRENT_REQUESTS="$2" - shift 2 - ;; - -d|--department) - DEPARTMENT_PARAM="$2" - shift 2 - ;; - -r|--role) - ROLE_PARAM="$2" - shift 2 - ;; - -k|--keyword) - KEYWORD_PARAM="$2" - shift 2 - ;; - -h|--help) - show_usage - exit 0 - ;; - *) - print_error "Unknown option: $1" - show_usage - exit 1 - ;; - esac -done - -# Main execution -main() { - echo "=== Performance Bottleneck Demo API Load Test ===" - echo "" - - # Check dependencies - if ! command -v curl &> /dev/null; then - print_error "curl is required but not installed" - exit 1 - fi - - if ! command -v bc &> /dev/null; then - print_error "bc is required but not installed" - exit 1 - fi - - # Handle special case for testing all endpoints - if [ "$ENDPOINT" = "all" ]; then - run_all_tests - else - # Validate endpoint - if [ "$ENDPOINT" != "colleagues" ] && [ "$ENDPOINT" != "permissions" ] && [ "$ENDPOINT" != "similar" ] && [ "$ENDPOINT" != "team" ]; then - print_error "Invalid endpoint: $ENDPOINT. Use 'colleagues', 'permissions', 'similar', 'team', or 'all'" - show_usage - exit 1 - fi - - # Check if server is running - if ! check_server; then - exit 1 - fi - - # Run the load test - run_load_test - - # Generate statistics - generate_stats - fi - - print_success "Performance bottleneck demo load test completed successfully!" - print_status "Check the results in: $OUTPUT_DIR" - print_warning "Consider implementing optimizations to improve algorithm efficiency!" -} - -# Run main function -main "$@" \ No newline at end of file diff --git a/examples/spring-boot-performance-bottleneck-demo/run-jmeter.sh b/examples/spring-boot-performance-bottleneck-demo/run-jmeter.sh new file mode 100755 index 00000000..97c8d999 --- /dev/null +++ b/examples/spring-boot-performance-bottleneck-demo/run-jmeter.sh @@ -0,0 +1,251 @@ +#!/bin/bash + +# JMeter Load Test Script +# This script runs JMeter tests and generates HTML reports + +set -e + +# Default configuration (can be overridden via environment variables) +JMETER_LOOPS=${JMETER_LOOPS:-1000} +JMETER_THREADS=${JMETER_THREADS:-1} +JMETER_RAMP_UP=${JMETER_RAMP_UP:-1} +GUI_MODE=false + +# Project paths +PROJECT_DIR=$(pwd) +TEST_PLAN="$PROJECT_DIR/src/test/resources/jmeter/load-test.jmx" +RESULTS_FILE="$PROJECT_DIR/target/jmeter-results.jtl" +REPORT_DIR="$PROJECT_DIR/target/jmeter-report" +LOG_FILE="$PROJECT_DIR/jmeter.log" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Function to show usage +show_usage() { + echo "JMeter Load Test Script" + echo "=======================" + echo "" + echo "DESCRIPTION:" + echo " This script executes JMeter load tests in non-GUI mode and generates" + echo " comprehensive HTML reports. It provides flexible configuration options" + echo " and automatically opens the results in your browser." + echo "" + echo "USAGE:" + echo " $0 [OPTIONS]" + echo "" + echo "OPTIONS:" + echo " -l, --loops LOOPS Number of loops per thread (default: $JMETER_LOOPS)" + echo " -t, --threads THREADS Number of concurrent threads (default: $JMETER_THREADS)" + echo " -r, --ramp-up SECONDS Ramp-up period in seconds (default: $JMETER_RAMP_UP)" + echo " -g, --gui Open JMeter GUI instead of running in non-GUI mode" + echo " -h, --help Show this detailed help message" + echo "" + echo "ENVIRONMENT VARIABLES:" + echo " JMETER_LOOPS Override default number of loops" + echo " JMETER_THREADS Override default number of threads" + echo " JMETER_RAMP_UP Override default ramp-up period" + echo "" + echo "EXAMPLES:" + echo " $0 # Run with defaults (1000 loops, 1 thread, 1s ramp-up)" + echo " $0 -l 500 -t 10 -r 30 # 500 loops, 10 threads, 30s ramp-up" + echo " $0 --loops 100 --threads 5 # Long form options" + echo " $0 -g # Open JMeter GUI with test plan loaded" + echo " JMETER_THREADS=5 $0 # Using environment variables" + echo "" + echo "OUTPUT FILES:" + echo " target/jmeter-results.jtl # Raw test results (JTL format)" + echo " target/jmeter-report/index.html # HTML dashboard report" + echo " jmeter.log # JMeter execution log" + echo "" + echo "REQUIREMENTS:" + echo " - JMeter must be installed and available in PATH" + echo " - Test plan must exist at: src/test/resources/jmeter/load-test.jmx" + echo "" + echo "For more information about JMeter, visit: https://jmeter.apache.org/" +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -l|--loops) + JMETER_LOOPS="$2" + shift 2 + ;; + -t|--threads) + JMETER_THREADS="$2" + shift 2 + ;; + -r|--ramp-up) + JMETER_RAMP_UP="$2" + shift 2 + ;; + -g|--gui) + GUI_MODE=true + shift + ;; + -h|--help) + show_usage + exit 0 + ;; + *) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; + esac +done + +# Function to check if JMeter is installed +check_jmeter() { + if ! command -v jmeter &> /dev/null; then + print_error "JMeter is not installed or not in PATH" + print_info "Please install JMeter and ensure it's in your PATH" + print_info "Download from: https://jmeter.apache.org/download_jmeter.cgi" + exit 1 + fi + + JMETER_VERSION=$(jmeter -v 2>&1 | head -n 1 | grep -o 'Version [0-9.]*' | cut -d' ' -f2) + print_info "Found JMeter version: $JMETER_VERSION" +} + +# Function to check if test plan exists +check_test_plan() { + if [[ ! -f "$TEST_PLAN" ]]; then + print_error "JMeter test plan not found: $TEST_PLAN" + exit 1 + fi + print_info "Using test plan: $TEST_PLAN" +} + +# Function to create target directory +create_target_dir() { + mkdir -p "$(dirname "$RESULTS_FILE")" + mkdir -p "$REPORT_DIR" + print_info "Created target directories" +} + +# Function to clean previous results +clean_previous_results() { + if [[ -f "$RESULTS_FILE" ]]; then + rm "$RESULTS_FILE" + print_info "Cleaned previous results file" + fi + + if [[ -d "$REPORT_DIR" ]]; then + rm -rf "$REPORT_DIR" + print_info "Cleaned previous report directory" + fi +} + +# Function to run JMeter test in non-GUI mode +run_jmeter_test() { + print_info "Starting JMeter test..." + print_info "Configuration:" + print_info " - Loops: $JMETER_LOOPS" + print_info " - Threads: $JMETER_THREADS" + print_info " - Ramp-up: $JMETER_RAMP_UP seconds" + + # Run JMeter in non-GUI mode + jmeter -n \ + -t "$TEST_PLAN" \ + -l "$RESULTS_FILE" \ + -e \ + -o "$REPORT_DIR" \ + -Jloops="$JMETER_LOOPS" \ + -Jthreads="$JMETER_THREADS" \ + -Jrampup="$JMETER_RAMP_UP" \ + -j "$LOG_FILE" + + if [[ $? -eq 0 ]]; then + print_success "JMeter test completed successfully" + else + print_error "JMeter test failed" + exit 1 + fi +} + +# Function to run JMeter in GUI mode +run_jmeter_gui() { + print_info "Opening JMeter GUI..." + print_info "Test plan will be loaded: $TEST_PLAN" + print_info "You can configure and run tests manually in the GUI" + + # Run JMeter in GUI mode with test plan loaded + jmeter -t "$TEST_PLAN" \ + -Jloops="$JMETER_LOOPS" \ + -Jthreads="$JMETER_THREADS" \ + -Jrampup="$JMETER_RAMP_UP" + + print_success "JMeter GUI session completed" +} + +# Function to show results +show_results() { + print_success "Test Results:" + print_info "Results file: $RESULTS_FILE" + print_info "HTML report: $REPORT_DIR/index.html" + print_info "Log file: $LOG_FILE" + + if [[ -f "$RESULTS_FILE" ]]; then + TOTAL_SAMPLES=$(tail -n +2 "$RESULTS_FILE" | wc -l) + print_info "Total samples: $TOTAL_SAMPLES" + fi + + # Try to open the HTML report + if command -v open &> /dev/null; then + print_info "Opening HTML report in browser..." + open "$REPORT_DIR/index.html" + elif command -v xdg-open &> /dev/null; then + print_info "Opening HTML report in browser..." + xdg-open "$REPORT_DIR/index.html" + else + print_info "To view the HTML report, open: file://$REPORT_DIR/index.html" + fi +} + +# Main execution +main() { + print_info "JMeter Load Test Script" + print_info "=======================" + + check_jmeter + check_test_plan + + if [[ "$GUI_MODE" == "true" ]]; then + # GUI mode - just open JMeter with the test plan + run_jmeter_gui + print_success "JMeter GUI session completed successfully!" + else + # Non-GUI mode - run automated test and generate report + create_target_dir + clean_previous_results + run_jmeter_test + show_results + print_success "JMeter load test completed successfully!" + fi +} + +# Execute main function +main "$@" \ No newline at end of file diff --git a/examples/spring-boot-performance-bottleneck-demo/src/test/resources/jmeter/load-test.jmx b/examples/spring-boot-performance-bottleneck-demo/src/test/resources/jmeter/load-test.jmx new file mode 100644 index 00000000..6a704cb1 --- /dev/null +++ b/examples/spring-boot-performance-bottleneck-demo/src/test/resources/jmeter/load-test.jmx @@ -0,0 +1,500 @@ + + + + + false + false + false + + + + BASE_URL + localhost + = + + + PORT + 8080 + = + + + + + + + continue + + ${__P(loops,5)} + false + + ${__P(threads,10)} + ${__P(rampup,10)} + false + false + + + + + false + + + + false + IT + = + department + + + + localhost + 8080 + http + /api/search/bad/users-with-colleagues + GET + true + false + true + false + + 30000 + 60000 + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + + + false + + + + false + HR + = + department + + + + localhost + 8080 + http + /api/search/bad/users-with-colleagues + GET + true + false + true + false + + 30000 + 60000 + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + + + false + + + + false + Manager + = + role + + + + localhost + 8080 + http + /api/search/bad/active-users-with-permissions + GET + true + false + true + false + + 30000 + 60000 + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + + false + + + + false + Developer + = + role + + + + localhost + 8080 + http + /api/search/bad/active-users-with-permissions + GET + true + false + true + false + + 30000 + 60000 + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + + + false + + + + false + John + = + keyword + + + + localhost + 8080 + http + /api/search/bad/similar-users + GET + true + false + true + false + + 30000 + 60000 + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + + false + + + + false + @company.com + = + keyword + + + + localhost + 8080 + http + /api/search/bad/similar-users + GET + true + false + true + false + + 30000 + 60000 + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + + + false + + + + false + IT + = + department + + + + localhost + 8080 + http + /api/search/bad/team-formation + GET + true + false + true + false + + 30000 + 120000 + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + + false + + + + false + Engineering + = + department + + + + localhost + 8080 + http + /api/search/bad/team-formation + GET + true + false + true + false + + 30000 + 120000 + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + true + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + +