diff --git a/.cursor/rules/151-java-performance-jmeter.mdc b/.cursor/rules/151-java-performance-jmeter.mdc new file mode 100644 index 00000000..bb4ff556 --- /dev/null +++ b/.cursor/rules/151-java-performance-jmeter.mdc @@ -0,0 +1,150 @@ +--- +description: +globs: +alwaysApply: false +--- +# Run performance tests based on JMeter + +## āš ļø CRITICAL INSTRUCTION FOR AI ASSISTANTS āš ļø + +**WHEN USING THE PERFORMANCE SCRIPT TEMPLATE:** +- **COPY THE BASH SCRIPT EXACTLY** from `java-performance-script-template.md` +- **NO MODIFICATIONS, INTERPRETATIONS, OR ENHANCEMENTS** allowed +- **USE EVERY LINE VERBATIM** - do not change logic, structure, or features +- **DO NOT CREATE YOUR OWN VERSION** - use the provided template only +- The template script is complete, tested, and production-ready + +--- + +## šŸ“‹ How to Copy the Performance Script Template + +### Step 1: Create the Script File +When a user requests JMeter performance testing setup: + +1. **Copy the entire script** from `.cursor/rules/templates/java-performance-script-template.md` +2. **Create the file** as `run-jmeter.sh` in the project root +3. **Make it executable**: `chmod +x run-jmeter.sh` + +### Step 2: Template Location +- **Source**: `.cursor/rules/templates/java-performance-script-template.md` +- **Destination**: `run-jmeter.sh` (project root) +- **Copy Method**: Exact verbatim copy - no changes allowed + +--- + +## šŸš€ Script Usage Instructions + +### Basic Usage +```bash +# Run with default settings (1000 loops, 1 thread, 1s ramp-up) +./run-jmeter.sh + +# Custom configuration +./run-jmeter.sh -l 500 -t 10 -r 30 + +# Open JMeter GUI +./run-jmeter.sh -g +``` + +### Available Options +- `-l, --loops LOOPS`: Number of loops per thread (default: 1000) +- `-t, --threads THREADS`: Number of concurrent threads (default: 1) +- `-r, --ramp-up SECONDS`: Ramp-up period in seconds (default: 1) +- `-g, --gui`: Open JMeter GUI instead of running in non-GUI mode +- `-h, --help`: Show detailed help message + +### Environment Variables +```bash +# Override defaults using environment variables +JMETER_LOOPS=500 JMETER_THREADS=5 ./run-jmeter.sh + +# Or export them +export JMETER_LOOPS=1000 +export JMETER_THREADS=10 +export JMETER_RAMP_UP=30 +./run-jmeter.sh +``` + +### Example Scenarios +```bash +# Light load test +./run-jmeter.sh -l 100 -t 1 -r 1 + +# Medium load test +./run-jmeter.sh -l 500 -t 5 -r 10 + +# Heavy load test +./run-jmeter.sh -l 1000 -t 20 -r 60 + +# Quick GUI setup +./run-jmeter.sh -g +``` + +--- + +## šŸ“ Required Project Structure + +The script expects this structure: +``` +project-root/ +ā”œā”€ā”€ run-jmeter.sh # The performance script +ā”œā”€ā”€ src/test/resources/jmeter/ +│ └── load-test.jmx # JMeter test plan +└── target/ # Generated reports (auto-created) + ā”œā”€ā”€ jmeter-results.jtl # Raw results + ā”œā”€ā”€ jmeter-report/ # HTML dashboard + │ └── index.html # Main report + └── jmeter.log # Execution log +``` + +--- + +## šŸ”§ Prerequisites + +1. **JMeter Installation**: Must be installed and available in PATH +2. **Test Plan**: Must exist at `src/test/resources/jmeter/load-test.jmx` +3. **Executable Permissions**: Script must be executable (`chmod +x run-jmeter.sh`) + +--- + +## šŸ“Š Output Files + +After running the script, you'll get: + +1. **Raw Results**: `target/jmeter-results.jtl` (JTL format) +2. **HTML Dashboard**: `target/jmeter-report/index.html` (automatically opens in browser) +3. **Execution Log**: `jmeter.log` (debugging information) + +--- + +## šŸ’” Best Practices + +### For Load Testing +- Start with low thread counts and gradually increase +- Monitor system resources during tests +- Use realistic ramp-up periods (don't slam the server) + +### For Test Plan Development +- Use GUI mode (`-g`) for initial test plan creation +- Test with low load first (`-l 10 -t 1`) +- Validate test plan logic before heavy load testing + +### For CI/CD Integration +```bash +# Example CI/CD usage +./run-jmeter.sh -l 100 -t 5 -r 10 +# Check exit code for pass/fail status +``` + +--- + +## 🚨 Important Notes + +- The script automatically cleans previous results +- HTML reports open automatically in your default browser +- All configuration is parameterized - no hardcoded values +- The script provides colored output for better visibility +- Comprehensive error handling and validation included + +--- + diff --git a/.cursor/rules/151-java-profiling-detect.mdc b/.cursor/rules/161-java-profiling-detect.mdc similarity index 100% rename from .cursor/rules/151-java-profiling-detect.mdc rename to .cursor/rules/161-java-profiling-detect.mdc diff --git a/.cursor/rules/152-java-profiling-analyze.mdc b/.cursor/rules/162-java-profiling-analyze.mdc similarity index 100% rename from .cursor/rules/152-java-profiling-analyze.mdc rename to .cursor/rules/162-java-profiling-analyze.mdc diff --git a/.cursor/rules/154-java-profiling-compare.mdc b/.cursor/rules/164-java-profiling-compare.mdc similarity index 100% rename from .cursor/rules/154-java-profiling-compare.mdc rename to .cursor/rules/164-java-profiling-compare.mdc diff --git a/.cursor/rules/templates/java-checklist-template.md b/.cursor/rules/templates/java-checklist-template.md index ecf37d91..432f129a 100644 --- a/.cursor/rules/templates/java-checklist-template.md +++ b/.cursor/rules/templates/java-checklist-template.md @@ -36,7 +36,6 @@ Use the following process to improve the java development in some areas if requi |-------------|-------------|--------|-------| | [131-java-unit-testing](.cursor/rules/131-java-unit-testing.mdc) | Unit Testing | `Can improve the unit tests using the cursor rule @131-java-unit-testing` | Add in the context a Test Class or the package | - ### Step 5: Refactoring | Cursor Rule | Description | Prompt | Notes | @@ -45,14 +44,20 @@ Use the following process to improve the java development in some areas if requi | [142-java-functional-programming](.cursor/rules/142-java-functional-programming.mdc) | Functional Programming | `Refactor my code to use functional programming using the cursor rule @142-java-functional-programming` | | | [143-java-data-oriented-programming](.cursor/rules/143-java-data-oriented-programming.mdc) | Data Oriented Programming | `Refactor my code to use data oriented programming using the cursor rule @143-java-data-oriented-programming` | | -### Step 6: Profiling +### Step 6: Performance (Jmeter) + +| 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 | + +### Step 7: Profiling (Async profiler) | Activity | Description | Prompt | Notes | |----------|------|--------|-------| -| [151-java-profiling-detect](.cursor/rules/151-java-profiling-detect.mdc) | Measure problems | `My Java application has performance issues - help me set up comprehensive profiling process using @151-java-profiling-detect.mdc and use the location YOUR-DEVELOPMENT/profiler` | Replace YOUR-DEVELOPMENT with your actual development path. Example: examples/spring-boot-memory-leak-demo/profiler | -| [152-java-profiling-analyze](.cursor/rules/152-java-profiling-analyze.mdc) | Analyze results | `Analyze the results located in YOUR-DEVELOPMENT/profiler and use the cursor rule @152-java-profiling-analyze` | Replace YOUR-DEVELOPMENT with your actual development path. Example: examples/spring-boot-memory-leak-demo/profiler | +| [161-java-profiling-detect](.cursor/rules/161-java-profiling-detect.mdc) | Measure problems | `My Java application has performance issues - help me set up comprehensive profiling process using @151-java-profiling-detect.mdc and use the location YOUR-DEVELOPMENT/profiler` | Replace YOUR-DEVELOPMENT with your actual development path. Example: examples/spring-boot-memory-leak-demo/profiler | +| [162-java-profiling-analyze](.cursor/rules/162-java-profiling-analyze.mdc) | Analyze results | `Analyze the results located in YOUR-DEVELOPMENT/profiler and use the cursor rule @152-java-profiling-analyze` | Replace YOUR-DEVELOPMENT with your actual development path. Example: examples/spring-boot-memory-leak-demo/profiler | | - | Code Refactoring | `Can you apply the solutions from @profiling-solutions-yyyymmdd.md in @/info to mitigate bottlenecks` | Make a refactoring with the notes from the analysis | -| [154-java-profiling-compare](.cursor/rules/152-java-profiling-compare.mdc) | Analyze results | `Review if the problems was solved with last refactoring using the reports located in @/results with the cursor rule 154-java-profiling-compare.mdc` | Put in the context the folder with the results | +| [164-java-profiling-compare](.cursor/rules/162-java-profiling-compare.mdc) | Analyze results | `Review if the problems was solved with last refactoring using the reports located in @/results with the cursor rule 154-java-profiling-compare.mdc` | Put in the context the folder with the results | ## Reference Table: Java Cursor Rules @@ -70,10 +75,10 @@ Use the following process to improve the java development in some areas if requi | 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-detect | Generate profiling data | -| Java Profiling | @152-java-profiling-analyze | Analyze profiling data | -| Java Profiling | @154-java-profiling-compare | Compare results after refactoring | -| SQL Guidelines | @500-sql | SQL development best practices | +| Performance testing | @151-java-performance-jmeter | Run a Jmeter script | +| Java Profiling | @161-java-profiling-detect | Generate profiling data | +| Java Profiling | @162-java-profiling-analyze | Analyze profiling data | +| Java Profiling | @164-java-profiling-compare | Compare results after refactoring | ## Tips & Best Practices diff --git a/.cursor/rules/templates/java-performance-script-template.md b/.cursor/rules/templates/java-performance-script-template.md new file mode 100644 index 00000000..0cc55edc --- /dev/null +++ b/.cursor/rules/templates/java-performance-script-template.md @@ -0,0 +1,253 @@ +```bash +#!/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/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 1f5b0675..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: [jabrena] diff --git a/.github/workflows/maven.yaml b/.github/workflows/maven.yaml index 45c5aa6a..1a1f1d92 100644 --- a/.github/workflows/maven.yaml +++ b/.github/workflows/maven.yaml @@ -37,3 +37,5 @@ jobs: run: cd examples/spring-boot-memory-leak-demo && ./mvnw --batch-mode --no-transfer-progress package --file pom.xml - name: Spring Boot Performance Bottleneck build run: cd examples/spring-boot-performance-bottleneck-demo && ./mvnw --batch-mode --no-transfer-progress package --file pom.xml + - name: Spring Boot JMeter build + run: cd examples/spring-boot-jmeter-demo && ./mvnw --batch-mode --no-transfer-progress package --file pom.xml \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7a742de0..cc4997b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store tmp target/ +*.log diff --git a/README.md b/README.md index 7dfc3a92..6ba3b909 100644 --- a/README.md +++ b/README.md @@ -57,14 +57,20 @@ Using the Cursor rules is straightforward: simply `drag and drop` the cursor rul | [142-java-functional-programming](.cursor/rules/142-java-functional-programming.mdc) | Functional Programming | `Refactor my code to use functional programming using the cursor rule @142-java-functional-programming` | | | [143-java-data-oriented-programming](.cursor/rules/143-java-data-oriented-programming.mdc) | Data Oriented Programming | `Refactor my code to use data oriented programming using the cursor rule @143-java-data-oriented-programming` | | -### Profiling rules +### Performance rule (Jmeter) | Activity | Description | Prompt | Notes | |----------|------|--------|-------| -| [151-java-profiling-detect](.cursor/rules/151-java-profiling-detect.mdc) | Measure problems | `My Java application has performance issues - help me set up comprehensive profiling process using @151-java-profiling-detect.mdc and use the location YOUR-DEVELOPMENT/profiler` | Replace YOUR-DEVELOPMENT with your actual development path. Example: examples/spring-boot-memory-leak-demo/profiler | -| [152-java-profiling-analyze](.cursor/rules/152-java-profiling-analyze.mdc) | Analyze results | `Analyze the results located in YOUR-DEVELOPMENT/profiler and use the cursor rule @152-java-profiling-analyze` | Replace YOUR-DEVELOPMENT with your actual development path. Example: examples/spring-boot-memory-leak-demo/profiler | +| [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 | + +### Profiling rules (Async profiler) + +| Activity | Description | Prompt | Notes | +|----------|------|--------|-------| +| [161-java-profiling-detect](.cursor/rules/161-java-profiling-detect.mdc) | Measure problems | `My Java application has performance issues - help me set up comprehensive profiling process using @161-java-profiling-detect.mdc and use the location YOUR-DEVELOPMENT/profiler` | Replace YOUR-DEVELOPMENT with your actual development path. Example: examples/spring-boot-memory-leak-demo/profiler | +| [162-java-profiling-analyze](.cursor/rules/162-java-profiling-analyze.mdc) | Analyze results | `Analyze the results located in YOUR-DEVELOPMENT/profiler and use the cursor rule @162-java-profiling-analyze` | Replace YOUR-DEVELOPMENT with your actual development path. Example: examples/spring-boot-memory-leak-demo/profiler | | - | Code Refactoring | `Can you apply the solutions from @profiling-solutions-yyyymmdd.md in @/info to mitigate bottlenecks` | Make a refactoring with the notes from the analysis | -| [154-java-profiling-compare](.cursor/rules/152-java-profiling-compare.mdc) | Analyze results | `Review if the problems was solved with last refactoring using the reports located in @/results with the cursor rule 154-java-profiling-compare.mdc` | Put in the context the folder with the results | +| [164-java-profiling-compare](.cursor/rules/164-java-profiling-compare.mdc) | Analyze results | `Review if the problems was solved with last refactoring using the reports located in @/results with the cursor rule 154-java-profiling-compare.mdc` | Put in the context the folder with the results | ## Getting started @@ -102,6 +108,7 @@ The rules was tested with the following examples: - [Microservices: Spring Boot application](./examples/spring-boot-demo/implementation/README.md) - [Microservices: Spring Boot application with Memory leaks](./examples/spring-boot-memory-leak-demo/README.md) - [Microservices: Spring Boot application with Performance Bottleneck](./examples/spring-boot-performance-bottleneck-demo/README.md) +- [Microservices: Spring Boot application with JMeter Load Testing](./examples/spring-boot-jmeter-demo/README.md) - [Serverless: AWS Lambda](./examples/aws-lambda-hello-world/README.md) - [Serverless: Azure Function](./examples/azure-function-hello-world/README.md) @@ -138,3 +145,5 @@ If you have new ideas to improve any of the current Cursor rules or add a new on - https://github.com/jabrena/plantuml-to-png-cli - https://github.com/jabrena/setup-cli - https://github.com/jabrena/jbang-catalog + +Powered by [Cursor](https://www.cursor.com/) diff --git a/examples/README.md b/examples/README.md index 6041a8a4..2d4cd7d8 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,7 +4,9 @@ |----------|-------| | [Maven demo](maven-demo/README.md) | Simple Maven demo generated with `jbang setup@jabrena init --maven`. Used to test the behaviour of Cursor rules for Java. | | [Spring Boot demo](spring-boot-demo/implementation/README.md) | Simple Maven demo generated with `jbang setup@jabrena init --spring-boot`. Used to test the behaviour of Cursor rules for Java & Spring Boot. | +| [Spring Boot JMeter demo](spring-boot-jmeter-demo/README.md) | Spring Boot application with JMeter load testing capabilities. Includes automated test scripts and HTML report generation for performance testing. | | [Spring Boot Memory Leak demo](spring-boot-memory-leak-demo/README.md) | Spring Boot application with intentional memory leaks. Used to demonstrate profiling and memory leak analysis techniques with JFR and flamegraphs. | | [Spring Boot Performance Bottleneck demo](spring-boot-performance-bottleneck-demo/README.md) | Spring Boot application demonstrating common performance anti-patterns with O(n²) and O(n³) algorithms. Includes load testing and profiling tools for performance analysis. | + | [AWS lambda Hello World](aws-lambda-hello-world/README.md) | Simple AWS Lambda. Used to test the behaviour of Cursor rules for Java & AWS Lambda. | | [Azure function Hello World](azure-function-hello-world/README.md) | Simple Azure function. Used to test the behaviour of Cursor rules for Java & Azure function. | diff --git a/examples/spring-boot-jmeter-demo/.gitattributes b/examples/spring-boot-jmeter-demo/.gitattributes new file mode 100644 index 00000000..3b41682a --- /dev/null +++ b/examples/spring-boot-jmeter-demo/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/examples/spring-boot-jmeter-demo/.gitignore b/examples/spring-boot-jmeter-demo/.gitignore new file mode 100644 index 00000000..667aaef0 --- /dev/null +++ b/examples/spring-boot-jmeter-demo/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/examples/spring-boot-jmeter-demo/.mvn/wrapper/maven-wrapper.properties b/examples/spring-boot-jmeter-demo/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..2f94e616 --- /dev/null +++ b/examples/spring-boot-jmeter-demo/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.10/apache-maven-3.9.10-bin.zip diff --git a/examples/spring-boot-jmeter-demo/README-DEV.md b/examples/spring-boot-jmeter-demo/README-DEV.md new file mode 100644 index 00000000..b2bbe838 --- /dev/null +++ b/examples/spring-boot-jmeter-demo/README-DEV.md @@ -0,0 +1,38 @@ +# Essential Maven Goals: + +```bash +# Analyze dependencies +./mvnw dependency:tree +./mvnw dependency:analyze +./mvnw dependency:resolve + +./mvnw clean validate -U +./mvnw buildplan:list-phase +./mvnw license:third-party-report + +# Clean the project +./mvnw clean + +# Clean and package in one command +./mvnw clean package + +# Run integration tests +./mvnw verify + +# Check for dependency updates +./mvnw versions:display-property-updates +./mvnw versions:display-dependency-updates +./mvnw versions:display-plugin-updates + +# Generate project reports +./mvnw site +jwebserver -p 8005 -d "$(pwd)/target/site/" + +curl -X GET http://localhost:8080/api/v1/hello + +./run-jmeter.sh --help +./run-jmeter.sh -l 5000 -t 10 -r 5 +./run-jmeter.sh -gui # Open JMeter GUI + +jwebserver -p 8007 -d "$(pwd)/target/jmeter-report/" +``` \ No newline at end of file diff --git a/examples/spring-boot-jmeter-demo/README.md b/examples/spring-boot-jmeter-demo/README.md new file mode 100644 index 00000000..ab8ca456 --- /dev/null +++ b/examples/spring-boot-jmeter-demo/README.md @@ -0,0 +1 @@ +# Spring Boot with JMeter \ No newline at end of file diff --git a/examples/spring-boot-jmeter-demo/mvnw b/examples/spring-boot-jmeter-demo/mvnw new file mode 100755 index 00000000..19529ddf --- /dev/null +++ b/examples/spring-boot-jmeter-demo/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/examples/spring-boot-jmeter-demo/mvnw.cmd b/examples/spring-boot-jmeter-demo/mvnw.cmd new file mode 100644 index 00000000..249bdf38 --- /dev/null +++ b/examples/spring-boot-jmeter-demo/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/examples/spring-boot-jmeter-demo/pom.xml b/examples/spring-boot-jmeter-demo/pom.xml new file mode 100644 index 00000000..5350024d --- /dev/null +++ b/examples/spring-boot-jmeter-demo/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.3 + + + + info.jab.ms + spring-boot-jmeter-demo + 0.0.1-SNAPSHOT + spring-boot-jmeter-demo + Demo project for Spring Boot with JMeter + + + 17 + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.7.0 + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/examples/spring-boot-jmeter-demo/src/main/java/info/jab/ms/MainApplication.java b/examples/spring-boot-jmeter-demo/src/main/java/info/jab/ms/MainApplication.java new file mode 100644 index 00000000..07a07479 --- /dev/null +++ b/examples/spring-boot-jmeter-demo/src/main/java/info/jab/ms/MainApplication.java @@ -0,0 +1,13 @@ +package info.jab.ms; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MainApplication { + + public static void main(String[] args) { + SpringApplication.run(MainApplication.class, args); + } + +} diff --git a/examples/spring-boot-jmeter-demo/src/main/java/info/jab/ms/MyController.java b/examples/spring-boot-jmeter-demo/src/main/java/info/jab/ms/MyController.java new file mode 100644 index 00000000..8246ad52 --- /dev/null +++ b/examples/spring-boot-jmeter-demo/src/main/java/info/jab/ms/MyController.java @@ -0,0 +1,22 @@ +package info.jab.ms; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1") +public class MyController { + + // Rule 3.1: Use static final Logger instance per class, obtained via LoggerFactory.getLogger(ClassName.class) + private static final Logger logger = LoggerFactory.getLogger(MyController.class); + + @GetMapping("/hello") + public ResponseEntity hello() { + logger.info("Processing hello request"); + return ResponseEntity.ok("Hello, World!"); + } +} diff --git a/examples/spring-boot-jmeter-demo/src/test/java/info/jab/ms/DemoApplicationTest.java b/examples/spring-boot-jmeter-demo/src/test/java/info/jab/ms/DemoApplicationTest.java new file mode 100644 index 00000000..81c7c21c --- /dev/null +++ b/examples/spring-boot-jmeter-demo/src/test/java/info/jab/ms/DemoApplicationTest.java @@ -0,0 +1,13 @@ +package info.jab.ms; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class MainApplicationTest { + + @Test + void contextLoads() { + } + +} diff --git a/examples/spring-boot-jmeter-demo/src/test/resources/jmeter/load-test.jmx b/examples/spring-boot-jmeter-demo/src/test/resources/jmeter/load-test.jmx new file mode 100644 index 00000000..88c889fd --- /dev/null +++ b/examples/spring-boot-jmeter-demo/src/test/resources/jmeter/load-test.jmx @@ -0,0 +1,190 @@ + + + + + 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/hello + GET + true + false + true + false + + 10000 + 30000 + + + + + 200 + + + Assertion.response_code + false + 1 + + + + + Hello, World! + + + Assertion.response_data + false + 16 + + + + + 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-memory-leak-demo/profiler/scripts/java-profile.sh b/examples/spring-boot-memory-leak-demo/profiler/scripts/java-profile.sh deleted file mode 100755 index 389973a8..00000000 --- a/examples/spring-boot-memory-leak-demo/profiler/scripts/java-profile.sh +++ /dev/null @@ -1,567 +0,0 @@ -#!/bin/bash - -# java-profile.sh - Automated Java profiling script - -set -e - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${GREEN}Java Application Profiler v4.0 - Interactive Mode${NC}" -echo "=================================================" - -# Step 0: Problem Identification -echo -e "${YELLOW}Step 0: Problem Identification${NC}" -echo "-----" -echo "What specific performance problem are you trying to solve?" -echo "" -echo -e "${BLUE}Problem Categories:${NC}" -echo " 1) Performance Bottlenecks (CPU hotspots, inefficient algorithms, unnecessary allocations, string operations)" -echo " 2) Memory-Related Problems (memory leaks, heap usage, object retention, off-heap issues)" -echo " 3) Concurrency/Threading Issues (lock contention, thread pool issues, deadlocks, context switching)" -echo " 4) Garbage Collection Problems (GC pressure, long pauses, generational issues)" -echo " 5) I/O and Network Bottlenecks (blocking operations, connection leaks, serialization issues)" -echo " 0) Not sure / General performance analysis" -echo "" - -while true; do - read -p "Select the problem category you're investigating (0-5): " PROBLEM_SELECTION - - if [[ "$PROBLEM_SELECTION" =~ ^[0-5]$ ]]; then - case $PROBLEM_SELECTION in - 1) PROBLEM_CATEGORY="Performance Bottlenecks"; SUGGESTED_PROFILE="CPU" ;; - 2) PROBLEM_CATEGORY="Memory-Related Problems"; SUGGESTED_PROFILE="Memory" ;; - 3) PROBLEM_CATEGORY="Concurrency/Threading Issues"; SUGGESTED_PROFILE="Lock/Threading" ;; - 4) PROBLEM_CATEGORY="Garbage Collection Problems"; SUGGESTED_PROFILE="GC Analysis" ;; - 5) PROBLEM_CATEGORY="I/O and Network Bottlenecks"; SUGGESTED_PROFILE="I/O Analysis" ;; - 0) PROBLEM_CATEGORY="General Analysis"; SUGGESTED_PROFILE="CPU" ;; - esac - - echo -e "${GREEN}Selected problem category: $PROBLEM_CATEGORY${NC}" - echo -e "${GREEN}Suggested profiling approach: $SUGGESTED_PROFILE${NC}" - echo "" - break - else - echo -e "${RED}Invalid selection. Please choose a number between 0 and 5.${NC}" - fi -done - -# Step 1: List Java processes and handle selection -echo -e "${YELLOW}Step 1: Available Java Processes${NC}" -echo "-----" - -# Get list of Java processes -JAVA_PROCESSES=$(jps -l | grep -v "Jps$") - -if [ -z "$JAVA_PROCESSES" ]; then - echo -e "${RED}No Java processes found running on this system.${NC}" - echo "Please start your Spring Boot application first:" - echo " cd examples/spring-boot-memory-leak-demo" - echo " ./mvnw spring-boot:run" - echo "" - echo "Then try running this profiler again." - exit 1 -fi - -# Count the number of processes -PROCESS_COUNT=$(echo "$JAVA_PROCESSES" | wc -l | xargs) - -if [ "$PROCESS_COUNT" -eq 1 ]; then - # Only one process found, auto-select it - PID=$(echo "$JAVA_PROCESSES" | cut -d' ' -f1) - PROCESS_NAME=$(echo "$JAVA_PROCESSES" | cut -d' ' -f2-) - echo -e "${GREEN}Found single Java process:${NC}" - echo " PID: $PID" - echo " Name: $PROCESS_NAME" - echo "" - read -p "Do you want to profile this process? (y/N): " CONFIRM - if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then - echo "Profiling cancelled by user." - exit 0 - fi -else - # Multiple processes found, provide selection menu - echo -e "${GREEN}Found $PROCESS_COUNT Java processes:${NC}" - echo "" - - # Create numbered list - i=1 - declare -a PIDS - declare -a NAMES - - while IFS= read -r line; do - pid=$(echo "$line" | cut -d' ' -f1) - name=$(echo "$line" | cut -d' ' -f2-) - PIDS[$i]=$pid - NAMES[$i]=$name - echo " $i) PID: $pid - $name" - ((i++)) - done <<< "$JAVA_PROCESSES" - - echo "" - echo "0) Manual PID entry" - echo "" - - # Get user selection - while true; do - read -p "Select a process to profile (0-$PROCESS_COUNT): " SELECTION - - if [[ "$SELECTION" =~ ^[0-9]+$ ]]; then - if [ "$SELECTION" -eq 0 ]; then - # Manual PID entry - read -p "Enter the PID of the Java process to profile: " PID - break - elif [ "$SELECTION" -ge 1 ] && [ "$SELECTION" -le "$PROCESS_COUNT" ]; then - # Valid selection - PID=${PIDS[$SELECTION]} - PROCESS_NAME=${NAMES[$SELECTION]} - echo -e "${GREEN}Selected process:${NC}" - echo " PID: $PID" - echo " Name: $PROCESS_NAME" - break - else - echo -e "${RED}Invalid selection. Please choose a number between 0 and $PROCESS_COUNT.${NC}" - fi - else - echo -e "${RED}Invalid input. Please enter a number.${NC}" - fi - done -fi - -# Validate PID -if ! kill -0 "$PID" 2>/dev/null; then - echo -e "${RED}Error: Process $PID does not exist or is not accessible${NC}" - exit 1 -fi - -# Double-check if it's a Java process (for manual entries) -if ! jps | grep -q "^$PID "; then - echo -e "${RED}Error: Process $PID is not a Java process${NC}" - exit 1 -fi - -echo -e "${GREEN}Ready to profile process $PID${NC}" - -# Step 2: Determine profiler directory (default to current script's parent directory) -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROFILER_DIR="$(dirname "$SCRIPT_DIR")" - -echo -e "${YELLOW}Step 2: Profiler Directory Setup${NC}" -echo "-----" -echo -e "${GREEN}Using profiler directory: $PROFILER_DIR${NC}" - -# Step 3: Detect OS and download profiler -echo -e "${YELLOW}Step 3: Setting up async-profiler${NC}" -echo "-----" - -detect_os_arch() { - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - ARCH=$(uname -m) - - case "$OS" in - linux*) - case "$ARCH" in - x86_64) PLATFORM="linux-x64" ;; - aarch64|arm64) PLATFORM="linux-arm64" ;; - *) echo -e "${RED}Unsupported architecture: $ARCH${NC}"; exit 1 ;; - esac - ;; - darwin*) - case "$ARCH" in - x86_64|arm64) PLATFORM="macos" ;; - *) echo -e "${RED}Unsupported architecture: $ARCH${NC}"; exit 1 ;; - esac - ;; - *) - echo -e "${RED}Unsupported operating system: $OS${NC}" - exit 1 - ;; - esac - - echo -e "${GREEN}Detected platform: $PLATFORM${NC}" -} - -download_profiler() { - local platform=$1 - local profiler_dir=$2 - local version="4.0" - - # For macOS, v4.0 uses .zip format, Linux still uses .tar.gz - if [[ "$platform" == "macos" ]]; then - local filename="async-profiler-$version-$platform.zip" - local extract_cmd="unzip -q" - else - local filename="async-profiler-$version-$platform.tar.gz" - local extract_cmd="tar -xzf" - fi - - local url="https://github.com/jvm-profiling-tools/async-profiler/releases/download/v$version/$filename" - - if [ ! -d "$profiler_dir/current" ]; then - echo "Downloading async-profiler..." - echo "URL: $url" - mkdir -p "$profiler_dir" - cd "$profiler_dir" - - # Remove any failed downloads - rm -f async-profiler-*.tar.gz async-profiler-*.zip 2>/dev/null - - if command -v curl >/dev/null 2>&1; then - curl -L -o "$filename" "$url" - elif command -v wget >/dev/null 2>&1; then - wget -O "$filename" "$url" - else - echo -e "${RED}Error: Neither curl nor wget is available${NC}" - exit 1 - fi - - # Check if download was successful - if [[ ! -f "$filename" ]]; then - echo -e "${RED}Download failed: File $filename not found${NC}" - exit 1 - fi - - # Check file size (should be larger than 1MB for a valid download) - local file_size - if [[ "$OSTYPE" == "darwin"* ]]; then - file_size=$(stat -f%z "$filename" 2>/dev/null || echo "0") - else - file_size=$(stat -c%s "$filename" 2>/dev/null || echo "0") - fi - - if [[ "$file_size" -lt 100000 ]]; then # Less than 100KB - echo -e "${RED}Download failed: File is too small ($file_size bytes). This usually means a redirect or error page was downloaded.${NC}" - echo -e "${YELLOW}Contents of downloaded file:${NC}" - head -10 "$filename" 2>/dev/null || echo "Cannot read file" - rm -f "$filename" - exit 1 - fi - - # Extract the archive - echo "Extracting $filename..." - $extract_cmd "$filename" - - if [[ $? -ne 0 ]]; then - echo -e "${RED}Failed to extract $filename${NC}" - rm -f "$filename" - exit 1 - fi - - # Create symbolic link - ln -sf "async-profiler-$version-$platform" current - - # Clean up archive - rm -f "$filename" - - cd - > /dev/null - echo -e "${GREEN}Async-profiler downloaded successfully to $profiler_dir${NC}" - else - echo -e "${GREEN}Async-profiler already available at $profiler_dir${NC}" - fi -} - -detect_os_arch -download_profiler "$PLATFORM" "$PROFILER_DIR" - -# Create results directory if it doesn't exist (inside profiler directory) -RESULTS_DIR="$PROFILER_DIR/results" -mkdir -p "$RESULTS_DIR" - -# Function to check platform capabilities -check_platform_capabilities() { - echo -e "${BLUE}Platform Capabilities Check:${NC}" - echo "-----" - - if [[ "$OSTYPE" == "darwin"* ]]; then - echo -e "${YELLOW}Platform: macOS${NC}" - echo "āœ… CPU profiling (limited to user space)" - echo "āœ… Memory allocation profiling" - echo "āœ… Lock contention profiling" - echo "āœ… Wall clock profiling" - echo "āœ… JFR format" - echo "āŒ Native memory profiling (requires Linux perf events)" - echo "āŒ Hardware performance counters" - echo "" - echo -e "${BLUE}Note: macOS profiling is limited to user-space code only${NC}" - else - echo -e "${YELLOW}Platform: Linux${NC}" - echo "āœ… Full CPU profiling (user + kernel space)" - echo "āœ… Memory allocation profiling" - echo "āœ… Native memory profiling" - echo "āœ… Lock contention profiling" - echo "āœ… Wall clock profiling" - echo "āœ… Hardware performance counters" - echo "āœ… JFR format" - - # Check if running as root or with proper permissions - if [[ $EUID -eq 0 ]]; then - echo "āœ… Running with elevated privileges" - else - echo -e "${YELLOW}āš ļø For optimal profiling, consider running with elevated privileges${NC}" - fi - fi - echo "" -} - -# Check platform capabilities -check_platform_capabilities - -# Function to show profiling menu -show_profiling_menu() { - echo "" - echo -e "${BLUE}===========================================${NC}" - echo -e "${YELLOW}Profiling Options for PID: $PID${NC}" - echo -e "${YELLOW}Process: $PROCESS_NAME${NC}" - echo -e "${YELLOW}Problem Category: $PROBLEM_CATEGORY${NC}" - echo -e "${YELLOW}Suggested Approach: $SUGGESTED_PROFILE${NC}" - echo -e "${BLUE}===========================================${NC}" - - # Show recommended options first based on problem category - case $SUGGESTED_PROFILE in - "Memory") - echo -e "${GREEN}*** RECOMMENDED FOR MEMORY LEAK DETECTION ***${NC}" - echo "2. Memory Allocation Profiling (30s) ⭐" - echo "10. Memory Leak Detection (5min) ⭐" - echo "6. Native Memory Profiling (30s) ⭐" - echo "" - echo -e "${BLUE}Other Options:${NC}" - ;; - "CPU") - echo -e "${GREEN}*** RECOMMENDED FOR YOUR PROBLEM ***${NC}" - echo "1. CPU Profiling (30s) ⭐" - echo "8. Custom Duration CPU Profiling ⭐" - echo "" - echo -e "${BLUE}Other Options:${NC}" - ;; - "Lock/Threading") - echo -e "${GREEN}*** RECOMMENDED FOR YOUR PROBLEM ***${NC}" - echo "3. Lock Contention Profiling (30s) ⭐" - echo "4. Wall Clock Profiling (30s) ⭐" - echo "" - echo -e "${BLUE}Other Options:${NC}" - ;; - "GC Analysis") - echo -e "${GREEN}*** RECOMMENDED FOR YOUR PROBLEM ***${NC}" - echo "5. Interactive Heatmap (60s) ⭐" - echo "2. Memory Allocation Profiling (30s) ⭐" - echo "" - echo -e "${BLUE}Other Options:${NC}" - ;; - "I/O Analysis") - echo -e "${GREEN}*** RECOMMENDED FOR YOUR PROBLEM ***${NC}" - echo "4. Wall Clock Profiling (30s) ⭐" - echo "5. Interactive Heatmap (60s) ⭐" - echo "" - echo -e "${BLUE}Other Options:${NC}" - ;; - esac - - echo "1. CPU Profiling (30s)" - echo "2. Memory Allocation Profiling (30s)" - echo "3. Lock Contention Profiling (30s)" - echo "4. Wall Clock Profiling (30s)" - echo "5. Interactive Heatmap (60s) - NEW in v4.0" - if [[ "$OSTYPE" == "darwin"* ]]; then - echo "6. Memory Profiling (30s) - macOS compatible" - else - echo "6. Native Memory Profiling (30s) - NEW in v4.0 (Linux)" - fi - echo "7. Inverted Flame Graph (30s) - NEW in v4.0" - echo "8. Custom Duration CPU Profiling" - echo "9. View recent results" - echo "10. Memory Leak Detection (5min)" - echo "11. Complete Memory Analysis Workflow" - echo "0. Exit profiler" - echo -e "${BLUE}===========================================${NC}" -} - -# Function to handle profiling errors -handle_profiling_error() { - local exit_code=$1 - local profiling_type=$2 - - if [[ $exit_code -ne 0 ]]; then - echo -e "${RED}Profiling failed with exit code: $exit_code${NC}" - echo -e "${YELLOW}Common solutions:${NC}" - - if [[ "$OSTYPE" == "darwin"* ]]; then - echo "• On macOS, some profiling modes have limitations" - echo "• Try using --all-user flag for CPU profiling" - echo "• Use allocation profiling instead of native memory profiling" - else - echo "• Check if you have sufficient permissions:" - echo " sudo sysctl kernel.perf_event_paranoid=1" - echo " sudo sysctl kernel.kptr_restrict=0" - echo "• Try running with sudo for full system profiling" - echo "• Use --all-user flag to profile only user-space code" - fi - - echo -e "${BLUE}Alternative: Try option 2 (Memory Allocation Profiling) which works on all platforms${NC}" - return 1 - fi - return 0 -} - -# Function to execute profiling -execute_profiling() { - local option=$1 - - case $option in - 1) - echo -e "${GREEN}Starting CPU profiling for 30 seconds...${NC}" - # On macOS, add --all-user flag to avoid kernel profiling issues - if [[ "$OSTYPE" == "darwin"* ]]; then - "$PROFILER_DIR/current/bin/asprof" -d 30 --all-user -f "$RESULTS_DIR/cpu-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - handle_profiling_error $? "CPU (macOS)" - else - "$PROFILER_DIR/current/bin/asprof" -d 30 -f "$RESULTS_DIR/cpu-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - handle_profiling_error $? "CPU (Linux)" - fi - ;; - 2) - echo -e "${GREEN}Starting memory allocation profiling for 30 seconds...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 30 -f "$RESULTS_DIR/allocation-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - ;; - 3) - echo -e "${GREEN}Starting lock contention profiling for 30 seconds...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e lock -d 30 -f "$RESULTS_DIR/lock-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - ;; - 4) - echo -e "${GREEN}Starting wall clock profiling for 30 seconds...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e wall -d 30 -f "$RESULTS_DIR/wall-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - ;; - 5) - echo -e "${GREEN}Starting interactive heatmap profiling for 60 seconds...${NC}" - TIMESTAMP=$(date +%Y%m%d-%H%M%S) - JFR_FILE="$RESULTS_DIR/profile-$TIMESTAMP.jfr" - HEATMAP_FILE="$RESULTS_DIR/heatmap-cpu-$TIMESTAMP.html" - - echo -e "${BLUE}Step 1: Generating JFR recording...${NC}" - "$PROFILER_DIR/current/bin/asprof" -d 60 -o jfr -f "$JFR_FILE" "$PID" - - echo -e "${BLUE}Step 2: Converting JFR to heatmap...${NC}" - "$PROFILER_DIR/current/bin/jfrconv" --cpu -o heatmap "$JFR_FILE" "$HEATMAP_FILE" - echo -e "${GREEN}Heatmap generated: $HEATMAP_FILE${NC}" - ;; - 6) - echo -e "${GREEN}Starting native memory profiling for 30 seconds...${NC}" - # Check if we're on macOS (Darwin) - if [[ "$OSTYPE" == "darwin"* ]]; then - echo -e "${YELLOW}Note: Native memory profiling requires Linux perf events.${NC}" - echo -e "${YELLOW}On macOS, using allocation profiling as alternative...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 30 --alloc 512k -f "$RESULTS_DIR/allocation-memory-$(date +%Y%m%d-%H%M%S).html" "$PID" - else - # Check if perf events are available on Linux - if ! "$PROFILER_DIR/current/bin/asprof" check native "$PID" 2>/dev/null; then - echo -e "${YELLOW}Native memory profiling not available. Using allocation profiling instead...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 30 --alloc 512k -f "$RESULTS_DIR/allocation-memory-$(date +%Y%m%d-%H%M%S).html" "$PID" - else - "$PROFILER_DIR/current/bin/asprof" -e native -d 30 -f "$RESULTS_DIR/native-memory-$(date +%Y%m%d-%H%M%S).html" "$PID" - fi - fi - ;; - 7) - echo -e "${GREEN}Starting inverted flame graph profiling for 30 seconds...${NC}" - "$PROFILER_DIR/current/bin/asprof" -d 30 --inverted -f "$RESULTS_DIR/inverted-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - ;; - 8) - read -p "Enter duration in seconds: " DURATION - echo -e "${GREEN}Starting CPU profiling for $DURATION seconds...${NC}" - # On macOS, add --all-user flag to avoid kernel profiling issues - if [[ "$OSTYPE" == "darwin"* ]]; then - "$PROFILER_DIR/current/bin/asprof" -d "$DURATION" --all-user -f "$RESULTS_DIR/cpu-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - else - "$PROFILER_DIR/current/bin/asprof" -d "$DURATION" -f "$RESULTS_DIR/cpu-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - fi - ;; - 9) - echo -e "${YELLOW}Recent profiling results in $RESULTS_DIR:${NC}" - ls -lat "$RESULTS_DIR"/*.html "$RESULTS_DIR"/*.jfr 2>/dev/null | head -10 || echo "No profiling files found" - return - ;; - 10) - echo -e "${GREEN}Starting Memory Leak Detection (5 minutes)...${NC}" - echo -e "${YELLOW}This will run for 5 minutes to capture memory leak patterns${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 300 --alloc 1m -f "$RESULTS_DIR/memory-leak-$(date +%Y%m%d-%H%M%S).html" "$PID" - ;; - 11) - echo -e "${GREEN}Starting Complete Memory Analysis Workflow...${NC}" - TIMESTAMP=$(date +%Y%m%d-%H%M%S) - - echo -e "${BLUE}Step 1: Quick memory allocation baseline (30s)...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 30 -f "$RESULTS_DIR/memory-baseline-$TIMESTAMP.html" "$PID" - - echo -e "${BLUE}Step 2: Heap profiling (60s)...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 60 -f "$RESULTS_DIR/heap-analysis-$TIMESTAMP.html" "$PID" - - echo -e "${BLUE}Step 3: Memory leak detection (5min)...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 300 --alloc 1m -f "$RESULTS_DIR/memory-leak-complete-$TIMESTAMP.html" "$PID" - - echo -e "${GREEN}Complete memory analysis finished! Check these files:${NC}" - echo "- memory-baseline-$TIMESTAMP.html (30s baseline)" - echo "- heap-analysis-$TIMESTAMP.html (60s detailed heap)" - echo "- memory-leak-complete-$TIMESTAMP.html (5min leak detection)" - ;; - 0) - echo -e "${GREEN}Exiting profiler. Goodbye!${NC}" - exit 0 - ;; - *) - echo -e "${RED}Invalid option${NC}" - return - ;; - esac - - if [ $option -ne 9 ] && [ $option -ne 0 ]; then - echo -e "${GREEN}Profiling completed!${NC}" - echo -e "${YELLOW}Generated files in $RESULTS_DIR:${NC}" - ls -lat "$RESULTS_DIR"/*.html "$RESULTS_DIR"/*.jfr 2>/dev/null | head -5 || echo "No profiling files found" - - # Automatically open the latest file if on macOS - if [[ "$OSTYPE" == "darwin"* ]]; then - LATEST_HTML=$(ls -t "$RESULTS_DIR"/*.html 2>/dev/null | head -1) - if [ ! -z "$LATEST_HTML" ]; then - echo -e "${BLUE}Opening latest result in browser...${NC}" - open "$LATEST_HTML" - fi - fi - fi -} - -# Interactive profiling loop -while true; do - # Verify process is still running - if ! kill -0 "$PID" 2>/dev/null; then - echo -e "${RED}Warning: Process $PID is no longer running!${NC}" - echo "The process may have been terminated or restarted." - - # Try to find the same process again - NEW_PID=$(jps -l | grep "$PROCESS_NAME" | head -1 | cut -d' ' -f1) - if [ ! -z "$NEW_PID" ] && [ "$NEW_PID" != "$PID" ]; then - echo -e "${YELLOW}Found similar process with new PID: $NEW_PID${NC}" - read -p "Switch to the new PID? (y/N): " SWITCH_CONFIRM - if [[ "$SWITCH_CONFIRM" =~ ^[Yy]$ ]]; then - PID=$NEW_PID - echo -e "${GREEN}Switched to PID: $PID${NC}" - else - echo "Please restart the profiler with the correct PID." - exit 1 - fi - else - echo "Please restart the profiler when your application is running." - exit 1 - fi - fi - - show_profiling_menu - read -p "Select profiling option (0-11): " PROFILE_TYPE - execute_profiling "$PROFILE_TYPE" - - echo "" - echo -e "${BLUE}Press Enter to continue or Ctrl+C to exit...${NC}" - read -p "" -done \ No newline at end of file diff --git a/examples/spring-boot-performance-bottleneck-demo/profiler/scripts/java-profile.sh b/examples/spring-boot-performance-bottleneck-demo/profiler/scripts/java-profile.sh deleted file mode 100755 index 389973a8..00000000 --- a/examples/spring-boot-performance-bottleneck-demo/profiler/scripts/java-profile.sh +++ /dev/null @@ -1,567 +0,0 @@ -#!/bin/bash - -# java-profile.sh - Automated Java profiling script - -set -e - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${GREEN}Java Application Profiler v4.0 - Interactive Mode${NC}" -echo "=================================================" - -# Step 0: Problem Identification -echo -e "${YELLOW}Step 0: Problem Identification${NC}" -echo "-----" -echo "What specific performance problem are you trying to solve?" -echo "" -echo -e "${BLUE}Problem Categories:${NC}" -echo " 1) Performance Bottlenecks (CPU hotspots, inefficient algorithms, unnecessary allocations, string operations)" -echo " 2) Memory-Related Problems (memory leaks, heap usage, object retention, off-heap issues)" -echo " 3) Concurrency/Threading Issues (lock contention, thread pool issues, deadlocks, context switching)" -echo " 4) Garbage Collection Problems (GC pressure, long pauses, generational issues)" -echo " 5) I/O and Network Bottlenecks (blocking operations, connection leaks, serialization issues)" -echo " 0) Not sure / General performance analysis" -echo "" - -while true; do - read -p "Select the problem category you're investigating (0-5): " PROBLEM_SELECTION - - if [[ "$PROBLEM_SELECTION" =~ ^[0-5]$ ]]; then - case $PROBLEM_SELECTION in - 1) PROBLEM_CATEGORY="Performance Bottlenecks"; SUGGESTED_PROFILE="CPU" ;; - 2) PROBLEM_CATEGORY="Memory-Related Problems"; SUGGESTED_PROFILE="Memory" ;; - 3) PROBLEM_CATEGORY="Concurrency/Threading Issues"; SUGGESTED_PROFILE="Lock/Threading" ;; - 4) PROBLEM_CATEGORY="Garbage Collection Problems"; SUGGESTED_PROFILE="GC Analysis" ;; - 5) PROBLEM_CATEGORY="I/O and Network Bottlenecks"; SUGGESTED_PROFILE="I/O Analysis" ;; - 0) PROBLEM_CATEGORY="General Analysis"; SUGGESTED_PROFILE="CPU" ;; - esac - - echo -e "${GREEN}Selected problem category: $PROBLEM_CATEGORY${NC}" - echo -e "${GREEN}Suggested profiling approach: $SUGGESTED_PROFILE${NC}" - echo "" - break - else - echo -e "${RED}Invalid selection. Please choose a number between 0 and 5.${NC}" - fi -done - -# Step 1: List Java processes and handle selection -echo -e "${YELLOW}Step 1: Available Java Processes${NC}" -echo "-----" - -# Get list of Java processes -JAVA_PROCESSES=$(jps -l | grep -v "Jps$") - -if [ -z "$JAVA_PROCESSES" ]; then - echo -e "${RED}No Java processes found running on this system.${NC}" - echo "Please start your Spring Boot application first:" - echo " cd examples/spring-boot-memory-leak-demo" - echo " ./mvnw spring-boot:run" - echo "" - echo "Then try running this profiler again." - exit 1 -fi - -# Count the number of processes -PROCESS_COUNT=$(echo "$JAVA_PROCESSES" | wc -l | xargs) - -if [ "$PROCESS_COUNT" -eq 1 ]; then - # Only one process found, auto-select it - PID=$(echo "$JAVA_PROCESSES" | cut -d' ' -f1) - PROCESS_NAME=$(echo "$JAVA_PROCESSES" | cut -d' ' -f2-) - echo -e "${GREEN}Found single Java process:${NC}" - echo " PID: $PID" - echo " Name: $PROCESS_NAME" - echo "" - read -p "Do you want to profile this process? (y/N): " CONFIRM - if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then - echo "Profiling cancelled by user." - exit 0 - fi -else - # Multiple processes found, provide selection menu - echo -e "${GREEN}Found $PROCESS_COUNT Java processes:${NC}" - echo "" - - # Create numbered list - i=1 - declare -a PIDS - declare -a NAMES - - while IFS= read -r line; do - pid=$(echo "$line" | cut -d' ' -f1) - name=$(echo "$line" | cut -d' ' -f2-) - PIDS[$i]=$pid - NAMES[$i]=$name - echo " $i) PID: $pid - $name" - ((i++)) - done <<< "$JAVA_PROCESSES" - - echo "" - echo "0) Manual PID entry" - echo "" - - # Get user selection - while true; do - read -p "Select a process to profile (0-$PROCESS_COUNT): " SELECTION - - if [[ "$SELECTION" =~ ^[0-9]+$ ]]; then - if [ "$SELECTION" -eq 0 ]; then - # Manual PID entry - read -p "Enter the PID of the Java process to profile: " PID - break - elif [ "$SELECTION" -ge 1 ] && [ "$SELECTION" -le "$PROCESS_COUNT" ]; then - # Valid selection - PID=${PIDS[$SELECTION]} - PROCESS_NAME=${NAMES[$SELECTION]} - echo -e "${GREEN}Selected process:${NC}" - echo " PID: $PID" - echo " Name: $PROCESS_NAME" - break - else - echo -e "${RED}Invalid selection. Please choose a number between 0 and $PROCESS_COUNT.${NC}" - fi - else - echo -e "${RED}Invalid input. Please enter a number.${NC}" - fi - done -fi - -# Validate PID -if ! kill -0 "$PID" 2>/dev/null; then - echo -e "${RED}Error: Process $PID does not exist or is not accessible${NC}" - exit 1 -fi - -# Double-check if it's a Java process (for manual entries) -if ! jps | grep -q "^$PID "; then - echo -e "${RED}Error: Process $PID is not a Java process${NC}" - exit 1 -fi - -echo -e "${GREEN}Ready to profile process $PID${NC}" - -# Step 2: Determine profiler directory (default to current script's parent directory) -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROFILER_DIR="$(dirname "$SCRIPT_DIR")" - -echo -e "${YELLOW}Step 2: Profiler Directory Setup${NC}" -echo "-----" -echo -e "${GREEN}Using profiler directory: $PROFILER_DIR${NC}" - -# Step 3: Detect OS and download profiler -echo -e "${YELLOW}Step 3: Setting up async-profiler${NC}" -echo "-----" - -detect_os_arch() { - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - ARCH=$(uname -m) - - case "$OS" in - linux*) - case "$ARCH" in - x86_64) PLATFORM="linux-x64" ;; - aarch64|arm64) PLATFORM="linux-arm64" ;; - *) echo -e "${RED}Unsupported architecture: $ARCH${NC}"; exit 1 ;; - esac - ;; - darwin*) - case "$ARCH" in - x86_64|arm64) PLATFORM="macos" ;; - *) echo -e "${RED}Unsupported architecture: $ARCH${NC}"; exit 1 ;; - esac - ;; - *) - echo -e "${RED}Unsupported operating system: $OS${NC}" - exit 1 - ;; - esac - - echo -e "${GREEN}Detected platform: $PLATFORM${NC}" -} - -download_profiler() { - local platform=$1 - local profiler_dir=$2 - local version="4.0" - - # For macOS, v4.0 uses .zip format, Linux still uses .tar.gz - if [[ "$platform" == "macos" ]]; then - local filename="async-profiler-$version-$platform.zip" - local extract_cmd="unzip -q" - else - local filename="async-profiler-$version-$platform.tar.gz" - local extract_cmd="tar -xzf" - fi - - local url="https://github.com/jvm-profiling-tools/async-profiler/releases/download/v$version/$filename" - - if [ ! -d "$profiler_dir/current" ]; then - echo "Downloading async-profiler..." - echo "URL: $url" - mkdir -p "$profiler_dir" - cd "$profiler_dir" - - # Remove any failed downloads - rm -f async-profiler-*.tar.gz async-profiler-*.zip 2>/dev/null - - if command -v curl >/dev/null 2>&1; then - curl -L -o "$filename" "$url" - elif command -v wget >/dev/null 2>&1; then - wget -O "$filename" "$url" - else - echo -e "${RED}Error: Neither curl nor wget is available${NC}" - exit 1 - fi - - # Check if download was successful - if [[ ! -f "$filename" ]]; then - echo -e "${RED}Download failed: File $filename not found${NC}" - exit 1 - fi - - # Check file size (should be larger than 1MB for a valid download) - local file_size - if [[ "$OSTYPE" == "darwin"* ]]; then - file_size=$(stat -f%z "$filename" 2>/dev/null || echo "0") - else - file_size=$(stat -c%s "$filename" 2>/dev/null || echo "0") - fi - - if [[ "$file_size" -lt 100000 ]]; then # Less than 100KB - echo -e "${RED}Download failed: File is too small ($file_size bytes). This usually means a redirect or error page was downloaded.${NC}" - echo -e "${YELLOW}Contents of downloaded file:${NC}" - head -10 "$filename" 2>/dev/null || echo "Cannot read file" - rm -f "$filename" - exit 1 - fi - - # Extract the archive - echo "Extracting $filename..." - $extract_cmd "$filename" - - if [[ $? -ne 0 ]]; then - echo -e "${RED}Failed to extract $filename${NC}" - rm -f "$filename" - exit 1 - fi - - # Create symbolic link - ln -sf "async-profiler-$version-$platform" current - - # Clean up archive - rm -f "$filename" - - cd - > /dev/null - echo -e "${GREEN}Async-profiler downloaded successfully to $profiler_dir${NC}" - else - echo -e "${GREEN}Async-profiler already available at $profiler_dir${NC}" - fi -} - -detect_os_arch -download_profiler "$PLATFORM" "$PROFILER_DIR" - -# Create results directory if it doesn't exist (inside profiler directory) -RESULTS_DIR="$PROFILER_DIR/results" -mkdir -p "$RESULTS_DIR" - -# Function to check platform capabilities -check_platform_capabilities() { - echo -e "${BLUE}Platform Capabilities Check:${NC}" - echo "-----" - - if [[ "$OSTYPE" == "darwin"* ]]; then - echo -e "${YELLOW}Platform: macOS${NC}" - echo "āœ… CPU profiling (limited to user space)" - echo "āœ… Memory allocation profiling" - echo "āœ… Lock contention profiling" - echo "āœ… Wall clock profiling" - echo "āœ… JFR format" - echo "āŒ Native memory profiling (requires Linux perf events)" - echo "āŒ Hardware performance counters" - echo "" - echo -e "${BLUE}Note: macOS profiling is limited to user-space code only${NC}" - else - echo -e "${YELLOW}Platform: Linux${NC}" - echo "āœ… Full CPU profiling (user + kernel space)" - echo "āœ… Memory allocation profiling" - echo "āœ… Native memory profiling" - echo "āœ… Lock contention profiling" - echo "āœ… Wall clock profiling" - echo "āœ… Hardware performance counters" - echo "āœ… JFR format" - - # Check if running as root or with proper permissions - if [[ $EUID -eq 0 ]]; then - echo "āœ… Running with elevated privileges" - else - echo -e "${YELLOW}āš ļø For optimal profiling, consider running with elevated privileges${NC}" - fi - fi - echo "" -} - -# Check platform capabilities -check_platform_capabilities - -# Function to show profiling menu -show_profiling_menu() { - echo "" - echo -e "${BLUE}===========================================${NC}" - echo -e "${YELLOW}Profiling Options for PID: $PID${NC}" - echo -e "${YELLOW}Process: $PROCESS_NAME${NC}" - echo -e "${YELLOW}Problem Category: $PROBLEM_CATEGORY${NC}" - echo -e "${YELLOW}Suggested Approach: $SUGGESTED_PROFILE${NC}" - echo -e "${BLUE}===========================================${NC}" - - # Show recommended options first based on problem category - case $SUGGESTED_PROFILE in - "Memory") - echo -e "${GREEN}*** RECOMMENDED FOR MEMORY LEAK DETECTION ***${NC}" - echo "2. Memory Allocation Profiling (30s) ⭐" - echo "10. Memory Leak Detection (5min) ⭐" - echo "6. Native Memory Profiling (30s) ⭐" - echo "" - echo -e "${BLUE}Other Options:${NC}" - ;; - "CPU") - echo -e "${GREEN}*** RECOMMENDED FOR YOUR PROBLEM ***${NC}" - echo "1. CPU Profiling (30s) ⭐" - echo "8. Custom Duration CPU Profiling ⭐" - echo "" - echo -e "${BLUE}Other Options:${NC}" - ;; - "Lock/Threading") - echo -e "${GREEN}*** RECOMMENDED FOR YOUR PROBLEM ***${NC}" - echo "3. Lock Contention Profiling (30s) ⭐" - echo "4. Wall Clock Profiling (30s) ⭐" - echo "" - echo -e "${BLUE}Other Options:${NC}" - ;; - "GC Analysis") - echo -e "${GREEN}*** RECOMMENDED FOR YOUR PROBLEM ***${NC}" - echo "5. Interactive Heatmap (60s) ⭐" - echo "2. Memory Allocation Profiling (30s) ⭐" - echo "" - echo -e "${BLUE}Other Options:${NC}" - ;; - "I/O Analysis") - echo -e "${GREEN}*** RECOMMENDED FOR YOUR PROBLEM ***${NC}" - echo "4. Wall Clock Profiling (30s) ⭐" - echo "5. Interactive Heatmap (60s) ⭐" - echo "" - echo -e "${BLUE}Other Options:${NC}" - ;; - esac - - echo "1. CPU Profiling (30s)" - echo "2. Memory Allocation Profiling (30s)" - echo "3. Lock Contention Profiling (30s)" - echo "4. Wall Clock Profiling (30s)" - echo "5. Interactive Heatmap (60s) - NEW in v4.0" - if [[ "$OSTYPE" == "darwin"* ]]; then - echo "6. Memory Profiling (30s) - macOS compatible" - else - echo "6. Native Memory Profiling (30s) - NEW in v4.0 (Linux)" - fi - echo "7. Inverted Flame Graph (30s) - NEW in v4.0" - echo "8. Custom Duration CPU Profiling" - echo "9. View recent results" - echo "10. Memory Leak Detection (5min)" - echo "11. Complete Memory Analysis Workflow" - echo "0. Exit profiler" - echo -e "${BLUE}===========================================${NC}" -} - -# Function to handle profiling errors -handle_profiling_error() { - local exit_code=$1 - local profiling_type=$2 - - if [[ $exit_code -ne 0 ]]; then - echo -e "${RED}Profiling failed with exit code: $exit_code${NC}" - echo -e "${YELLOW}Common solutions:${NC}" - - if [[ "$OSTYPE" == "darwin"* ]]; then - echo "• On macOS, some profiling modes have limitations" - echo "• Try using --all-user flag for CPU profiling" - echo "• Use allocation profiling instead of native memory profiling" - else - echo "• Check if you have sufficient permissions:" - echo " sudo sysctl kernel.perf_event_paranoid=1" - echo " sudo sysctl kernel.kptr_restrict=0" - echo "• Try running with sudo for full system profiling" - echo "• Use --all-user flag to profile only user-space code" - fi - - echo -e "${BLUE}Alternative: Try option 2 (Memory Allocation Profiling) which works on all platforms${NC}" - return 1 - fi - return 0 -} - -# Function to execute profiling -execute_profiling() { - local option=$1 - - case $option in - 1) - echo -e "${GREEN}Starting CPU profiling for 30 seconds...${NC}" - # On macOS, add --all-user flag to avoid kernel profiling issues - if [[ "$OSTYPE" == "darwin"* ]]; then - "$PROFILER_DIR/current/bin/asprof" -d 30 --all-user -f "$RESULTS_DIR/cpu-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - handle_profiling_error $? "CPU (macOS)" - else - "$PROFILER_DIR/current/bin/asprof" -d 30 -f "$RESULTS_DIR/cpu-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - handle_profiling_error $? "CPU (Linux)" - fi - ;; - 2) - echo -e "${GREEN}Starting memory allocation profiling for 30 seconds...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 30 -f "$RESULTS_DIR/allocation-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - ;; - 3) - echo -e "${GREEN}Starting lock contention profiling for 30 seconds...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e lock -d 30 -f "$RESULTS_DIR/lock-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - ;; - 4) - echo -e "${GREEN}Starting wall clock profiling for 30 seconds...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e wall -d 30 -f "$RESULTS_DIR/wall-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - ;; - 5) - echo -e "${GREEN}Starting interactive heatmap profiling for 60 seconds...${NC}" - TIMESTAMP=$(date +%Y%m%d-%H%M%S) - JFR_FILE="$RESULTS_DIR/profile-$TIMESTAMP.jfr" - HEATMAP_FILE="$RESULTS_DIR/heatmap-cpu-$TIMESTAMP.html" - - echo -e "${BLUE}Step 1: Generating JFR recording...${NC}" - "$PROFILER_DIR/current/bin/asprof" -d 60 -o jfr -f "$JFR_FILE" "$PID" - - echo -e "${BLUE}Step 2: Converting JFR to heatmap...${NC}" - "$PROFILER_DIR/current/bin/jfrconv" --cpu -o heatmap "$JFR_FILE" "$HEATMAP_FILE" - echo -e "${GREEN}Heatmap generated: $HEATMAP_FILE${NC}" - ;; - 6) - echo -e "${GREEN}Starting native memory profiling for 30 seconds...${NC}" - # Check if we're on macOS (Darwin) - if [[ "$OSTYPE" == "darwin"* ]]; then - echo -e "${YELLOW}Note: Native memory profiling requires Linux perf events.${NC}" - echo -e "${YELLOW}On macOS, using allocation profiling as alternative...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 30 --alloc 512k -f "$RESULTS_DIR/allocation-memory-$(date +%Y%m%d-%H%M%S).html" "$PID" - else - # Check if perf events are available on Linux - if ! "$PROFILER_DIR/current/bin/asprof" check native "$PID" 2>/dev/null; then - echo -e "${YELLOW}Native memory profiling not available. Using allocation profiling instead...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 30 --alloc 512k -f "$RESULTS_DIR/allocation-memory-$(date +%Y%m%d-%H%M%S).html" "$PID" - else - "$PROFILER_DIR/current/bin/asprof" -e native -d 30 -f "$RESULTS_DIR/native-memory-$(date +%Y%m%d-%H%M%S).html" "$PID" - fi - fi - ;; - 7) - echo -e "${GREEN}Starting inverted flame graph profiling for 30 seconds...${NC}" - "$PROFILER_DIR/current/bin/asprof" -d 30 --inverted -f "$RESULTS_DIR/inverted-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - ;; - 8) - read -p "Enter duration in seconds: " DURATION - echo -e "${GREEN}Starting CPU profiling for $DURATION seconds...${NC}" - # On macOS, add --all-user flag to avoid kernel profiling issues - if [[ "$OSTYPE" == "darwin"* ]]; then - "$PROFILER_DIR/current/bin/asprof" -d "$DURATION" --all-user -f "$RESULTS_DIR/cpu-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - else - "$PROFILER_DIR/current/bin/asprof" -d "$DURATION" -f "$RESULTS_DIR/cpu-flamegraph-$(date +%Y%m%d-%H%M%S).html" "$PID" - fi - ;; - 9) - echo -e "${YELLOW}Recent profiling results in $RESULTS_DIR:${NC}" - ls -lat "$RESULTS_DIR"/*.html "$RESULTS_DIR"/*.jfr 2>/dev/null | head -10 || echo "No profiling files found" - return - ;; - 10) - echo -e "${GREEN}Starting Memory Leak Detection (5 minutes)...${NC}" - echo -e "${YELLOW}This will run for 5 minutes to capture memory leak patterns${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 300 --alloc 1m -f "$RESULTS_DIR/memory-leak-$(date +%Y%m%d-%H%M%S).html" "$PID" - ;; - 11) - echo -e "${GREEN}Starting Complete Memory Analysis Workflow...${NC}" - TIMESTAMP=$(date +%Y%m%d-%H%M%S) - - echo -e "${BLUE}Step 1: Quick memory allocation baseline (30s)...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 30 -f "$RESULTS_DIR/memory-baseline-$TIMESTAMP.html" "$PID" - - echo -e "${BLUE}Step 2: Heap profiling (60s)...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 60 -f "$RESULTS_DIR/heap-analysis-$TIMESTAMP.html" "$PID" - - echo -e "${BLUE}Step 3: Memory leak detection (5min)...${NC}" - "$PROFILER_DIR/current/bin/asprof" -e alloc -d 300 --alloc 1m -f "$RESULTS_DIR/memory-leak-complete-$TIMESTAMP.html" "$PID" - - echo -e "${GREEN}Complete memory analysis finished! Check these files:${NC}" - echo "- memory-baseline-$TIMESTAMP.html (30s baseline)" - echo "- heap-analysis-$TIMESTAMP.html (60s detailed heap)" - echo "- memory-leak-complete-$TIMESTAMP.html (5min leak detection)" - ;; - 0) - echo -e "${GREEN}Exiting profiler. Goodbye!${NC}" - exit 0 - ;; - *) - echo -e "${RED}Invalid option${NC}" - return - ;; - esac - - if [ $option -ne 9 ] && [ $option -ne 0 ]; then - echo -e "${GREEN}Profiling completed!${NC}" - echo -e "${YELLOW}Generated files in $RESULTS_DIR:${NC}" - ls -lat "$RESULTS_DIR"/*.html "$RESULTS_DIR"/*.jfr 2>/dev/null | head -5 || echo "No profiling files found" - - # Automatically open the latest file if on macOS - if [[ "$OSTYPE" == "darwin"* ]]; then - LATEST_HTML=$(ls -t "$RESULTS_DIR"/*.html 2>/dev/null | head -1) - if [ ! -z "$LATEST_HTML" ]; then - echo -e "${BLUE}Opening latest result in browser...${NC}" - open "$LATEST_HTML" - fi - fi - fi -} - -# Interactive profiling loop -while true; do - # Verify process is still running - if ! kill -0 "$PID" 2>/dev/null; then - echo -e "${RED}Warning: Process $PID is no longer running!${NC}" - echo "The process may have been terminated or restarted." - - # Try to find the same process again - NEW_PID=$(jps -l | grep "$PROCESS_NAME" | head -1 | cut -d' ' -f1) - if [ ! -z "$NEW_PID" ] && [ "$NEW_PID" != "$PID" ]; then - echo -e "${YELLOW}Found similar process with new PID: $NEW_PID${NC}" - read -p "Switch to the new PID? (y/N): " SWITCH_CONFIRM - if [[ "$SWITCH_CONFIRM" =~ ^[Yy]$ ]]; then - PID=$NEW_PID - echo -e "${GREEN}Switched to PID: $PID${NC}" - else - echo "Please restart the profiler with the correct PID." - exit 1 - fi - else - echo "Please restart the profiler when your application is running." - exit 1 - fi - fi - - show_profiling_menu - read -p "Select profiling option (0-11): " PROFILE_TYPE - execute_profiling "$PROFILE_TYPE" - - echo "" - echo -e "${BLUE}Press Enter to continue or Ctrl+C to exit...${NC}" - read -p "" -done \ No newline at end of file