├── .github └── workflows │ ├── build-async-profiler.yml │ ├── release.yml │ ├── sonar-cloud.yml │ └── test.yml ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── HOW_IT_WORKS.md ├── LICENSE ├── Makefile ├── README.md ├── README_ZH.md ├── bin └── install.sh ├── coverage-report-aggregate └── pom.xml ├── docs ├── app-can-not-invoke-profiler-class.svg ├── app-invoke-profiler-class.svg ├── arch-of-classloader.svg ├── class-confict.svg ├── details-of-method-invoke.png ├── entire-flow.svg ├── full-flame-graph.png ├── spring-bean-initialization.png ├── spring-bean-timeline.png ├── spring-startup-cli_EN.svg ├── spring-startup-cli_ZH.svg ├── startup-using-idea.png └── unused-jars.png ├── pom.xml ├── spring-async-bean-core ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── github │ │ └── linyimin0812 │ │ └── async │ │ ├── bean │ │ └── AsyncInitBeanFinder.java │ │ ├── config │ │ ├── AsyncBeanProperties.java │ │ └── AsyncConfig.java │ │ ├── executor │ │ ├── AsyncTaskExecutor.java │ │ └── NamedThreadFactory.java │ │ ├── listener │ │ └── AsyncTaskExecutionListener.java │ │ ├── processor │ │ ├── AsyncBeanPriorityLoadPostProcessor.java │ │ ├── AsyncProxyBeanPostProcessor.java │ │ └── InstantiationAwareBeanPostProcessorAdapter.java │ │ └── utils │ │ └── BeanDefinitionUtil.java │ └── test │ ├── java │ └── io │ │ └── github │ │ └── linyimin0812 │ │ └── async │ │ ├── bean │ │ └── AsyncInitBeanFinderSpec.groovy │ │ ├── config │ │ ├── AsyncBeanPropertiesSpec.groovy │ │ └── AsyncConfigSpec.groovy │ │ ├── executor │ │ ├── AsyncTaskExecutorSpec.groovy │ │ └── NamedThreadFactorySpec.groovy │ │ ├── listener │ │ └── AsyncTaskExecutionListenerSpec.groovy │ │ ├── processor │ │ ├── AsyncBeanPriorityLoadPostProcessorSpec.groovy │ │ └── AsyncProxyBeanPostProcessorSpec.groovy │ │ ├── springbeans │ │ ├── SpringFactory.java │ │ ├── TestComponentBean.java │ │ └── TestXmlBean.java │ │ └── utils │ │ └── BeanDefinitionUtilSpec.groovy │ └── resources │ ├── application.properties │ └── bean-context.xml ├── spring-async-bean-starter ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── linyimin0812 │ │ │ └── async │ │ │ ├── AsyncBeanAutoConfiguration.java │ │ │ └── AsyncSpringBeanProperties.java │ └── resources │ │ └── META-INF │ │ ├── spring.factories │ │ └── spring │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ └── test │ └── java │ └── io │ └── github │ └── linyimin0812 │ └── async │ ├── AsyncBeanAutoConfigurationSpec.groovy │ └── AsyncSpringBeanPropertiesSpec.groovy ├── spring-profiler-agent ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── github │ │ └── linyimin0812 │ │ └── profiler │ │ └── agent │ │ ├── ProfilerAgentBoostrap.java │ │ └── ProfilerAgentClassLoader.java │ └── test │ ├── java │ └── io │ │ └── github │ │ └── linyimin0812 │ │ └── profiler │ │ └── agent │ │ └── ProfilerAgentClassLoaderSpec.groovy │ └── resources │ └── spring-profiler-api.jar ├── spring-profiler-api ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── github │ │ └── linyimin0812 │ │ └── profiler │ │ └── api │ │ ├── EventListener.java │ │ ├── Lifecycle.java │ │ └── event │ │ ├── AtEnterEvent.java │ │ ├── AtExceptionExitEvent.java │ │ ├── AtExitEvent.java │ │ ├── Event.java │ │ └── InvokeEvent.java │ └── test │ └── java │ └── io │ └── github │ └── linyimin0812 │ └── profiler │ └── api │ └── event │ └── AtEnterEventSpec.groovy ├── spring-profiler-bridge ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── github │ │ └── linyimin0812 │ │ └── Bridge.java │ └── test │ └── java │ └── io │ └── github │ └── linyimin0812 │ └── BridgeSpec.groovy ├── spring-profiler-common ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── github │ │ └── linyimin0812 │ │ └── profiler │ │ └── common │ │ ├── instruction │ │ └── InstrumentationHolder.java │ │ ├── logger │ │ ├── Level.java │ │ ├── LogFactory.java │ │ ├── Logger.java │ │ └── LoggerName.java │ │ ├── settings │ │ └── ProfilerSettings.java │ │ ├── ui │ │ ├── BeanInitResult.java │ │ ├── MethodInvokeDetail.java │ │ ├── MethodInvokeMetrics.java │ │ ├── StartupVO.java │ │ └── Statistics.java │ │ └── utils │ │ ├── AgentHomeUtil.java │ │ ├── GsonUtil.java │ │ ├── IpUtil.java │ │ ├── NameUtil.java │ │ └── OSUtil.java │ └── test │ ├── java │ └── io │ │ └── github │ │ └── linyimin0812 │ │ └── profiler │ │ └── common │ │ ├── instruction │ │ └── InstrumentationHolderSpec.groovy │ │ ├── logger │ │ ├── LogFactorySpec.groovy │ │ └── LoggerSpec.groovy │ │ ├── settings │ │ └── ProfilerSettingsSpec.groovy │ │ ├── ui │ │ ├── BeanInitResultSpec.groovy │ │ └── StartupVOSpec.groovy │ │ └── utils │ │ ├── AgentHomeUtilSpec.groovy │ │ ├── IpUtilSpec.groovy │ │ ├── NameUtilSpec.groovy │ │ └── OSUtilSpec.groovy │ └── resources │ └── spring-startup-analyzer.properties ├── spring-profiler-core ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── linyimin0812 │ │ │ └── profiler │ │ │ └── core │ │ │ ├── container │ │ │ └── IocContainer.java │ │ │ ├── enhance │ │ │ ├── EventDispatcher.java │ │ │ ├── Interceptor.java │ │ │ ├── Matcher.java │ │ │ └── ProfilerClassFileTransformer.java │ │ │ ├── http │ │ │ └── SimpleHttpServer.java │ │ │ └── monitor │ │ │ ├── ApplicationRunMonitor.java │ │ │ ├── StartupMonitor.java │ │ │ └── check │ │ │ ├── AppStatus.java │ │ │ ├── AppStatusCheckService.java │ │ │ ├── EndpointCheckService.java │ │ │ └── TimeoutCheckService.java │ └── resources │ │ ├── hyperapp.js │ │ ├── spring-startup-analyzer.properties │ │ ├── startup-analysis.html │ │ └── tailwind.js │ └── test │ ├── java │ └── io │ │ └── github │ │ └── linyimin0812 │ │ └── profiler │ │ └── core │ │ ├── container │ │ ├── EventListenerTest.java │ │ ├── IocContainerSpec.groovy │ │ └── LifecycleTest.java │ │ ├── enhance │ │ ├── EventDispatcherSpec.groovy │ │ ├── MatcherSpec.groovy │ │ └── ProfilerClassFileTransformerSpec.groovy │ │ ├── http │ │ └── SimpleHttpServerSpec.groovy │ │ └── monitor │ │ ├── ApplicationRunMonitorSpec.groovy │ │ └── check │ │ ├── EndpointCheckServiceSpec.groovy │ │ └── TimeoutCheckServiceSpec.groovy │ └── resources │ ├── spring-startup-analyzer.properties │ └── src │ └── empty.txt ├── spring-profiler-extension ├── async-profiler │ ├── libasyncProfiler-linux-arm64.so │ ├── libasyncProfiler-linux-musl-arm64.so │ ├── libasyncProfiler-linux-musl-x64.so │ ├── libasyncProfiler-linux-x64.so │ └── libasyncProfiler-mac.so ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── linyimin0812 │ │ │ └── profiler │ │ │ └── extension │ │ │ └── enhance │ │ │ ├── invoke │ │ │ └── InvokeDetailListener.java │ │ │ ├── jar │ │ │ └── JarCollector.java │ │ │ ├── sample │ │ │ ├── AsyncProfilerListener.java │ │ │ ├── Profiler.java │ │ │ ├── asyncprofiler │ │ │ │ ├── AsyncProfiler.java │ │ │ │ └── one │ │ │ │ │ ├── AsyncProfiler.java │ │ │ │ │ ├── AsyncProfilerMXBean.java │ │ │ │ │ ├── Counter.java │ │ │ │ │ ├── Events.java │ │ │ │ │ └── package-info.java │ │ │ └── jvmprofiler │ │ │ │ ├── FlameGraph.java │ │ │ │ └── StacktraceProfiler.java │ │ │ └── springbean │ │ │ ├── BeanCreateListener.java │ │ │ └── PersistentThreadLocal.java │ └── resources │ │ └── flame-graph.html │ └── test │ ├── java │ └── io │ │ └── github │ │ └── linyimin0812 │ │ └── profiler │ │ └── extension │ │ └── enhance │ │ ├── invoke │ │ └── InvokeDetailListenerSpec.groovy │ │ ├── sample │ │ ├── AsyncProfilerListenerSpec.groovy │ │ └── jvmprofiler │ │ │ └── FlameGraphSpec.groovy │ │ └── springbean │ │ └── BeanCreateListenerSpec.groovy │ └── resources │ ├── flame-graph.html │ ├── profiler.txt │ └── spring-startup-analyzer.properties ├── spring-profiler-starter └── pom.xml └── spring-startup-cli ├── pom.xml └── src ├── main └── java │ └── io │ └── github │ └── linyimin0812 │ └── spring │ └── startup │ ├── cli │ ├── CliMain.java │ └── command │ │ ├── ClearScreen.java │ │ ├── CliCommands.java │ │ ├── Config.java │ │ ├── Configuration.java │ │ └── Reload.java │ ├── constant │ └── Constants.java │ ├── jdwp │ ├── JDWPClient.java │ └── command │ │ ├── AllClassesCommand.java │ │ ├── AllClassesReplyPackage.java │ │ ├── CommandCache.java │ │ ├── CommandPackage.java │ │ ├── Package.java │ │ ├── RedefineClassesCommand.java │ │ ├── RedefineClassesReplyPackage.java │ │ └── ReplyPackage.java │ ├── recompile │ ├── ModifiedFileProcessor.java │ └── ModifiedFileWatcher.java │ └── utils │ ├── GitUtil.java │ ├── ModuleUtil.java │ ├── OSUtil.java │ ├── ShellUtil.java │ └── StringUtil.java └── test └── java └── io └── github └── linyimin0812 └── spring └── startup ├── recompile └── ModifiedFileWatcherSpec.groovy └── utils ├── GitUtilSpec.groovy ├── ModuleUtilSpec.groovy ├── OSUtilSpec.groovy ├── ShellUtilSpec.groovy └── StringUtilSpec.groovy /.github/workflows/sonar-cloud.yml: -------------------------------------------------------------------------------- 1 | name: SonarCloud 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | types: [opened, synchronize, reopened] 8 | jobs: 9 | build: 10 | name: Build and analyze 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 16 | - name: Set up JDK 17 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: 17 20 | distribution: 'zulu' # Alternative distribution options are available. 21 | - name: Cache SonarCloud packages 22 | uses: actions/cache@v3 23 | with: 24 | path: ~/.sonar/cache 25 | key: ${{ runner.os }}-sonar 26 | restore-keys: ${{ runner.os }}-sonar 27 | - name: Import GPG secret key 28 | uses: crazy-max/ghaction-import-gpg@v5 29 | with: 30 | gpg_private_key: ${{ secrets.GPG_SECRET_KEY }} 31 | passphrase: ${{ secrets.GPG_PASSPHRASE }} 32 | - name: Cache Maven packages 33 | uses: actions/cache@v3 34 | with: 35 | path: ~/.m2 36 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 37 | restore-keys: ${{ runner.os }}-m2 38 | - name: Build and analyze 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 41 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 42 | run: mvn -B install verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=linyimin0812_spring-startup-analyzer -pl '!spring-startup-cli' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | !**/.flattened-pom.xml 34 | .flattened-pom.xml 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | 39 | ### Mac OS ### 40 | .DS_Store 41 | 42 | .idea 43 | 44 | logs -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | 本项目基于java8开发,所以需要提前安装JDK8 4 | 5 | 1. 下载源码 6 | 7 | ```shell 8 | git clone https://github.com/linyimin0812/spring-startup-analyzer.git 9 | cd spring-startup-analyzer 10 | ``` 11 | 12 | 2. 编译 13 | 14 | ```shell 15 | make all 16 | ``` 17 | 18 | # 其他依赖 19 | 20 | 本项目依赖了async-profiler,并对其进行了一些扩展 21 | 22 | ## async-profiler 23 | 24 | 指定线程名称采样,支持正则匹配 25 | 26 | ```shell 27 | git clone https://github.com/linyimin0812/async-profier.git 28 | cd async-profier 29 | 30 | git checkout feat/20230505_support_sample_specify_thread_1 31 | 32 | ``` 33 | 34 | 切换到`feat/20230505_support_sample_specify_thread_1`分支,修改完成后,进行编译 35 | 36 | ```shell 37 | make 38 | ``` 39 | 40 | 编译结果会放到build文件夹下,需要将so文件移动到`spring-startup-analyzer`项目文件中 41 | 42 | ```shell 43 | # linux 44 | mv ./build/libasyncProfiler.so ${dir}/spring-startup-analyzer/spring-profiler-extension/async-profiler/libasyncProfiler-linux-x64.so 45 | # mac 46 | mv ./build/libasyncProfiler.so ${dir}/spring-startup-analyzer/spring-profiler-extension/async-profiler/libasyncProfiler-mac.so 47 | ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | WORKDIR /spring-startup-analyzer 4 | 5 | COPY ./build/ /spring-startup-analyzer/ -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MVN=mvn 2 | PROJECT_NAME=spring-startup-analyzer 3 | INSTALL_DIR=${HOME}/${PROJECT_NAME} 4 | 5 | .PHONY: clean 6 | clean: 7 | ifeq ($(strip $(VERSION)),) 8 | $(MVN) clean 9 | cd ./spring-profiler-extension && $(MVN) clean 10 | else 11 | $(MVN) clean -Drevision=$(VERSION) 12 | cd ./spring-profiler-extension && $(MVN) clean -Drevision=$(VERSION) 13 | endif 14 | 15 | .PHONY: clean-skip-tests 16 | clean-skip-tests: 17 | ifeq ($(strip $(VERSION)),) 18 | $(MVN) clean -Dmaven.test.skip=true 19 | cd ./spring-profiler-extension && $(MVN) clean -Dmaven.test.skip=true 20 | else 21 | $(MVN) clean -Drevision=$(VERSION) -Dmaven.test.skip=true 22 | cd ./spring-profiler-extension && $(MVN) clean -Drevision=$(VERSION) -Dmaven.test.skip=true 23 | endif 24 | 25 | .PHONY: package 26 | package: clean install 27 | ifeq ($(strip $(VERSION)),) 28 | ${MVN} package -pl '!coverage-report-aggregate,!spring-startup-cli' 29 | cd ./spring-profiler-extension && ${MVN} package 30 | else 31 | ${MVN} package -Drevision=$(VERSION) -pl '!coverage-report-aggregate,!spring-startup-cli' 32 | cd ./spring-profiler-extension && ${MVN} package -Drevision=$(VERSION) 33 | endif 34 | mkdir -p ./build && rm -rf ./build/* && cp -r ~/${PROJECT_NAME}/* ./build/ && rm -rf ./build/${PROJECT_NAME}.tar.gz 35 | 36 | 37 | .PHONY: package-skip-tests 38 | package-skip-tests: clean-skip-tests install-skip-tests 39 | ifeq ($(strip $(VERSION)),) 40 | ${MVN} package -pl '!coverage-report-aggregate,!spring-startup-cli' -Dmaven.test.skip=true 41 | cd ./spring-profiler-extension && ${MVN} package -Dmaven.test.skip=true 42 | else 43 | ${MVN} package -Drevision=$(VERSION) -pl '!coverage-report-aggregate,!spring-startup-cli' -Dmaven.test.skip=true 44 | cd ./spring-profiler-extension && ${MVN} package -Drevision=$(VERSION) -Dmaven.test.skip=true 45 | endif 46 | mkdir -p ./build && rm -rf ./build/* && cp -r ~/${PROJECT_NAME}/* ./build/ && rm -rf ./build/${PROJECT_NAME}.tar.gz 47 | 48 | 49 | .PHONY: install 50 | install: clean 51 | ifeq ($(strip $(VERSION)),) 52 | ${MVN} install -pl '!coverage-report-aggregate,!spring-startup-cli' 53 | cd ./spring-profiler-extension && ${MVN} install 54 | else 55 | ${MVN} install -Drevision=$(VERSION) -pl '!coverage-report-aggregate,!spring-startup-cli' 56 | cd ./spring-profiler-extension && ${MVN} install -Drevision=$(VERSION) 57 | endif 58 | 59 | .PHONY: install-skip-tests 60 | install-skip-tests: clean-skip-tests 61 | ifeq ($(strip $(VERSION)),) 62 | ${MVN} install -pl '!coverage-report-aggregate,!spring-startup-cli' -Dmaven.test.skip=true 63 | cd ./spring-profiler-extension && ${MVN} install -Dmaven.test.skip=true 64 | else 65 | ${MVN} install -Drevision=$(VERSION) -pl '!coverage-report-aggregate,!spring-startup-cli' -Dmaven.test.skip=true 66 | cd ./spring-profiler-extension && ${MVN} install -Drevision=$(VERSION) -Dmaven.test.skip=true 67 | endif 68 | 69 | .PHONY: deploy 70 | deploy: clean 71 | ifeq ($(strip $(VERSION)),) 72 | ${MVN} deploy -pl '!coverage-report-aggregate,!spring-startup-cli' 73 | cd ./spring-profiler-extension && ${MVN} deploy 74 | else 75 | ${MVN} deploy -Drevision=$(VERSION) -pl '!coverage-report-aggregate,!spring-startup-cli' 76 | cd ./spring-profiler-extension && ${MVN} deploy -Drevision=$(VERSION) 77 | endif 78 | 79 | .PHONY: deploy-skip-tests 80 | deploy-skip-tests: clean-skip-tests 81 | ifeq ($(strip $(VERSION)),) 82 | ${MVN} deploy -pl '!coverage-report-aggregate,!spring-startup-cli' -Dmaven.test.skip=true 83 | cd ./spring-profiler-extension && ${MVN} deploy -Dmaven.test.skip=true 84 | else 85 | ${MVN} deploy -Drevision=$(VERSION) -pl '!coverage-report-aggregate,!spring-startup-cli' 86 | cd ./spring-profiler-extension && ${MVN} deploy -Drevision=$(VERSION) -Dmaven.test.skip=true 87 | endif 88 | 89 | .PHONY: docker-build 90 | docker-build: package 91 | ifeq ($(strip $(VERSION)),) 92 | docker build --tag ${PROJECT_NAME}:latest . 93 | else 94 | docker build --tag ${PROJECT_NAME}:${VERSION} . 95 | endif 96 | 97 | 98 | .PHONY: tar 99 | tar: 100 | cd ${INSTALL_DIR} && tar -zcvf ${PROJECT_NAME}.tar.gz ./lib/ ./config/ ./template 101 | 102 | .PHONY: all 103 | all: clean install package tar 104 | 105 | .PHONY: all-skip-tests 106 | all-skip-tests: clean-skip-tests install-skip-tests package-skip-tests tar -------------------------------------------------------------------------------- /bin/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxf -o pipefail 4 | 5 | PRODUCT_NAME=spring-startup-analyzer 6 | LAST_TAG=${1:-v3.1.4} 7 | PROFILER_HOME=${HOME}/spring-startup-analyzer 8 | 9 | check_permission() { 10 | if [ ! -w "${HOME}" ]; then 11 | echo "permission denied, ${HOME} is not writable." && exit 1 12 | fi 13 | } 14 | 15 | download_jar() { 16 | 17 | if [ ! -d "${PROFILER_HOME}" ]; then 18 | mkdir -p "${PROFILER_HOME}" 19 | fi 20 | 21 | download_url="https://github.com/linyimin0812/${PRODUCT_NAME}/releases/download/${LAST_TAG}/${PRODUCT_NAME}.tar.gz" 22 | 23 | echo "Download spring-startup-analyzer from: ${download_url}" 24 | 25 | curl -#Lkf \ 26 | -o "${PROFILER_HOME}/${PRODUCT_NAME}.tar.gz" \ 27 | "${download_url}" \ 28 | || (echo "Download spring-startup-analyzer from: ${download_url} error, please install manually!!!" && exit 1) 29 | 30 | } 31 | 32 | extract_jar() { 33 | if [ ! -f "${PROFILER_HOME}/${PRODUCT_NAME}.tar.gz" ]; then 34 | echo "${PROFILER_HOME}/${PRODUCT_NAME}.tar.gz does not exist, please install manually!!!" && exit 1 35 | fi 36 | 37 | cd "${PROFILER_HOME}" || (echo "cd ${PROFILER_HOME} error." && exit 1) 38 | 39 | tar -zxvf "${PROFILER_HOME}/${PRODUCT_NAME}.tar.gz" 40 | 41 | } 42 | 43 | main() { 44 | 45 | check_permission 46 | 47 | download_jar 48 | 49 | extract_jar 50 | 51 | rm -rf "${PROFILER_HOME}/${PRODUCT_NAME}.tar.gz" 52 | 53 | echo "${PRODUCT_NAME} install success. install directory: ${PROFILER_HOME}" 54 | 55 | } 56 | 57 | main 58 | -------------------------------------------------------------------------------- /coverage-report-aggregate/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.github.linyimin0812 8 | spring-startup-analyzer 9 | ${revision} 10 | 11 | 12 | coverage-report-aggregate 13 | coverage-report-aggregate 14 | Aggregate Coverage Report 15 | 16 | 17 | linyimin-bupt 18 | https://sonarcloud.io 19 | 20 | ${project.basedir}/report-aggregate/target/site/ 21 | jacoco-aggregate/jacoco.xml 22 | 23 | 24 | 25 | 26 | 27 | 28 | io.github.linyimin0812 29 | spring-profiler-agent 30 | ${revision} 31 | 32 | 33 | 34 | io.github.linyimin0812 35 | spring-profiler-common 36 | ${revision} 37 | 38 | 39 | 40 | io.github.linyimin0812 41 | spring-profiler-bridge 42 | ${revision} 43 | 44 | 45 | 46 | io.github.linyimin0812 47 | spring-profiler-core 48 | ${revision} 49 | 50 | 51 | 52 | io.github.linyimin0812 53 | spring-async-bean-core 54 | ${revision} 55 | 56 | 57 | 58 | io.github.linyimin0812 59 | spring-profiler-extension 60 | ${revision} 61 | 62 | 63 | 64 | io.github.linyimin0812 65 | spring-async-bean-starter 66 | ${revision} 67 | 68 | 69 | 70 | io.github.linyimin0812 71 | spring-profiler-api 72 | ${revision} 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.jacoco 80 | jacoco-maven-plugin 81 | 0.8.10 82 | 83 | 84 | report-aggregate 85 | verify 86 | 87 | report-aggregate 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /docs/details-of-method-invoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/ecee7021024d3ade8f5fb1ab0bc84ad6a551b15e/docs/details-of-method-invoke.png -------------------------------------------------------------------------------- /docs/full-flame-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/ecee7021024d3ade8f5fb1ab0bc84ad6a551b15e/docs/full-flame-graph.png -------------------------------------------------------------------------------- /docs/spring-bean-initialization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/ecee7021024d3ade8f5fb1ab0bc84ad6a551b15e/docs/spring-bean-initialization.png -------------------------------------------------------------------------------- /docs/spring-bean-timeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/ecee7021024d3ade8f5fb1ab0bc84ad6a551b15e/docs/spring-bean-timeline.png -------------------------------------------------------------------------------- /docs/startup-using-idea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/ecee7021024d3ade8f5fb1ab0bc84ad6a551b15e/docs/startup-using-idea.png -------------------------------------------------------------------------------- /docs/unused-jars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/ecee7021024d3ade8f5fb1ab0bc84ad6a551b15e/docs/unused-jars.png -------------------------------------------------------------------------------- /spring-async-bean-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-startup-analyzer 7 | io.github.linyimin0812 8 | ${revision} 9 | 10 | 4.0.0 11 | 12 | spring-async-bean-core 13 | 14 | 15 | org.springframework 16 | spring-context 17 | 5.2.21.RELEASE 18 | provided 19 | 20 | 21 | 22 | io.github.linyimin0812 23 | spring-profiler-common 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot 29 | 2.7.18 30 | provided 31 | 32 | 33 | 34 | com.google.code.gson 35 | gson 36 | test 37 | 38 | 39 | 40 | javax.annotation 41 | javax.annotation-api 42 | 1.3.2 43 | 44 | 45 | 46 | org.spockframework 47 | spock-spring 48 | 2.4-M1-groovy-4.0 49 | test 50 | 51 | 52 | 53 | net.bytebuddy 54 | byte-buddy 55 | 1.14.10 56 | test 57 | 58 | 59 | 60 | org.objenesis 61 | objenesis 62 | 3.3 63 | test 64 | 65 | 66 | 67 | org.springframework 68 | spring-test 69 | 4.3.19.RELEASE 70 | test 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/main/java/io/github/linyimin0812/async/config/AsyncConfig.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.config; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public class AsyncConfig { 7 | private static final AsyncConfig INSTANCE = new AsyncConfig(); 8 | 9 | private AsyncBeanProperties asyncBeanProperties; 10 | 11 | public static AsyncConfig getInstance() { 12 | return INSTANCE; 13 | } 14 | 15 | public AsyncBeanProperties getAsyncBeanProperties() { 16 | return this.asyncBeanProperties; 17 | } 18 | 19 | public void setAsyncBeanProperties(AsyncBeanProperties asyncBeanProperties) { 20 | this.asyncBeanProperties = asyncBeanProperties == null ? new AsyncBeanProperties() : asyncBeanProperties; 21 | } 22 | 23 | public boolean isAsyncBean(String beanName) { 24 | if (asyncBeanProperties == null) { 25 | asyncBeanProperties = new AsyncBeanProperties(); 26 | } 27 | if (asyncBeanProperties.getBeanNames() == null) { 28 | return false; 29 | } 30 | return asyncBeanProperties.getBeanNames().contains(beanName); 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "AsyncConfig{" + 36 | "asyncBeanProperties=" + asyncBeanProperties + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/main/java/io/github/linyimin0812/async/executor/AsyncTaskExecutor.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.executor; 2 | 3 | import io.github.linyimin0812.async.config.AsyncConfig; 4 | import io.github.linyimin0812.profiler.common.logger.LogFactory; 5 | import io.github.linyimin0812.profiler.common.logger.Logger; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.concurrent.*; 10 | 11 | /** 12 | * @author linyimin 13 | **/ 14 | public class AsyncTaskExecutor { 15 | 16 | private static final Logger logger = LogFactory.getAsyncBeanLogger(); 17 | 18 | private static ThreadPoolExecutor threadPool; 19 | 20 | private static boolean finished = false; 21 | 22 | private static final List> futureList = new ArrayList<>(); 23 | 24 | 25 | public static void submitTask(Runnable runnable) { 26 | if (threadPool == null) { 27 | threadPool = createThreadPoolExecutor(); 28 | } 29 | 30 | futureList.add(threadPool.submit(runnable, null)); 31 | } 32 | 33 | public static void ensureAsyncTasksFinish() { 34 | 35 | if (futureList.isEmpty()) { 36 | return; 37 | } 38 | 39 | for (Future future : futureList) { 40 | try { 41 | future.get(); 42 | } catch (ExecutionException e) { 43 | throw new RuntimeException(e); 44 | } catch (InterruptedException e) { 45 | Thread.currentThread().interrupt(); 46 | throw new RuntimeException(e); 47 | } 48 | } 49 | 50 | finished = true; 51 | 52 | futureList.clear(); 53 | 54 | threadPool.shutdown(); 55 | 56 | } 57 | 58 | public static boolean isFinished() { 59 | return finished; 60 | } 61 | 62 | private static ThreadPoolExecutor createThreadPoolExecutor() { 63 | int threadPoolCoreSize = AsyncConfig.getInstance().getAsyncBeanProperties().getInitBeanThreadPoolCoreSize(); 64 | 65 | int threadPoolMaxSize = AsyncConfig.getInstance().getAsyncBeanProperties().getInitBeanThreadPoolMaxSize(); 66 | 67 | logger.info(AsyncTaskExecutor.class, "create async-init-bean thread pool, corePoolSize: {}, maxPoolSize: {}.", threadPoolCoreSize, threadPoolMaxSize); 68 | 69 | NamedThreadFactory threadFactory = new NamedThreadFactory("async-init-bean"); 70 | 71 | return new ThreadPoolExecutor(threadPoolCoreSize, threadPoolMaxSize, 30, TimeUnit.SECONDS, new SynchronousQueue<>(), threadFactory, new ThreadPoolExecutor.CallerRunsPolicy()); 72 | 73 | } 74 | 75 | public static List> getFutureList() { 76 | return futureList; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/main/java/io/github/linyimin0812/async/executor/NamedThreadFactory.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.executor; 2 | 3 | import java.util.concurrent.ThreadFactory; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | /** 7 | * @author linyimin 8 | **/ 9 | public class NamedThreadFactory implements ThreadFactory { 10 | 11 | private static final AtomicInteger poolNumber = new AtomicInteger(1); 12 | private static final AtomicInteger threadNumber = new AtomicInteger(1); 13 | 14 | private final ThreadGroup group; 15 | private final String namePrefix; 16 | private final boolean isDaemon; 17 | 18 | public NamedThreadFactory(String name) { 19 | this(name, false); 20 | } 21 | 22 | public NamedThreadFactory(String prefix, boolean isDaemon) { 23 | 24 | this.namePrefix = prefix + "-" + poolNumber.getAndIncrement() + "-thread-"; 25 | this.isDaemon = isDaemon; 26 | 27 | SecurityManager s = System.getSecurityManager(); 28 | group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); 29 | 30 | } 31 | 32 | @Override 33 | public Thread newThread(Runnable runnable) { 34 | Thread thread = new Thread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0); 35 | 36 | thread.setDaemon(isDaemon); 37 | 38 | return thread; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/main/java/io/github/linyimin0812/async/listener/AsyncTaskExecutionListener.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.listener; 2 | 3 | import io.github.linyimin0812.async.executor.AsyncTaskExecutor; 4 | import org.springframework.beans.BeansException; 5 | import org.springframework.context.ApplicationContext; 6 | import org.springframework.context.ApplicationContextAware; 7 | import org.springframework.context.ApplicationListener; 8 | import org.springframework.context.event.ContextRefreshedEvent; 9 | import org.springframework.core.Ordered; 10 | import org.springframework.core.PriorityOrdered; 11 | 12 | /** 13 | * @author linyimin 14 | **/ 15 | public class AsyncTaskExecutionListener implements ApplicationListener, ApplicationContextAware, PriorityOrdered { 16 | 17 | private ApplicationContext applicationContext; 18 | 19 | @Override 20 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 21 | this.applicationContext = applicationContext; 22 | } 23 | 24 | @Override 25 | public void onApplicationEvent(ContextRefreshedEvent event) { 26 | if (this.applicationContext.equals(event.getApplicationContext())) { 27 | AsyncTaskExecutor.ensureAsyncTasksFinish(); 28 | } 29 | } 30 | 31 | @Override 32 | public int getOrder() { 33 | return Ordered.HIGHEST_PRECEDENCE; 34 | } 35 | 36 | public ApplicationContext getApplicationContext() { 37 | return applicationContext; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/main/java/io/github/linyimin0812/async/processor/AsyncBeanPriorityLoadPostProcessor.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.processor; 2 | 3 | import io.github.linyimin0812.async.config.AsyncConfig; 4 | import io.github.linyimin0812.profiler.common.logger.LogFactory; 5 | import io.github.linyimin0812.profiler.common.logger.Logger; 6 | import org.springframework.beans.BeansException; 7 | import org.springframework.beans.factory.BeanFactory; 8 | import org.springframework.beans.factory.BeanFactoryAware; 9 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 10 | 11 | import java.util.List; 12 | 13 | 14 | /** 15 | * @author linyimin 16 | **/ 17 | public class AsyncBeanPriorityLoadPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware { 18 | 19 | private final Logger logger = LogFactory.getAsyncBeanLogger(); 20 | 21 | @Override 22 | public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 23 | 24 | if (!AsyncConfig.getInstance().getAsyncBeanProperties().isBeanPriorityLoadEnable()) { 25 | return; 26 | } 27 | 28 | List asyncBeans = AsyncConfig.getInstance().getAsyncBeanProperties().getBeanNames(); 29 | 30 | for (String beanName : asyncBeans) { 31 | 32 | if (beanFactory instanceof DefaultListableBeanFactory && !((DefaultListableBeanFactory) beanFactory).containsBeanDefinition(beanName)) { 33 | logger.warn(AsyncBeanPriorityLoadPostProcessor.class, "BeanDefinition of bean {} is not exist.", beanName); 34 | continue; 35 | } 36 | 37 | logger.info(AsyncBeanPriorityLoadPostProcessor.class, "async init bean: {}", beanName); 38 | beanFactory.getBean(beanName); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/main/java/io/github/linyimin0812/async/processor/InstantiationAwareBeanPostProcessorAdapter.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.processor; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.beans.PropertyValues; 5 | import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; 6 | 7 | import java.beans.PropertyDescriptor; 8 | import java.lang.reflect.Constructor; 9 | 10 | /** 11 | * @author linyimin 12 | **/ 13 | public class InstantiationAwareBeanPostProcessorAdapter implements SmartInstantiationAwareBeanPostProcessor { 14 | public Class predictBeanType(Class beanClass, String beanName) throws BeansException { 15 | return null; 16 | } 17 | 18 | public Constructor[] determineCandidateConstructors(Class beanClass, String beanName) throws BeansException { 19 | return null; 20 | } 21 | 22 | public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { 23 | return bean; 24 | } 25 | 26 | public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { 27 | return null; 28 | } 29 | 30 | public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { 31 | return true; 32 | } 33 | 34 | public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException { 35 | return null; 36 | } 37 | 38 | public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { 39 | return pvs; 40 | } 41 | 42 | public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 43 | return bean; 44 | } 45 | 46 | public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 47 | return bean; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/main/java/io/github/linyimin0812/async/utils/BeanDefinitionUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.utils; 2 | 3 | import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; 4 | import org.springframework.beans.factory.config.BeanDefinition; 5 | import org.springframework.beans.factory.support.GenericBeanDefinition; 6 | import org.springframework.beans.factory.support.RootBeanDefinition; 7 | import org.springframework.core.type.AnnotationMetadata; 8 | import org.springframework.core.type.MethodMetadata; 9 | import org.springframework.lang.Nullable; 10 | import org.springframework.util.ClassUtils; 11 | import org.springframework.util.StringUtils; 12 | 13 | /** 14 | * @author linyimin 15 | **/ 16 | public class BeanDefinitionUtil { 17 | 18 | public static boolean isFromConfigurationSource(BeanDefinition beanDefinition) { 19 | String beanDefinitionClassName = beanDefinition.getClass().getCanonicalName(); 20 | 21 | return beanDefinitionClassName.startsWith("org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader"); 22 | } 23 | 24 | public static Class resolveBeanClassType(BeanDefinition beanDefinition) { 25 | 26 | Class clazz = null; 27 | String className = null; 28 | 29 | if (beanDefinition instanceof GenericBeanDefinition) { 30 | className = beanDefinition.getBeanClassName(); 31 | } else if (beanDefinition instanceof AnnotatedBeanDefinition) { 32 | if (isFromConfigurationSource(beanDefinition)) { 33 | MethodMetadata methodMetadata = ((AnnotatedBeanDefinition) beanDefinition).getFactoryMethodMetadata(); 34 | assert methodMetadata != null; 35 | className = methodMetadata.getReturnTypeName(); 36 | } else { 37 | AnnotationMetadata annotationMetadata = ((AnnotatedBeanDefinition) beanDefinition).getMetadata(); 38 | className = annotationMetadata.getClassName(); 39 | } 40 | } 41 | 42 | try { 43 | clazz = StringUtils.isEmpty(className) ? null : ClassUtils.forName(className, null); 44 | } catch (ClassNotFoundException ignore) { 45 | } 46 | 47 | if (clazz == null) { 48 | if (beanDefinition instanceof RootBeanDefinition) { 49 | clazz = ((RootBeanDefinition) beanDefinition).getTargetType(); 50 | } 51 | } 52 | 53 | if (isCglibProxyClass(clazz)) { 54 | return clazz.getSuperclass(); 55 | } else { 56 | return clazz; 57 | } 58 | } 59 | 60 | private static boolean isCglibProxyClass(@Nullable Class clazz) { 61 | 62 | if (clazz == null) { 63 | return false; 64 | } 65 | 66 | return clazz.getName().contains("$$"); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/test/java/io/github/linyimin0812/async/bean/AsyncInitBeanFinderSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.bean 2 | 3 | import io.github.linyimin0812.async.config.AsyncBeanProperties 4 | import io.github.linyimin0812.async.config.AsyncConfig 5 | import org.springframework.beans.factory.annotation.Autowired 6 | import org.springframework.beans.factory.config.BeanDefinition 7 | import org.springframework.context.ApplicationContext 8 | import org.springframework.context.support.GenericApplicationContext 9 | import org.springframework.test.context.ContextConfiguration 10 | import org.springframework.test.context.TestPropertySource 11 | import spock.lang.Specification 12 | 13 | 14 | /** 15 | * @author linyimin 16 | * */ 17 | @ContextConfiguration("classpath:bean-context.xml") 18 | @TestPropertySource(locations = "classpath:application.properties") 19 | class AsyncInitBeanFinderSpec extends Specification { 20 | 21 | @Autowired 22 | private ApplicationContext applicationContext; 23 | 24 | def "test getAsyncInitMethodName"() { 25 | 26 | given: 27 | AsyncBeanProperties properties = AsyncBeanProperties.parse(applicationContext.getEnvironment()) 28 | AsyncConfig.getInstance().setAsyncBeanProperties(properties) 29 | String beanName = "testComponentBean" 30 | 31 | when: 32 | 33 | BeanDefinition beanDefinition = ((GenericApplicationContext) applicationContext) 34 | .getBeanFactory() 35 | .getBeanDefinition(beanName) 36 | 37 | then: 38 | AsyncInitBeanFinder.getAsyncInitMethodName(beanName, beanDefinition) == 'initTestComponentBean' 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/test/java/io/github/linyimin0812/async/config/AsyncConfigSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.config 2 | 3 | import spock.lang.Specification 4 | 5 | /** 6 | * @author linyimin 7 | * */ 8 | class AsyncConfigSpec extends Specification { 9 | 10 | def "test getInstance"() { 11 | when: 12 | AsyncConfig instance = AsyncConfig.getInstance() 13 | 14 | then: 15 | instance 16 | } 17 | 18 | def "test getAsyncBeanProperties"() { 19 | 20 | given: 21 | AsyncBeanProperties asyncBeanProperties = AsyncBeanPropertiesSpec.parsePropertiesFromFile() 22 | 23 | when: 24 | AsyncConfig.getInstance().setAsyncBeanProperties(asyncBeanProperties) 25 | 26 | then: 27 | AsyncConfig.getInstance().getAsyncBeanProperties() == asyncBeanProperties 28 | } 29 | 30 | def "test setAsyncBeanProperties"() { 31 | 32 | given: 33 | AsyncBeanProperties asyncBeanProperties = AsyncBeanPropertiesSpec.parsePropertiesFromFile() 34 | 35 | when: 36 | AsyncConfig.getInstance().setAsyncBeanProperties(asyncBeanProperties) 37 | 38 | then: 39 | AsyncConfig.getInstance().getAsyncBeanProperties() == asyncBeanProperties 40 | } 41 | 42 | def "test isAsyncBean"() { 43 | when: 44 | AsyncConfig.getInstance().setAsyncBeanProperties(AsyncBeanPropertiesSpec.parsePropertiesFromFile()) 45 | 46 | then: 47 | AsyncConfig.getInstance().isAsyncBean("testBean") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/test/java/io/github/linyimin0812/async/executor/AsyncTaskExecutorSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.executor 2 | 3 | import io.github.linyimin0812.async.config.AsyncBeanPropertiesSpec 4 | import io.github.linyimin0812.async.config.AsyncConfig 5 | import spock.lang.Specification 6 | 7 | import java.util.concurrent.Future 8 | 9 | /** 10 | * @author linyimin 11 | * */ 12 | class AsyncTaskExecutorSpec extends Specification { 13 | 14 | def "test submitTask"() { 15 | when: 16 | AsyncConfig.getInstance().setAsyncBeanProperties(AsyncBeanPropertiesSpec.parsePropertiesFromFile()) 17 | AsyncTaskExecutor.submitTask(() -> {}) 18 | 19 | then: 20 | AsyncTaskExecutor.getFutureList().size() == 1 21 | } 22 | 23 | def "test ensureAsyncTasksFinish"() { 24 | when: 25 | if (!AsyncTaskExecutor.isFinished()) { 26 | AsyncTaskExecutor.ensureAsyncTasksFinish(); 27 | } 28 | 29 | then: 30 | AsyncTaskExecutor.isFinished() 31 | } 32 | 33 | def "test isFinished"() { 34 | when: 35 | if (!AsyncTaskExecutor.isFinished()) { 36 | AsyncTaskExecutor.ensureAsyncTasksFinish(); 37 | } 38 | 39 | then: 40 | AsyncTaskExecutor.isFinished() 41 | } 42 | 43 | def "test getFutureList"() { 44 | when: 45 | List> list = AsyncTaskExecutor.getFutureList() 46 | 47 | then: 48 | list != null 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/test/java/io/github/linyimin0812/async/executor/NamedThreadFactorySpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.executor 2 | 3 | import spock.lang.Specification 4 | 5 | 6 | /** 7 | * @author linyimin 8 | * */ 9 | class NamedThreadFactorySpec extends Specification { 10 | 11 | def "test newThread"() { 12 | when: 13 | NamedThreadFactory factory = new NamedThreadFactory("test") 14 | Thread thread = factory.newThread(() -> {}) 15 | 16 | then: 17 | thread.name =~ 'test-(\\d+)-thread-(\\d+)' 18 | !thread.isDaemon() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/test/java/io/github/linyimin0812/async/listener/AsyncTaskExecutionListenerSpec.groovy: -------------------------------------------------------------------------------- 1 | //package io.github.linyimin0812.async.listener 2 | // 3 | //import org.springframework.beans.factory.annotation.Autowired 4 | //import org.springframework.context.ApplicationContext 5 | //import org.springframework.core.Ordered 6 | //import org.springframework.test.context.ContextConfiguration 7 | //import spock.lang.Specification 8 | // 9 | // 10 | ///** 11 | // * @author linyimin 12 | // * */ 13 | //@ContextConfiguration("classpath:bean-context.xml") 14 | //class AsyncTaskExecutionListenerSpec extends Specification { 15 | // 16 | // @Autowired 17 | // private ApplicationContext applicationContext 18 | // 19 | // def "test setApplicationContext"() { 20 | // when: 21 | // AsyncTaskExecutionListener listener = applicationContext.getBean(AsyncTaskExecutionListener.class) 22 | // 23 | // then: 24 | // listener.getApplicationContext() != null 25 | // } 26 | // 27 | // def "test getOrder"() { 28 | // when: 29 | // AsyncTaskExecutionListener listener = new AsyncTaskExecutionListener() 30 | // 31 | // then: 32 | // listener.getOrder() == Ordered.HIGHEST_PRECEDENCE 33 | // } 34 | //} 35 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/test/java/io/github/linyimin0812/async/processor/AsyncBeanPriorityLoadPostProcessorSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.processor 2 | 3 | import io.github.linyimin0812.async.config.AsyncBeanProperties 4 | import io.github.linyimin0812.async.config.AsyncConfig 5 | import org.springframework.beans.factory.support.DefaultListableBeanFactory 6 | import spock.lang.Specification 7 | 8 | /** 9 | * @author linyimin 10 | * */ 11 | class AsyncBeanPriorityLoadPostProcessorSpec extends Specification { 12 | 13 | def "test setBeanFactory"() { 14 | 15 | given: 16 | DefaultListableBeanFactory beanFactory = Mock() 17 | AsyncBeanProperties properties = new AsyncBeanProperties() 18 | AsyncConfig.instance.setAsyncBeanProperties(properties) 19 | AsyncBeanPriorityLoadPostProcessor processor = new AsyncBeanPriorityLoadPostProcessor() 20 | 21 | when: "测试不开启优先加载Bean的情况" 22 | processor.setBeanFactory(beanFactory) 23 | 24 | then: 25 | 0 * beanFactory.getBean(_) 26 | 27 | when: "测试开启优先加载,但是beanName不存在的情况" 28 | 29 | properties.setBeanPriorityLoadEnable(true) 30 | beanFactory.containsBeanDefinition('testBean') >> true 31 | 32 | processor.setBeanFactory(beanFactory) 33 | 34 | then: 35 | 0 * beanFactory.getBean(_) 36 | 37 | when: "测试开启优先加载且beanName存在的情况" 38 | 39 | properties.setBeanPriorityLoadEnable(true) 40 | properties.setBeanNames(['testBean']) 41 | 42 | beanFactory.containsBeanDefinition('testBean') >> true 43 | processor.setBeanFactory(beanFactory) 44 | 45 | then: 46 | 1 * beanFactory.getBean(_) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/test/java/io/github/linyimin0812/async/processor/AsyncProxyBeanPostProcessorSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.processor 2 | 3 | import io.github.linyimin0812.async.config.AsyncConfig 4 | import org.springframework.beans.factory.annotation.Autowired 5 | import org.springframework.core.Ordered 6 | import org.springframework.test.context.ContextConfiguration 7 | import org.springframework.test.context.TestPropertySource 8 | import spock.lang.Specification 9 | 10 | /** 11 | * @author linyimin 12 | * */ 13 | @ContextConfiguration("classpath:bean-context.xml") 14 | @TestPropertySource(locations = "classpath:application.properties") 15 | class AsyncProxyBeanPostProcessorSpec extends Specification { 16 | 17 | @Autowired 18 | AsyncProxyBeanPostProcessor processor 19 | 20 | def "test getOrder"() { 21 | expect: 22 | processor.getOrder() == Ordered.HIGHEST_PRECEDENCE 23 | } 24 | 25 | def "test setApplicationContext"() { 26 | expect: 27 | AsyncConfig.instance.isAsyncBean('testXmlBean') || AsyncConfig.instance.isAsyncBean('testBean') 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/test/java/io/github/linyimin0812/async/springbeans/SpringFactory.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.springbeans; 2 | 3 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 4 | import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; 5 | 6 | /** 7 | * @author linyimin 8 | **/ 9 | public class SpringFactory { 10 | 11 | private static final DefaultListableBeanFactory beanFactory; 12 | 13 | static { 14 | beanFactory = new DefaultListableBeanFactory(); 15 | XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); 16 | reader.loadBeanDefinitions("bean-context.xml"); 17 | } 18 | public static DefaultListableBeanFactory getBeanFactory() { 19 | return beanFactory; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/test/java/io/github/linyimin0812/async/springbeans/TestComponentBean.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.springbeans; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import javax.annotation.PostConstruct; 6 | 7 | /** 8 | * @author linyimin 9 | **/ 10 | @Component 11 | public class TestComponentBean { 12 | @PostConstruct 13 | public void initTestComponentBean() {} 14 | } 15 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/test/java/io/github/linyimin0812/async/springbeans/TestXmlBean.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.springbeans; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public class TestXmlBean { 7 | public void init() { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/test/java/io/github/linyimin0812/async/utils/BeanDefinitionUtilSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async.utils 2 | 3 | import io.github.linyimin0812.async.springbeans.SpringFactory 4 | import io.github.linyimin0812.async.springbeans.TestComponentBean 5 | import io.github.linyimin0812.async.springbeans.TestXmlBean 6 | import spock.lang.Specification 7 | 8 | /** 9 | * @author linyimin 10 | * */ 11 | class BeanDefinitionUtilSpec extends Specification { 12 | 13 | def "test isFromConfigurationSource"() { 14 | when: 15 | boolean isFrom = BeanDefinitionUtil.isFromConfigurationSource(SpringFactory.getBeanFactory().getBeanDefinition("testComponentBean")) 16 | 17 | then: 18 | !isFrom 19 | } 20 | 21 | def "test resolveBeanClassType"() { 22 | when: 23 | Class clazz = BeanDefinitionUtil.resolveBeanClassType(SpringFactory.getBeanFactory().getBeanDefinition(beanName)); 24 | 25 | then: 26 | clazz == result 27 | 28 | where: 29 | beanName || result 30 | 'testXmlBean' || TestXmlBean.class 31 | 'testComponentBean' || TestComponentBean.class 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spring-async-bean-core/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring-startup-analyzer.boost.spring.async.bean-priority-load-enable=true 2 | spring-startup-analyzer.boost.spring.async.bean-names=testBean,testComponentBean,testXmlBean 3 | spring-startup-analyzer.boost.spring.async.init-bean-thread-pool-core-size=8 4 | spring-startup-analyzer.boost.spring.async.init-bean-thread-pool-max-size=16 -------------------------------------------------------------------------------- /spring-async-bean-core/src/test/resources/bean-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /spring-async-bean-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-startup-analyzer 7 | io.github.linyimin0812 8 | ${revision} 9 | 10 | 4.0.0 11 | 12 | spring-async-bean-starter 13 | 14 | 15 | 16 | 17 | io.github.linyimin0812 18 | spring-async-bean-core 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-autoconfigure 24 | 2.5.15 25 | provided 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-configuration-processor 31 | 2.1.7.RELEASE 32 | compile 33 | true 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-test 38 | 2.3.5.RELEASE 39 | test 40 | 41 | 42 | 43 | org.assertj 44 | assertj-core 45 | 3.20.2 46 | 47 | 48 | 49 | org.spockframework 50 | spock-core 51 | test 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /spring-async-bean-starter/src/main/java/io/github/linyimin0812/async/AsyncBeanAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async; 2 | 3 | import io.github.linyimin0812.async.listener.AsyncTaskExecutionListener; 4 | import io.github.linyimin0812.async.processor.AsyncBeanPriorityLoadPostProcessor; 5 | import io.github.linyimin0812.async.processor.AsyncProxyBeanPostProcessor; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * @author linyimin 13 | **/ 14 | @Configuration 15 | @EnableConfigurationProperties(AsyncSpringBeanProperties.class) 16 | public class AsyncBeanAutoConfiguration { 17 | 18 | @Bean 19 | @ConditionalOnMissingBean 20 | public static AsyncTaskExecutionListener asyncTaskExecutionListener() { 21 | return new AsyncTaskExecutionListener(); 22 | } 23 | 24 | @Bean 25 | @ConditionalOnMissingBean 26 | public static AsyncProxyBeanPostProcessor asyncProxyBeanPostProcessor() { 27 | return new AsyncProxyBeanPostProcessor(); 28 | } 29 | 30 | @Bean 31 | @ConditionalOnMissingBean 32 | public static AsyncBeanPriorityLoadPostProcessor asyncBeanPriorityLoadPostProcessor() { 33 | return new AsyncBeanPriorityLoadPostProcessor(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /spring-async-bean-starter/src/main/java/io/github/linyimin0812/async/AsyncSpringBeanProperties.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async; 2 | 3 | import io.github.linyimin0812.async.config.AsyncBeanProperties; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | 6 | /** 7 | * @author linyimin 8 | **/ 9 | @ConfigurationProperties(prefix = AsyncBeanProperties.PREFIX) 10 | public class AsyncSpringBeanProperties extends AsyncBeanProperties { 11 | } 12 | -------------------------------------------------------------------------------- /spring-async-bean-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | io.github.linyimin0812.async.AsyncBeanAutoConfiguration -------------------------------------------------------------------------------- /spring-async-bean-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | io.github.linyimin0812.async.AsyncBeanAutoConfiguration -------------------------------------------------------------------------------- /spring-async-bean-starter/src/test/java/io/github/linyimin0812/async/AsyncBeanAutoConfigurationSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async 2 | 3 | import io.github.linyimin0812.async.listener.AsyncTaskExecutionListener 4 | import io.github.linyimin0812.async.processor.AsyncBeanPriorityLoadPostProcessor 5 | import io.github.linyimin0812.async.processor.AsyncProxyBeanPostProcessor 6 | import org.springframework.boot.autoconfigure.AutoConfigurations 7 | import org.springframework.boot.test.context.runner.ApplicationContextRunner 8 | import spock.lang.Shared 9 | import spock.lang.Specification 10 | 11 | /** 12 | * @author linyimin 13 | * */ 14 | class AsyncBeanAutoConfigurationSpec extends Specification { 15 | @Shared 16 | ApplicationContextRunner contextRunner = new ApplicationContextRunner() 17 | .withConfiguration(AutoConfigurations.of(AsyncBeanAutoConfiguration.class)); 18 | 19 | def "test autoConfigurationProperties"() { 20 | 21 | given: 22 | AsyncSpringBeanProperties properties = null 23 | 24 | when: 25 | this.contextRunner.run(context -> properties = context.getBean(AsyncSpringBeanProperties.class)) 26 | 27 | then: 28 | properties != null 29 | } 30 | 31 | def "test asyncTaskExecutionListener"() { 32 | 33 | given: 34 | AsyncTaskExecutionListener listener = null 35 | 36 | when: 37 | this.contextRunner.run(context -> listener = context.getBean(AsyncTaskExecutionListener.class)) 38 | 39 | then: 40 | listener != null 41 | } 42 | 43 | def "test asyncBeanPriorityLoadPostProcessor"() { 44 | 45 | given: 46 | AsyncBeanPriorityLoadPostProcessor processor = null 47 | 48 | when: 49 | this.contextRunner.run(context -> processor = context.getBean(AsyncBeanPriorityLoadPostProcessor.class)) 50 | 51 | then: 52 | processor != null 53 | } 54 | 55 | def "test asyncProxyBeanPostProcessor"() { 56 | 57 | given: 58 | AsyncProxyBeanPostProcessor processor = null 59 | 60 | when: 61 | this.contextRunner.run(context -> processor = context.getBean(AsyncProxyBeanPostProcessor.class)) 62 | 63 | then: 64 | processor != null 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /spring-async-bean-starter/src/test/java/io/github/linyimin0812/async/AsyncSpringBeanPropertiesSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.async 2 | 3 | import org.springframework.boot.test.context.runner.ApplicationContextRunner 4 | import spock.lang.Shared 5 | import spock.lang.Specification 6 | 7 | /** 8 | * @author linyimin 9 | * */ 10 | class AsyncSpringBeanPropertiesSpec extends Specification { 11 | @Shared 12 | ApplicationContextRunner contextRunner = new ApplicationContextRunner() 13 | .withUserConfiguration(AsyncBeanAutoConfiguration.class) 14 | .withPropertyValues("spring-startup-analyzer.boost.spring.async.bean-names=testBean"); 15 | 16 | def "test propertiesLoading"() { 17 | 18 | given: 19 | AsyncSpringBeanProperties properties = null 20 | 21 | when: 22 | this.contextRunner.run(context -> properties = context.getBean(AsyncSpringBeanProperties.class)) 23 | 24 | then: 25 | String.join(",", properties.getBeanNames()) == 'testBean' 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spring-profiler-agent/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | spring-startup-analyzer 7 | io.github.linyimin0812 8 | ${revision} 9 | 10 | 11 | 12 | 13 | org.spockframework 14 | spock-core 15 | test 16 | 17 | 18 | 19 | 4.0.0 20 | 21 | spring-profiler-agent 22 | spring-profiler-agent 23 | 24 | 25 | spring-profiler-agent 26 | 27 | 28 | org.apache.maven.plugins 29 | maven-jar-plugin 30 | 3.1.1 31 | 32 | ${output.directory} 33 | 34 | 35 | 36 | io.github.linyimin0812.profiler.agent.ProfilerAgentBoostrap 37 | true 38 | true 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /spring-profiler-agent/src/main/java/io/github/linyimin0812/profiler/agent/ProfilerAgentClassLoader.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.agent; 2 | 3 | import java.net.URL; 4 | import java.net.URLClassLoader; 5 | 6 | /** 7 | * @author linyimin 8 | **/ 9 | public class ProfilerAgentClassLoader extends URLClassLoader { 10 | 11 | private static final String BRIDGE_CLASS = "io.github.linyimin0812.Bridge"; 12 | 13 | static { 14 | ClassLoader.registerAsParallelCapable(); 15 | } 16 | 17 | public ProfilerAgentClassLoader(URL[] urls) { 18 | super(urls, ClassLoader.getSystemClassLoader().getParent()); 19 | } 20 | 21 | @Override 22 | protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { 23 | 24 | final Class loadedClass = findLoadedClass(name); 25 | if (loadedClass != null) { 26 | return loadedClass; 27 | } 28 | 29 | // 优先从parent加载系统类,避免抛出ClassNotFoundException 30 | if (name != null && (name.startsWith("java.") || name.startsWith("sun.") || BRIDGE_CLASS.equals(name))) { 31 | return super.loadClass(name, resolve); 32 | } 33 | 34 | try { 35 | Class aClass = findClass(name); 36 | if (resolve) { 37 | resolveClass(aClass); 38 | } 39 | 40 | return aClass; 41 | } catch (Exception ignore) { 42 | 43 | } 44 | 45 | return super.loadClass(name, resolve); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spring-profiler-agent/src/test/java/io/github/linyimin0812/profiler/agent/ProfilerAgentClassLoaderSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.agent 2 | 3 | import spock.lang.Specification 4 | 5 | /** 6 | * @author linyimin 7 | * */ 8 | class ProfilerAgentClassLoaderSpec extends Specification { 9 | 10 | def "test loadClass"() { 11 | 12 | given: 13 | URL testJarUrl = ProfilerAgentClassLoaderSpec.class.getClassLoader().getResource("spring-profiler-api.jar") 14 | ProfilerAgentClassLoader classLoader = new ProfilerAgentClassLoader(new URL[] {testJarUrl}) 15 | 16 | when: 17 | Class clazz = classLoader.loadClass(className, true) 18 | def loaderName = clazz.getClassLoader() == null ? null : clazz.getClassLoader().getClass().getName() 19 | 20 | then: 21 | loaderName == loader 22 | 23 | where: 24 | className || loader 25 | 'io.github.linyimin0812.profiler.api.event.AtEnterEvent' || 'io.github.linyimin0812.profiler.agent.ProfilerAgentClassLoader' 26 | 'java.lang.String' || null 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-profiler-agent/src/test/resources/spring-profiler-api.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/ecee7021024d3ade8f5fb1ab0bc84ad6a551b15e/spring-profiler-agent/src/test/resources/spring-profiler-api.jar -------------------------------------------------------------------------------- /spring-profiler-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | spring-startup-analyzer 7 | io.github.linyimin0812 8 | ${revision} 9 | 10 | 4.0.0 11 | 12 | spring-profiler-api 13 | spring-profiler-api 14 | 15 | 16 | 17 | org.picocontainer 18 | picocontainer 19 | 20 | 21 | 22 | org.spockframework 23 | spock-core 24 | test 25 | 26 | 27 | 28 | 29 | 30 | spring-profiler-api 31 | 32 | 33 | org.apache.maven.plugins 34 | maven-jar-plugin 35 | 3.1.1 36 | 37 | ${output.directory} 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /spring-profiler-api/src/main/java/io/github/linyimin0812/profiler/api/EventListener.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.api; 2 | 3 | 4 | import io.github.linyimin0812.profiler.api.event.Event; 5 | import org.picocontainer.Startable; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author linyimin 11 | **/ 12 | public interface EventListener extends Startable { 13 | /** 14 | * 需要增强的类 15 | * @param className 类全限定名, 如果为空, 默认返回为true 16 | 17 | * @return true: 进行增强, false: 不进行增强 18 | */ 19 | boolean filter(String className); 20 | 21 | /** 22 | * 需要增强的方法(此方法会依赖filter(className), 只有filter(className)返回true时,才会执行到此方法) 23 | * @param methodName 方法名 24 | * @param methodTypes 方法参数列表 25 | * @return true: 进行增强, false: 不进行增强 26 | */ 27 | default boolean filter(String methodName, String[] methodTypes) { 28 | return true; 29 | } 30 | 31 | /** 32 | * 事件响应处理逻辑 33 | * @param event 触发的事件 34 | */ 35 | void onEvent(Event event); 36 | 37 | /** 38 | * 监听的事件 39 | * @return 需要监听的事件列表 40 | */ 41 | List listen(); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /spring-profiler-api/src/main/java/io/github/linyimin0812/profiler/api/Lifecycle.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.api; 2 | 3 | import org.picocontainer.Startable; 4 | 5 | /** 6 | * @author linyimin 7 | **/ 8 | public interface Lifecycle extends Startable { 9 | } 10 | -------------------------------------------------------------------------------- /spring-profiler-api/src/main/java/io/github/linyimin0812/profiler/api/event/AtEnterEvent.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.api.event; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public class AtEnterEvent extends InvokeEvent { 7 | 8 | public AtEnterEvent(long processId, 9 | long invokeId, 10 | Class clazz, 11 | Object target, 12 | String methodName, 13 | String methodDesc, 14 | Object[] args) { 15 | super(processId, invokeId, clazz, target, methodName, methodDesc, args, Type.AT_ENTER); 16 | 17 | } 18 | 19 | public AtEnterEvent changeParameter(int index, Object changeValue) { 20 | args[index] = changeValue; 21 | return this; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-profiler-api/src/main/java/io/github/linyimin0812/profiler/api/event/AtExceptionExitEvent.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.api.event; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public class AtExceptionExitEvent extends InvokeEvent { 7 | 8 | public final Throwable throwable; 9 | 10 | public AtExceptionExitEvent(long processId, 11 | long invokeId, 12 | Class clazz, 13 | Object target, 14 | String methodName, 15 | String methodDesc, 16 | Object[] args, 17 | Throwable throwable) { 18 | super(processId, invokeId, clazz, target, methodName, methodDesc, args, Type.AT_EXCEPTION_EXIT); 19 | 20 | this.throwable = throwable; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spring-profiler-api/src/main/java/io/github/linyimin0812/profiler/api/event/AtExitEvent.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.api.event; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public class AtExitEvent extends InvokeEvent { 7 | 8 | public final Object returnObj; 9 | 10 | public AtExitEvent(long processId, 11 | long invokeId, 12 | Class clazz, 13 | Object target, 14 | String methodName, 15 | String methodDesc, 16 | Object[] args, 17 | Object returnObj) { 18 | super(processId, invokeId, clazz, target, methodName, methodDesc, args, Type.AT_EXIT); 19 | this.returnObj = returnObj; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-profiler-api/src/main/java/io/github/linyimin0812/profiler/api/event/Event.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.api.event; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public abstract class Event { 7 | 8 | public final Type type; 9 | 10 | protected Event(Type type) { 11 | this.type = type; 12 | } 13 | 14 | public enum Type { 15 | /** 16 | * 函数入口 17 | */ 18 | AT_ENTER, 19 | 20 | /** 21 | * 函数退出 22 | */ 23 | AT_EXIT, 24 | 25 | /** 26 | * 函数抛出异常 27 | */ 28 | AT_EXCEPTION_EXIT 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /spring-profiler-api/src/main/java/io/github/linyimin0812/profiler/api/event/InvokeEvent.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.api.event; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public abstract class InvokeEvent extends Event { 7 | 8 | public final long processId; 9 | public final long invokeId; 10 | 11 | /** 12 | * 触发调用事件的类 13 | */ 14 | public final Class clazz; 15 | 16 | /** 17 | * 触发调用事件的方法名称 18 | */ 19 | public final String methodName; 20 | 21 | /** 22 | * 触发调用事件的方法签名 23 | */ 24 | public final String methodDesc; 25 | 26 | /** 27 | * 触发调用事件的方法参数 28 | */ 29 | public final Object[] args; 30 | 31 | /** 32 | * 触发调用事件的对象 33 | */ 34 | public final Object target; 35 | 36 | protected InvokeEvent(long processId, 37 | long invokeId, 38 | Class clazz, 39 | Object target, 40 | String methodName, 41 | String methodDesc, 42 | Object[] args, 43 | Type type) { 44 | super(type); 45 | this.processId = processId; 46 | this.invokeId = invokeId; 47 | this.clazz = clazz; 48 | this.target = target; 49 | this.methodName = methodName; 50 | this.methodDesc = methodDesc; 51 | this.args = args; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /spring-profiler-api/src/test/java/io/github/linyimin0812/profiler/api/event/AtEnterEventSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.api.event 2 | 3 | import spock.lang.Specification 4 | 5 | /** 6 | * @author linyimin 7 | * */ 8 | class AtEnterEventSpec extends Specification { 9 | 10 | def "test changeParameter"() { 11 | given: 12 | AtEnterEvent enterEvent = new AtEnterEvent( 13 | 1000, 14 | 1001, 15 | null, 16 | null, 17 | null, 18 | null, 19 | new Object[] { '1', '2', '3' } 20 | ) 21 | 22 | when: 23 | enterEvent.changeParameter(index, value) 24 | 25 | then: 26 | enterEvent.args[index] == value 27 | 28 | where: 29 | index || value 30 | 0 || 'test1' 31 | 1 || 'test2' 32 | 2 || 'test3' 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spring-profiler-bridge/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | spring-startup-analyzer 7 | io.github.linyimin0812 8 | ${revision} 9 | 10 | 11 | 12 | 13 | org.spockframework 14 | spock-core 15 | test 16 | 17 | 18 | 19 | 4.0.0 20 | 21 | spring-profiler-bridge 22 | 23 | spring-profiler-bridge 24 | jar 25 | 26 | 27 | spring-profiler-bridge 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-jar-plugin 32 | 3.1.1 33 | 34 | ${output.directory} 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /spring-profiler-bridge/src/main/java/io/github/linyimin0812/Bridge.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The Arthas Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.github.linyimin0812; 7 | 8 | /** 9 | * @author linyimin 10 | **/ 11 | public class Bridge { 12 | 13 | public static final AbstractBridge NOP_BRIDGE = new NopBridge(); 14 | 15 | private static AbstractBridge bridgeInstance = NOP_BRIDGE; 16 | 17 | public static AbstractBridge getBridge() { 18 | return bridgeInstance; 19 | } 20 | 21 | public static void setBridge(AbstractBridge bridge) { 22 | bridgeInstance = bridge; 23 | } 24 | 25 | public static void atEnter(Class clazz, Object target, String methodName, String methodDesc, Object[] args) { 26 | bridgeInstance.atEnter(clazz, target, methodName, methodDesc, args); 27 | } 28 | 29 | public static void atExit(Class clazz, Object target, String methodName, String methodDesc, Object[] args, Object returnObject) { 30 | bridgeInstance.atExit(clazz, target, methodName, methodDesc, args, returnObject); 31 | } 32 | 33 | public static void atExceptionExit(Class clazz, Object target, String methodName, String methodDesc, Object[] args, Throwable throwable) { 34 | bridgeInstance.atExceptionExit(clazz, target, methodName, methodDesc, args, throwable); 35 | } 36 | 37 | public static abstract class AbstractBridge { 38 | public abstract void atEnter(Class clazz, Object target, String methodName, String methodDesc, Object[] args); 39 | 40 | public abstract void atExit(Class clazz, Object target, String methodName, String methodDesc, Object[] args, Object returnObject); 41 | 42 | public abstract void atExceptionExit(Class clazz, Object target, String methodName, String methodDesc, Object[] args, Throwable throwable); 43 | } 44 | 45 | static class NopBridge extends AbstractBridge { 46 | 47 | @Override 48 | public void atEnter(Class clazz, Object target, String methodName, String methodDesc, Object[] args) { 49 | // NopBridge do not need to do anything 50 | } 51 | 52 | @Override 53 | public void atExit(Class clazz, Object target, String methodName, String methodDesc, Object[] args, Object returnObject) { 54 | // NopBridge do not need to do anything 55 | } 56 | 57 | @Override 58 | public void atExceptionExit(Class clazz, Object target, String methodName, String methodDesc, Object[] args, Throwable throwable) { 59 | // NopBridge do not need to do anything 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /spring-profiler-bridge/src/test/java/io/github/linyimin0812/BridgeSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812 2 | 3 | import spock.lang.Specification 4 | 5 | /** 6 | * @author linyimin 7 | * */ 8 | class BridgeSpec extends Specification { 9 | 10 | private static boolean atEnter = false 11 | private static boolean atExit = false 12 | private static boolean atException = false 13 | 14 | def "test getBridge"() { 15 | when: 16 | Bridge.setBridge(Bridge.NOP_BRIDGE) 17 | 18 | then: 19 | Bridge.NOP_BRIDGE == Bridge.getBridge() 20 | } 21 | 22 | 23 | def "test setBridge"() { 24 | 25 | given: 26 | BridgeImplTest bridgeImplTest = new BridgeImplTest() 27 | 28 | when: 29 | Bridge.setBridge(bridgeImplTest) 30 | 31 | then: 32 | bridgeImplTest == Bridge.getBridge() 33 | } 34 | 35 | def "test atEnter"() { 36 | 37 | when: 38 | Bridge.setBridge(Bridge.NOP_BRIDGE) 39 | Bridge.atEnter(null, null, null, null, null) 40 | 41 | then: 42 | !atEnter 43 | 44 | when: 45 | Bridge.setBridge(new BridgeImplTest()) 46 | Bridge.atEnter(null, null, null, null, null) 47 | 48 | then: 49 | atEnter 50 | 51 | } 52 | 53 | def "test atExit"() { 54 | 55 | when: 56 | Bridge.setBridge(Bridge.NOP_BRIDGE) 57 | Bridge.atExit(null, null, null, null, null, null) 58 | 59 | then: 60 | !atExit 61 | 62 | when: 63 | Bridge.setBridge(new BridgeImplTest()) 64 | Bridge.atExit(null, null, null, null, null, null) 65 | 66 | then: 67 | atExit 68 | } 69 | 70 | def "test atExceptionExit"() { 71 | 72 | when: 73 | Bridge.setBridge(Bridge.NOP_BRIDGE) 74 | Bridge.atExceptionExit(null, null, null, null, null, null) 75 | 76 | then: 77 | !atException 78 | 79 | when: 80 | 81 | Bridge.setBridge(new BridgeImplTest()) 82 | Bridge.atExceptionExit(null, null, null, null, null, null) 83 | 84 | then: 85 | atException 86 | } 87 | 88 | private static class BridgeImplTest extends Bridge.AbstractBridge { 89 | 90 | @Override 91 | void atEnter(Class clazz, Object target, String methodName, String methodDesc, Object[] args) { 92 | atEnter = true 93 | System.out.println("BridgeImplTest.atEnter") 94 | } 95 | 96 | @Override 97 | void atExit(Class clazz, Object target, String methodName, String methodDesc, Object[] args, Object returnObject) { 98 | atExit = true 99 | System.out.println("BridgeImplTest.atExit") 100 | } 101 | 102 | @Override 103 | void atExceptionExit(Class clazz, Object target, String methodName, String methodDesc, Object[] args, Throwable throwable) { 104 | atException = true 105 | System.out.println("BridgeImplTest.atExceptionExit") 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /spring-profiler-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-startup-analyzer 7 | io.github.linyimin0812 8 | ${revision} 9 | 10 | 4.0.0 11 | 12 | spring-profiler-common 13 | 14 | 15 | 16 | 17 | org.picocontainer 18 | picocontainer 19 | provided 20 | 21 | 22 | 23 | com.google.code.gson 24 | gson 25 | provided 26 | 27 | 28 | 29 | org.spockframework 30 | spock-core 31 | test 32 | 33 | 34 | 35 | 36 | 37 | 8 38 | 8 39 | 40 | 41 | 42 | spring-profiler-common 43 | 44 | 45 | org.apache.maven.plugins 46 | maven-jar-plugin 47 | 3.1.1 48 | 49 | ${output.directory} 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /spring-profiler-common/src/main/java/io/github/linyimin0812/profiler/common/instruction/InstrumentationHolder.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.instruction; 2 | 3 | import java.lang.instrument.Instrumentation; 4 | 5 | /** 6 | * @author linyimin 7 | **/ 8 | public class InstrumentationHolder { 9 | 10 | private static Instrumentation instrumentation; 11 | 12 | public static void setInstrumentation(Instrumentation instrumentation) { 13 | InstrumentationHolder.instrumentation = instrumentation; 14 | } 15 | 16 | public static Instrumentation getInstrumentation() { 17 | return instrumentation; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /spring-profiler-common/src/main/java/io/github/linyimin0812/profiler/common/logger/Level.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.logger; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public enum Level { 7 | DEBUG, 8 | INFO, 9 | WARN, 10 | ERROR 11 | } 12 | -------------------------------------------------------------------------------- /spring-profiler-common/src/main/java/io/github/linyimin0812/profiler/common/logger/LogFactory.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.logger; 2 | 3 | import io.github.linyimin0812.profiler.common.settings.ProfilerSettings; 4 | 5 | import java.io.File; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author linyimin 11 | **/ 12 | public class LogFactory { 13 | private final static Map LOGGER_MAP = new HashMap<>(); 14 | 15 | static { 16 | initialize(); 17 | } 18 | 19 | static void initialize() { 20 | String defaultLogPath = System.getProperty("user.home") + File.separator + "spring-startup-analyzer" + File.separator + "logs" + File.separator; 21 | String logPath = ProfilerSettings.getProperty("spring-startup-analyzer.log.path", defaultLogPath); 22 | if (!logPath.endsWith(File.separator)) { 23 | logPath = logPath + File.separator; 24 | } 25 | 26 | LOGGER_MAP.clear(); 27 | createLogger(LoggerName.STARTUP, logPath); 28 | createLogger(LoggerName.TRANSFORM, logPath); 29 | createLogger(LoggerName.ASYNC_INIT_BEAN, logPath); 30 | } 31 | 32 | public static Logger getStartupLogger() { 33 | return LOGGER_MAP.get(LoggerName.STARTUP); 34 | } 35 | 36 | public static Logger getTransFormLogger() { 37 | return LOGGER_MAP.get(LoggerName.TRANSFORM); 38 | } 39 | 40 | public static Logger getAsyncBeanLogger() { 41 | return LOGGER_MAP.get(LoggerName.ASYNC_INIT_BEAN); 42 | } 43 | 44 | public static void close() { 45 | for (Logger logger : LOGGER_MAP.values()) { 46 | logger.close(); 47 | } 48 | } 49 | 50 | public static void createLogger(LoggerName loggerName, String path) { 51 | Logger logger = new Logger(loggerName, path); 52 | LOGGER_MAP.put(loggerName, logger); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /spring-profiler-common/src/main/java/io/github/linyimin0812/profiler/common/logger/LoggerName.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.logger; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public enum LoggerName { 7 | /** 8 | * information about app startup 9 | */ 10 | STARTUP("startup"), 11 | 12 | /** 13 | * enhanced method information 14 | */ 15 | TRANSFORM("transform"), 16 | 17 | /** 18 | * async init method bean information 19 | */ 20 | ASYNC_INIT_BEAN("async-init-bean"); 21 | 22 | private final String value; 23 | 24 | private LoggerName(String value) { 25 | this.value = value; 26 | } 27 | 28 | public String getValue() { 29 | return this.value; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spring-profiler-common/src/main/java/io/github/linyimin0812/profiler/common/settings/ProfilerSettings.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.settings; 2 | 3 | import io.github.linyimin0812.profiler.common.utils.AgentHomeUtil; 4 | 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.IOException; 8 | import java.util.Properties; 9 | import java.util.logging.Logger; 10 | 11 | /** 12 | * @author linyimin 13 | **/ 14 | public class ProfilerSettings { 15 | // Due to circular dependency issues with LogFactory, java.util.logging.Logger was used. 16 | private final static Logger logger = Logger.getLogger(ProfilerSettings.class.getName()); 17 | 18 | private final static Properties properties = new Properties(); 19 | 20 | static { 21 | loadProperties(AgentHomeUtil.home() + "config" + File.separator + "spring-startup-analyzer.properties"); 22 | } 23 | 24 | public static String getProperty(String key, String defaultValue) { 25 | if (isNotBlank(System.getProperty(key))) { 26 | logger.info("Key: " + key + " from command line, value is " + System.getProperty(key)); 27 | return System.getProperty(key); 28 | } 29 | 30 | if (properties.containsKey(key)) { 31 | String value = isNotBlank(properties.getProperty(key)) ? properties.getProperty(key) : defaultValue; 32 | logger.info("Key: " + key + " from configuration file, value is " + value); 33 | return value; 34 | } 35 | 36 | logger.info("Key: " + key + " not found, use default value: " + defaultValue); 37 | 38 | return defaultValue; 39 | } 40 | 41 | public static String getProperty(String key) { 42 | if (isNotBlank(System.getProperty(key))) { 43 | return System.getProperty(key); 44 | } 45 | return properties.getProperty(key); 46 | } 47 | 48 | public static boolean contains(String key) { 49 | return properties.containsKey(key); 50 | } 51 | 52 | public static boolean isNotBlank(String text) { 53 | return text != null && !text.isEmpty(); 54 | } 55 | 56 | public static void loadProperties(String path) { 57 | clear(); 58 | 59 | try (FileInputStream fileInputStream = new FileInputStream(path)) { 60 | properties.load(fileInputStream); 61 | logger.info("loaded settings from " + path); 62 | } catch (IOException e) { 63 | logger.severe("load settings from " + path + " error."); 64 | } 65 | } 66 | 67 | public static void clear() { 68 | properties.clear(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /spring-profiler-common/src/main/java/io/github/linyimin0812/profiler/common/ui/BeanInitResult.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.ui; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.concurrent.atomic.AtomicLong; 8 | 9 | /** 10 | * @author linyimin 11 | **/ 12 | public class BeanInitResult { 13 | 14 | private static final AtomicLong SEQUENCE_ID = new AtomicLong(1000); 15 | 16 | private final long id; 17 | private long parentId; 18 | private final String name; 19 | private long startMillis; 20 | private long endMillis; 21 | private long duration; 22 | private long actualDuration; 23 | private final Map tags; 24 | private final List children; 25 | 26 | public BeanInitResult(String name) { 27 | this.id = SEQUENCE_ID.incrementAndGet(); 28 | this.name = name; 29 | this.startMillis = System.currentTimeMillis(); 30 | this.tags = new HashMap<>(); 31 | this.children = new ArrayList<>(); 32 | } 33 | 34 | public long getId() { 35 | return id; 36 | } 37 | 38 | public long getParentId() { 39 | return parentId; 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | 47 | public long getDuration() { 48 | return duration; 49 | } 50 | 51 | public long getActualDuration() { 52 | return actualDuration; 53 | } 54 | 55 | public void duration() { 56 | this.endMillis = System.currentTimeMillis(); 57 | this.duration = this.endMillis - this.startMillis; 58 | long childrenDuration = this.children.stream().mapToLong(BeanInitResult::getDuration).sum(); 59 | this.actualDuration = duration - childrenDuration; 60 | } 61 | 62 | public Map getTags() { 63 | return tags; 64 | } 65 | 66 | public List getChildren() { 67 | return children; 68 | } 69 | 70 | public void addChild(BeanInitResult child) { 71 | child.parentId = this.id; 72 | this.children.add(child); 73 | } 74 | 75 | public long getStartMillis() { 76 | return startMillis; 77 | } 78 | 79 | public long getEndMillis() { 80 | return endMillis; 81 | } 82 | 83 | public void setStartMillis(long startMillis) { 84 | this.startMillis = startMillis; 85 | } 86 | 87 | public void setTags(Map tags) { 88 | this.tags.putAll(tags); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /spring-profiler-common/src/main/java/io/github/linyimin0812/profiler/common/ui/MethodInvokeDetail.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.ui; 2 | 3 | import com.google.gson.Gson; 4 | import io.github.linyimin0812.profiler.common.utils.GsonUtil; 5 | 6 | /** 7 | * @author linyimin 8 | **/ 9 | public class MethodInvokeDetail { 10 | private final String methodQualifier; 11 | private final long startMillis; 12 | private long duration; 13 | 14 | private Object[] args; 15 | 16 | public MethodInvokeDetail(String methodQualifier, Object[] args) { 17 | this.methodQualifier = methodQualifier; 18 | this.startMillis = System.currentTimeMillis(); 19 | 20 | if (args == null) { 21 | return; 22 | } 23 | 24 | Gson gson = GsonUtil.create(); 25 | 26 | Object[] argStrList = new String[args.length]; 27 | 28 | for (int i = 0; i < args.length; i++) { 29 | try { 30 | argStrList[i] = gson.toJson(args[i]); 31 | } catch (Throwable ignored) { 32 | argStrList[i] = args[i].toString(); 33 | } 34 | } 35 | this.args = argStrList; 36 | } 37 | 38 | public MethodInvokeDetail(String methodQualifier, long startMillis, long duration) { 39 | this.methodQualifier = methodQualifier; 40 | this.startMillis = startMillis; 41 | this.duration = duration; 42 | } 43 | 44 | public void setDuration(long duration) { 45 | this.duration = duration; 46 | } 47 | 48 | public String getMethodQualifier() { 49 | return methodQualifier; 50 | } 51 | 52 | public long getStartMillis() { 53 | return startMillis; 54 | } 55 | 56 | public long getDuration() { 57 | return duration; 58 | } 59 | 60 | public Object[] getArgs() { 61 | return args; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /spring-profiler-common/src/main/java/io/github/linyimin0812/profiler/common/ui/MethodInvokeMetrics.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.ui; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author linyimin 7 | **/ 8 | public class MethodInvokeMetrics { 9 | 10 | private final String method; 11 | private final long invokeCount; 12 | private final long totalCost; 13 | private final String averageCost; 14 | 15 | private final List invokeDetails; 16 | 17 | public MethodInvokeMetrics(String method, long invokeCount, long totalCost, double averageCost, List invokeDetails) { 18 | this.method = method; 19 | this.invokeCount = invokeCount; 20 | this.totalCost = totalCost; 21 | this.averageCost = String.format("%.2f", averageCost); 22 | this.invokeDetails = invokeDetails; 23 | } 24 | 25 | public String getMethod() { 26 | return method; 27 | } 28 | 29 | public long getTotalCost() { 30 | return totalCost; 31 | } 32 | 33 | public String getAverageCost() { 34 | return averageCost; 35 | } 36 | 37 | public List getInvokeDetails() { 38 | return invokeDetails; 39 | } 40 | 41 | public long getInvokeCount() { 42 | return invokeCount; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /spring-profiler-common/src/main/java/io/github/linyimin0812/profiler/common/ui/Statistics.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.ui; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public class Statistics { 7 | private static final int DEFAULT_ORDER = 100; 8 | 9 | private final int order; 10 | private final String label; 11 | private final String value; 12 | 13 | public Statistics(String label, String value) { 14 | this(DEFAULT_ORDER, label, value); 15 | } 16 | 17 | public Statistics(int order, String label, String value) { 18 | this.order = order; 19 | this.label = label; 20 | this.value = value; 21 | } 22 | 23 | public int getOrder() { 24 | return order; 25 | } 26 | 27 | public String getLabel() { 28 | return label; 29 | } 30 | 31 | public String getValue() { 32 | return value; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spring-profiler-common/src/main/java/io/github/linyimin0812/profiler/common/utils/AgentHomeUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.utils; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * @author linyimin 7 | **/ 8 | public class AgentHomeUtil { 9 | 10 | public static String home() { 11 | String currentFilePath = AgentHomeUtil.class.getProtectionDomain() 12 | .getCodeSource() 13 | .getLocation() 14 | .getPath(); 15 | 16 | File file = new File(currentFilePath); 17 | 18 | if (!file.exists()) { 19 | return System.getProperty("user.home") + File.separator + "spring-startup-analyzer" + File.separator; 20 | } 21 | 22 | return file.getParentFile().getParent() + File.separator; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /spring-profiler-common/src/main/java/io/github/linyimin0812/profiler/common/utils/GsonUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.utils; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | 6 | import static java.lang.reflect.Modifier.TRANSIENT; 7 | 8 | /** 9 | * @author banzhe 10 | **/ 11 | public class GsonUtil { 12 | 13 | public static Gson create() { 14 | return new GsonBuilder() 15 | .disableJdkUnsafe() 16 | .enableComplexMapKeySerialization() 17 | .create(); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /spring-profiler-common/src/main/java/io/github/linyimin0812/profiler/common/utils/IpUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.utils; 2 | 3 | import java.net.*; 4 | import java.util.Enumeration; 5 | 6 | /** 7 | * @author linyimin 8 | **/ 9 | public class IpUtil { 10 | 11 | public static String getIp() { 12 | try { 13 | Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); 14 | 15 | while (interfaces.hasMoreElements()) { 16 | NetworkInterface ni = interfaces.nextElement(); 17 | Enumeration addresses = ni.getInetAddresses(); 18 | while (addresses.hasMoreElements()) { 19 | InetAddress inetAddress = addresses.nextElement(); 20 | if (!inetAddress.isLoopbackAddress() && !inetAddress.getHostAddress().contains(":")) { 21 | return inetAddress.getHostAddress(); 22 | } 23 | } 24 | } 25 | 26 | } catch (SocketException ignored ) { 27 | return "127.0.0.1"; 28 | } 29 | 30 | return "127.0.0.1"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /spring-profiler-common/src/main/java/io/github/linyimin0812/profiler/common/utils/NameUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.utils; 2 | 3 | import java.io.File; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | 7 | /** 8 | * @author linyimin 9 | **/ 10 | public class NameUtil { 11 | 12 | private static String appName; 13 | private static String startupInstanceName; 14 | 15 | // 来自sentinel的实现 16 | private static void resolveAppName() { 17 | appName = System.getProperty("project.name"); 18 | // use -Dproject.name first 19 | if (appName != null && !appName.isEmpty()) { 20 | return; 21 | } 22 | 23 | appName = System.getProperty("spring.application.name"); 24 | 25 | if (appName != null && !appName.isEmpty()) { 26 | return; 27 | } 28 | 29 | // parse sun.java.command property 30 | String command = System.getProperty("sun.java.command"); 31 | if (command == null || command.isEmpty()) { 32 | return; 33 | } 34 | command = command.split("\\s")[0]; 35 | String separator = File.separator; 36 | if (command.contains(separator)) { 37 | String[] strs; 38 | if ("\\".equals(separator)) { 39 | strs = command.split("\\\\"); 40 | } else { 41 | strs = command.split(separator); 42 | } 43 | command = strs[strs.length - 1]; 44 | } 45 | if (command.toLowerCase().endsWith(".jar")) { 46 | command = command.substring(0, command.length() - 4); 47 | } 48 | appName = command; 49 | } 50 | 51 | public static String getAppName() { 52 | 53 | if (appName == null) { 54 | resolveAppName(); 55 | } 56 | 57 | return appName; 58 | } 59 | 60 | public static String getStartupInstanceName() { 61 | 62 | if (startupInstanceName == null) { 63 | String currentTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); 64 | startupInstanceName = String.format("%s-%s-%s", NameUtil.getAppName(), currentTime, IpUtil.getIp()); 65 | } 66 | 67 | return startupInstanceName; 68 | } 69 | 70 | public static String getFlameGraphHtmlName() { 71 | return getStartupInstanceName() + "-flame-graph.html"; 72 | } 73 | 74 | public static String getAnalysisHtmlName() { 75 | return getStartupInstanceName() + "-analyzer.html"; 76 | } 77 | 78 | public static String getAnalysisCsvName() { 79 | return getStartupInstanceName() + "-analyzer-bean-list.csv"; 80 | } 81 | 82 | 83 | public static String getOutputPath() { 84 | return AgentHomeUtil.home() + File.separator + "output" + File.separator; 85 | } 86 | 87 | public static String getTemplatePath() { 88 | return AgentHomeUtil.home() + File.separator + "template" + File.separator; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /spring-profiler-common/src/test/java/io/github/linyimin0812/profiler/common/instruction/InstrumentationHolderSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.instruction 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Stepwise 5 | 6 | import java.lang.instrument.Instrumentation 7 | 8 | /** 9 | * @author linyimin 10 | * */ 11 | @Stepwise 12 | class InstrumentationHolderSpec extends Specification { 13 | 14 | def "test setInstrumentation"() { 15 | given: 16 | Instrumentation instrumentation = Mock() 17 | 18 | when: 19 | InstrumentationHolder.instrumentation = instrumentation 20 | 21 | then: 22 | InstrumentationHolder.instrumentation != null 23 | } 24 | 25 | def "test getInstrumentation"() { 26 | expect: 27 | InstrumentationHolder.instrumentation != null 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spring-profiler-common/src/test/java/io/github/linyimin0812/profiler/common/logger/LogFactorySpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.logger 2 | 3 | import spock.lang.Specification 4 | 5 | import java.nio.file.Files 6 | import java.nio.file.Path 7 | import java.nio.file.Paths 8 | 9 | /** 10 | * @author linyimin 11 | * */ 12 | class LogFactorySpec extends Specification { 13 | 14 | Path customPathDir = Paths.get(System.getProperty("user.home"), "spring-startup-analyzer", "customlogs", File.separator) 15 | 16 | def setup() { 17 | System.clearProperty("spring-startup-analyzer.log.path") 18 | LogFactory.initialize() 19 | } 20 | 21 | def cleanup() { 22 | if (Files.exists(customPathDir)) { 23 | Files.walk(customPathDir) 24 | .sorted(Comparator.reverseOrder()) 25 | .map(Path::toFile) 26 | .forEach(File::delete) 27 | } 28 | } 29 | 30 | def "test getStartupLogger"() { 31 | when: 32 | Logger logger = LogFactory.getStartupLogger() 33 | then: 34 | logger != null 35 | } 36 | 37 | def "test getTransFormLogger"() { 38 | when: 39 | Logger logger = LogFactory.getTransFormLogger() 40 | then: 41 | logger != null 42 | } 43 | 44 | def "test getAsyncInitBeanLogger - default path"() { 45 | given: 46 | String defaultLogPath = System.getProperty("user.home") + File.separator + "spring-startup-analyzer" + File.separator + "logs" + File.separator 47 | 48 | when: 49 | Logger logger = LogFactory.getAsyncBeanLogger() 50 | then: 51 | logger != null 52 | logger.path().startsWith(defaultLogPath) 53 | } 54 | 55 | def "test getAsyncInitBeanLogger - custom path"() { 56 | given: 57 | System.setProperty("spring-startup-analyzer.log.path", customPathDir.toString()) 58 | LogFactory.initialize() 59 | 60 | when: 61 | Logger logger = LogFactory.getAsyncBeanLogger() 62 | then: 63 | logger != null 64 | logger.path().startsWith(customPathDir.toString()) 65 | } 66 | 67 | def "test createLogger"() { 68 | given: 69 | Logger originalLogger = LogFactory.getStartupLogger() 70 | 71 | when: 72 | URL url = LogFactorySpec.class.getClassLoader().getResource("spring-startup-analyzer.properties") 73 | Path path = Paths.get(url.toURI()) 74 | LogFactory.createLogger(LoggerName.STARTUP, path.getParent().toUri().getPath()) 75 | Logger newLogger = LogFactory.getStartupLogger() 76 | 77 | then: 78 | url != null 79 | originalLogger != newLogger 80 | } 81 | 82 | } 83 | 84 | -------------------------------------------------------------------------------- /spring-profiler-common/src/test/java/io/github/linyimin0812/profiler/common/logger/LoggerSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.logger 2 | 3 | import spock.lang.Shared 4 | import spock.lang.Specification 5 | 6 | import java.nio.charset.StandardCharsets 7 | import java.nio.file.Files 8 | import java.nio.file.Paths 9 | 10 | /** 11 | * @author linyimin 12 | * */ 13 | class LoggerSpec extends Specification { 14 | 15 | @Shared 16 | static Logger logger; 17 | 18 | def setupSpec() { 19 | 20 | URL url = LoggerSpec.class.getClassLoader().getResource("spring-startup-analyzer.properties"); 21 | assert url != null; 22 | 23 | String path = Paths.get(url.toURI()).getParent().toUri().getPath(); 24 | logger = new Logger(LoggerName.STARTUP, path); 25 | 26 | } 27 | 28 | def "test debug"() { 29 | when: 30 | logger.debug(LoggerSpec.class, "debug") 31 | then: 32 | getLogContent().contains("debug") 33 | } 34 | 35 | 36 | def "test debug format"() { 37 | when: 38 | logger.debug(LoggerSpec.class, "debug: {}", "params") 39 | 40 | then: 41 | getLogContent().contains("debug: params") 42 | } 43 | 44 | 45 | def "test warn"() { 46 | when: 47 | logger.debug(LoggerSpec.class, "warn") 48 | then: 49 | getLogContent().contains("warn") 50 | } 51 | 52 | 53 | def "test warn format"() { 54 | when: 55 | logger.debug(LoggerSpec.class, "warn: {}", "params") 56 | 57 | then: 58 | getLogContent().contains("warn: params") 59 | } 60 | 61 | def "test info"() { 62 | when: 63 | logger.debug(LoggerSpec.class, "info") 64 | then: 65 | getLogContent().contains("info") 66 | } 67 | 68 | 69 | def "test info format"() { 70 | when: 71 | logger.debug(LoggerSpec.class, "info: {}", "params") 72 | 73 | then: 74 | getLogContent().contains("info: params") 75 | } 76 | 77 | def "test error"() { 78 | when: 79 | logger.debug(LoggerSpec.class, "error") 80 | then: 81 | getLogContent().contains("error") 82 | } 83 | 84 | 85 | def "test error format"() { 86 | when: 87 | logger.debug(LoggerSpec.class, "error: {}", "params") 88 | 89 | then: 90 | getLogContent().contains("error: params") 91 | } 92 | 93 | def "test error with error"() { 94 | when: 95 | logger.error(LoggerSpec.class, "error: {}, {}", "params", new RuntimeException("")) 96 | then: 97 | getLogContent().contains("error: ") 98 | } 99 | 100 | def "test close"() { 101 | when: 102 | logger.close() 103 | then: 104 | true 105 | } 106 | 107 | String getLogContent() { 108 | 109 | URL url = LoggerSpec.class.getClassLoader().getResource("spring-startup-analyzer.properties"); 110 | assert url != null; 111 | 112 | try { 113 | String path = Paths.get(url.toURI()).getParent().toUri().getPath(); 114 | 115 | byte[] bytes = Files.readAllBytes(Paths.get(path, LoggerName.STARTUP.getValue() + ".log")); 116 | 117 | return new String(bytes, StandardCharsets.UTF_8); 118 | } catch (Exception ignored) { 119 | return ""; 120 | } 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /spring-profiler-common/src/test/java/io/github/linyimin0812/profiler/common/settings/ProfilerSettingsSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.settings 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Stepwise 5 | 6 | /** 7 | * @author linyimin 8 | * */ 9 | @Stepwise 10 | class ProfilerSettingsSpec extends Specification { 11 | 12 | def "get getProperty"() { 13 | 14 | given: 15 | URL configurationURL = ProfilerSettingsSpec.class.getClassLoader().getResource("spring-startup-analyzer.properties") 16 | 17 | when: 18 | ProfilerSettings.loadProperties(configurationURL.getPath()) 19 | 20 | then: 21 | ProfilerSettings.getProperty("key") == null 22 | 'default' == ProfilerSettings.getProperty("key", "default") 23 | 'testValue3' == ProfilerSettings.getProperty("testKey3") 24 | '/xxxxxx/' != ProfilerSettings.getProperty("user.home") 25 | '/xxxxxx/' != ProfilerSettings.getProperty("user.home", "/xxxxxx/") 26 | } 27 | 28 | def "test contains"() { 29 | 30 | given: 31 | URL configurationURL = ProfilerSettingsSpec.class.getClassLoader().getResource("spring-startup-analyzer.properties") 32 | 33 | when: 34 | ProfilerSettings.loadProperties(configurationURL.getPath()) 35 | 36 | then: 37 | ProfilerSettings.contains("testKey2") 38 | !ProfilerSettings.contains("key") 39 | } 40 | def "test isNotBlank"() { 41 | 42 | given: 43 | URL configurationURL = ProfilerSettingsSpec.class.getClassLoader().getResource("spring-startup-analyzer.properties") 44 | 45 | when: 46 | ProfilerSettings.loadProperties(configurationURL.getPath()) 47 | 48 | then: 49 | ProfilerSettings.isNotBlank("test") 50 | !ProfilerSettings.isNotBlank("") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /spring-profiler-common/src/test/java/io/github/linyimin0812/profiler/common/ui/BeanInitResultSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.ui 2 | 3 | import spock.lang.Specification 4 | 5 | /** 6 | * @author linyimin 7 | * */ 8 | class BeanInitResultSpec extends Specification { 9 | 10 | def "test getId"() { 11 | when: 12 | BeanInitResult beanInitResult = new BeanInitResult("test") 13 | then: 14 | beanInitResult.getId() >= 1000 15 | } 16 | 17 | def "test getParentId"() { 18 | 19 | given: 20 | BeanInitResult beanInitResult = new BeanInitResult("test") 21 | BeanInitResult childBeanInitResult = new BeanInitResult("child") 22 | 23 | when: 24 | beanInitResult.addChild(childBeanInitResult) 25 | 26 | then: 27 | childBeanInitResult.getParentId() == beanInitResult.getId() 28 | } 29 | 30 | def "test getName"() { 31 | when: 32 | BeanInitResult beanInitResult = new BeanInitResult("test") 33 | 34 | then: 35 | "test" == beanInitResult.getName() 36 | } 37 | 38 | 39 | def "test getDuration"() { 40 | 41 | given: 42 | BeanInitResult beanInitResult = new BeanInitResult("test") 43 | 44 | when: 45 | beanInitResult.duration() 46 | 47 | then: 48 | beanInitResult.getDuration() >= 0 49 | } 50 | 51 | def "test getActualDuration"() { 52 | 53 | given: 54 | BeanInitResult beanInitResult = new BeanInitResult("test") 55 | BeanInitResult childBeanInitResult = new BeanInitResult("child") 56 | beanInitResult.addChild(childBeanInitResult) 57 | 58 | when: 59 | childBeanInitResult.duration() 60 | beanInitResult.duration() 61 | 62 | then: 63 | beanInitResult.getActualDuration() < 10 64 | } 65 | 66 | def "test duration"() { 67 | 68 | given: 69 | BeanInitResult beanInitResult = new BeanInitResult("test") 70 | BeanInitResult childBeanInitResult = new BeanInitResult("child") 71 | beanInitResult.addChild(childBeanInitResult) 72 | 73 | when: 74 | childBeanInitResult.duration() 75 | beanInitResult.duration() 76 | 77 | then: 78 | beanInitResult.getDuration() == beanInitResult.getActualDuration() + childBeanInitResult.getDuration() 79 | 80 | } 81 | 82 | def "test getTags"() { 83 | 84 | given: 85 | BeanInitResult beanInitResult = new BeanInitResult("test") 86 | Map tags = new HashMap<>() 87 | tags.put("class", "Test") 88 | 89 | when: 90 | beanInitResult.setTags(tags) 91 | 92 | then: 93 | beanInitResult.getTags().containsKey("class") 94 | 95 | } 96 | 97 | def "test getChildren"() { 98 | 99 | given: 100 | BeanInitResult beanInitResult = new BeanInitResult("test") 101 | BeanInitResult childBeanInitResult = new BeanInitResult("child") 102 | 103 | when: 104 | beanInitResult.addChild(childBeanInitResult) 105 | 106 | then: 107 | beanInitResult.getChildren().size() == 1 108 | 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /spring-profiler-common/src/test/java/io/github/linyimin0812/profiler/common/ui/StartupVOSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.ui 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.reflect.TypeToken 5 | import io.github.linyimin0812.profiler.common.utils.GsonUtil 6 | import spock.lang.Specification 7 | import spock.lang.Stepwise 8 | 9 | /** 10 | * @author linyimin 11 | * */ 12 | @Stepwise 13 | class StartupVOSpec extends Specification { 14 | 15 | def "test addBeanInitResult"() { 16 | 17 | given: 18 | BeanInitResult beanInitResult = new BeanInitResult("test") 19 | 20 | when: 21 | beanInitResult.duration() 22 | StartupVO.addBeanInitResult(beanInitResult) 23 | 24 | then: 25 | beanInitResult.getDuration() >= 0 26 | 27 | } 28 | 29 | def "test addStatistics"() { 30 | 31 | given: 32 | Statistics statistics = new Statistics(0, "Startup Time(s)", String.format("%.2f", 180.28)) 33 | 34 | when: 35 | StartupVO.addStatistics(statistics) 36 | 37 | then: 38 | StartupVO.getStatisticsList().size() == 1 39 | } 40 | 41 | def "test addUnusedJar"() { 42 | 43 | given: 44 | URLClassLoader classLoader = new URLClassLoader(new URL[]{}) 45 | Map> unusedJarMap = new HashMap<>(); 46 | Set jars = new HashSet<>() 47 | 48 | when: 49 | jars.add("file:/Users/yiminlin/.m2/repository/org/webjars/swagger-ui/3.25.0/swagger-ui-3.25.0.jar") 50 | unusedJarMap.put(classLoader, jars) 51 | for (Map.Entry> entry : unusedJarMap.entrySet()) { 52 | StartupVO.addUnusedJar(entry) 53 | } 54 | 55 | then: 56 | !unusedJarMap.isEmpty() 57 | } 58 | 59 | def "test addMethodInvokeDetail"() { 60 | 61 | given: 62 | MethodInvokeDetail invokeDetail = new MethodInvokeDetail("io.github.linyimin0812.profiler.common.ui.StartupVOTest.addMethodInvokeDetail", System.currentTimeMillis(), 10) 63 | 64 | when: 65 | StartupVO.addMethodInvokeDetail(invokeDetail) 66 | 67 | then: 68 | invokeDetail.getDuration() == 10 69 | StartupVO.getBeanInitResultList().size() == 1 70 | StartupVO.getStatisticsList().size() == 1 71 | } 72 | 73 | def "test getMethodInvokeDetailList"() { 74 | 75 | given: 76 | Gson GSON = GsonUtil.create(); 77 | TypeToken> typeToken = new TypeToken>() {} 78 | String text = StartupVO.toJSONString() 79 | 80 | when: 81 | Map map = GSON.fromJson(text, typeToken); 82 | 83 | then: 84 | map.containsKey("methodInvokeDetailList") 85 | } 86 | 87 | def "test toJSONString"() { 88 | 89 | given: 90 | Gson GSON = GsonUtil.create(); 91 | String text = StartupVO.toJSONString() 92 | 93 | when: 94 | Map map = GSON.fromJson(text, new TypeToken>() {}) 95 | 96 | then: 97 | map.containsKey("beanInitResultList") 98 | map.containsKey("statisticsList") 99 | map.containsKey("unusedJarMap") 100 | map.containsKey("methodInvokeDetailList") 101 | 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /spring-profiler-common/src/test/java/io/github/linyimin0812/profiler/common/utils/AgentHomeUtilSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.utils 2 | 3 | import spock.lang.Specification 4 | 5 | /** 6 | * @author linyimin 7 | * */ 8 | class AgentHomeUtilSpec extends Specification { 9 | 10 | def "test AgentHomeUtil"() { 11 | when: 12 | String home = AgentHomeUtil.home(); 13 | 14 | then: 15 | home != null 16 | home.contains('spring-startup-analyzer') 17 | 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /spring-profiler-common/src/test/java/io/github/linyimin0812/profiler/common/utils/IpUtilSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.utils 2 | 3 | import spock.lang.Specification 4 | 5 | /** 6 | * @author linyimin 7 | * */ 8 | class IpUtilSpec extends Specification { 9 | def "test getIp" () { 10 | when: 11 | def ip = IpUtil.getIp(); 12 | 13 | then: 14 | ip != null 15 | 16 | String regex = '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' 17 | 18 | ip ==~ regex 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-profiler-common/src/test/java/io/github/linyimin0812/profiler/common/utils/NameUtilSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.utils 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Stepwise 5 | 6 | import java.lang.reflect.Field 7 | 8 | /** 9 | * @author linyimin 10 | * */ 11 | @Stepwise 12 | class NameUtilSpec extends Specification { 13 | def setup() { 14 | Class appNameUtilClass = NameUtil.class 15 | 16 | Field appNameField = appNameUtilClass.getDeclaredField("appName") 17 | appNameField.setAccessible(true) 18 | appNameField.set(null, null) 19 | } 20 | 21 | def "test get app name from project.name" () { 22 | 23 | when: 24 | System.setProperty("project.name", "test-project") 25 | System.setProperty("spring.application.name", "test-application") 26 | System.setProperty("sun.java.command", "TestApplication.jar") 27 | 28 | then: 29 | 'test-project' == NameUtil.getAppName() 30 | 31 | } 32 | 33 | def "test get app name from spring.application.name" () { 34 | when: 35 | System.clearProperty("project.name") 36 | System.setProperty("spring.application.name", "test-application") 37 | System.setProperty("sun.java.command", "TestApplication.jar") 38 | 39 | then: 40 | 'test-application' == NameUtil.getAppName() 41 | } 42 | 43 | def "test get app name from command"() { 44 | when: 45 | System.clearProperty("project.name") 46 | System.clearProperty("spring.application.name") 47 | System.setProperty("sun.java.command", "TestApplication.jar") 48 | 49 | then: 50 | 'TestApplication' == NameUtil.getAppName() 51 | } 52 | 53 | def "test getStartupInstanceName"() { 54 | when: 55 | System.setProperty("project.name", "application") 56 | def instanceName = NameUtil.getStartupInstanceName() 57 | 58 | then: 59 | instanceName != null 60 | 61 | instanceName ==~ '^application-(\\d{14})-(\\d+\\.\\d+\\.\\d+\\.\\d+)$' 62 | } 63 | 64 | def "test getFlameGraphHtmlName"() { 65 | when: 66 | System.setProperty("project.name", "application") 67 | def flameGraphHtmlName = NameUtil.getFlameGraphHtmlName() 68 | 69 | then: 70 | flameGraphHtmlName != null 71 | flameGraphHtmlName ==~ '^application-(\\d{14})-(\\d+\\.\\d+\\.\\d+\\.\\d+)-flame-graph.html$' 72 | } 73 | 74 | def "test getAnalysisHtmlName"() { 75 | when: 76 | System.setProperty("project.name", "application") 77 | def analysisHtmlName = NameUtil.getAnalysisHtmlName() 78 | 79 | then: 80 | analysisHtmlName != null 81 | 82 | analysisHtmlName ==~ '^application-(\\d{14})-(\\d+\\.\\d+\\.\\d+\\.\\d+)-analyzer.html$' 83 | } 84 | 85 | def "test getOutputPath"() { 86 | when: 87 | def output = NameUtil.getOutputPath() 88 | then: 89 | output.contains('/output/') 90 | } 91 | 92 | def "test getTemplatePath"() { 93 | when: 94 | def output = NameUtil.getTemplatePath() 95 | then: 96 | output.contains('/template/') 97 | } 98 | 99 | } 100 | 101 | 102 | -------------------------------------------------------------------------------- /spring-profiler-common/src/test/java/io/github/linyimin0812/profiler/common/utils/OSUtilSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.common.utils 2 | 3 | import spock.lang.Specification 4 | 5 | import java.lang.reflect.Field 6 | 7 | /** 8 | * @author linyimin 9 | * */ 10 | class OSUtilSpec extends Specification { 11 | 12 | def "test platform"() { 13 | 14 | given: 15 | setStatic(OSUtil, 'platform', platform) 16 | 17 | when: 18 | def os = OSUtil.platform() 19 | 20 | then: 21 | os == result 22 | 23 | where: 24 | platform || result 25 | OSUtil.PlatformEnum.LINUX || 'LINUX' 26 | OSUtil.PlatformEnum.MACOS || 'MACOS' 27 | OSUtil.PlatformEnum.WINDOWS || 'WINDOWS' 28 | } 29 | 30 | def "test isLinux"() { 31 | when: 32 | setStatic(OSUtil, 'platform', OSUtil.PlatformEnum.LINUX) 33 | then: 34 | OSUtil.isLinux() 35 | } 36 | 37 | def "test isMac"() { 38 | when: 39 | setStatic(OSUtil, 'platform', OSUtil.PlatformEnum.MACOS) 40 | then: 41 | OSUtil.isMac() 42 | } 43 | 44 | def "test isWindows"() { 45 | when: 46 | setStatic(OSUtil, 'platform', OSUtil.PlatformEnum.WINDOWS) 47 | then: 48 | OSUtil.isWindows() 49 | } 50 | 51 | def "test arch"() { 52 | when: 53 | setStatic(OSUtil, 'arch', arch) 54 | 55 | then: 56 | OSUtil.arch() == result 57 | 58 | where: 59 | arch || result 60 | 'arm_32' || 'arm_32' 61 | 'aarch_64' || 'aarch_64' 62 | 'x86_32' || 'x86_32' 63 | 'x86_64' || 'x86_64' 64 | 65 | } 66 | 67 | def "test isArm32"() { 68 | when: 69 | setStatic(OSUtil, 'arch', "arm_32") 70 | 71 | then: 72 | OSUtil.isArm32() 73 | } 74 | 75 | def "test isArm64"() { 76 | when: 77 | setStatic(OSUtil, 'arch', "aarch_64") 78 | 79 | then: 80 | OSUtil.isArm64() 81 | } 82 | 83 | def "test isX86"() { 84 | when: 85 | setStatic(OSUtil, 'arch', "x86_32") 86 | 87 | then: 88 | OSUtil.isX86() 89 | } 90 | 91 | def "test isX86_64"() { 92 | when: 93 | setStatic(OSUtil, 'arch', "x86_64") 94 | 95 | then: 96 | OSUtil.isX86_64() 97 | } 98 | 99 | static void setStatic(Class clazz, String fieldName, Object value) { 100 | Field filed = clazz.getDeclaredField(fieldName) 101 | filed.accessible = true 102 | 103 | filed.set(null, value) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /spring-profiler-common/src/test/resources/spring-startup-analyzer.properties: -------------------------------------------------------------------------------- 1 | testKey1=testValue1 2 | testKey2=testValue2 3 | testKey3=testValue3 4 | user.home=/xxxxxx/ -------------------------------------------------------------------------------- /spring-profiler-core/src/main/java/io/github/linyimin0812/profiler/core/enhance/Interceptor.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.enhance; 2 | 3 | import com.alibaba.bytekit.asm.binding.Binding; 4 | import com.alibaba.bytekit.asm.interceptor.annotation.AtEnter; 5 | import com.alibaba.bytekit.asm.interceptor.annotation.AtExceptionExit; 6 | import com.alibaba.bytekit.asm.interceptor.annotation.AtExit; 7 | import io.github.linyimin0812.Bridge; 8 | 9 | /** 10 | * @author linyimin 11 | **/ 12 | public class Interceptor { 13 | 14 | @AtEnter 15 | public static void atEnter(@Binding.Class Class clazz, 16 | @Binding.This Object target, 17 | @Binding.MethodName String methodName, 18 | @Binding.MethodDesc String methodDesc, 19 | @Binding.Args Object[] args) { 20 | Bridge.atEnter(clazz, target, methodName, methodDesc, args); 21 | } 22 | 23 | @AtExit 24 | public static void atExit(@Binding.Class Class clazz, 25 | @Binding.This Object target, 26 | @Binding.MethodName String methodName, 27 | @Binding.MethodDesc String methodDesc, 28 | @Binding.Args Object[] args, 29 | @Binding.Return Object returnObj) { 30 | Bridge.atExit(clazz, target, methodName, methodDesc, args, returnObj); 31 | } 32 | 33 | @AtExceptionExit 34 | public static void atExceptionExit(@Binding.Class Class clazz, 35 | @Binding.This Object target, 36 | @Binding.MethodName String methodName, 37 | @Binding.MethodDesc String methodDesc, 38 | @Binding.Args Object[] args, 39 | @Binding.Throwable Throwable throwable) { 40 | Bridge.atExceptionExit(clazz, target, methodName, methodDesc, args, throwable); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spring-profiler-core/src/main/java/io/github/linyimin0812/profiler/core/enhance/Matcher.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.enhance; 2 | 3 | import com.alibaba.deps.org.objectweb.asm.Type; 4 | import com.alibaba.deps.org.objectweb.asm.tree.ClassNode; 5 | import com.alibaba.deps.org.objectweb.asm.tree.MethodNode; 6 | import io.github.linyimin0812.profiler.core.container.IocContainer; 7 | import io.github.linyimin0812.profiler.api.EventListener; 8 | import org.apache.commons.lang3.StringUtils; 9 | 10 | import java.lang.reflect.Method; 11 | import java.util.HashSet; 12 | import java.util.List; 13 | import java.util.Set; 14 | 15 | import static com.alibaba.deps.org.objectweb.asm.Opcodes.*; 16 | 17 | /** 18 | * @author linyimin 19 | **/ 20 | public class Matcher { 21 | 22 | private final static Set FILTER_METHODS = new HashSet<>(); 23 | 24 | static { 25 | // 不对类实例初始化方法注入 26 | FILTER_METHODS.add(""); 27 | // 不对类初始化方法注入 28 | FILTER_METHODS.add(""); 29 | // 不注入类重写的Object父类的方法 30 | Method[] methods = Object.class.getDeclaredMethods(); 31 | for (Method method : methods) { 32 | FILTER_METHODS.add(method.getName()); 33 | } 34 | } 35 | 36 | public static boolean isJavaProfilerFamily(ClassNode classNode) { 37 | 38 | String className = classNode.name; 39 | 40 | if (className.startsWith("com/github/linyimin/profiler")) { 41 | return true; 42 | } 43 | 44 | List interfaces = classNode.interfaces; 45 | 46 | return interfaces.stream().anyMatch(name -> name.contains("io/github/linyimin0812/profiler/api")); 47 | } 48 | 49 | public static boolean isMatchClass(ClassNode classNode) { 50 | 51 | String className = StringUtils.replaceAll(classNode.name, "/", "."); 52 | return isMatchClass(className); 53 | } 54 | 55 | public static boolean isMatchClass(String className) { 56 | 57 | for (EventListener listener : IocContainer.getComponents(EventListener.class)) { 58 | if (listener.filter(className)) { 59 | return true; 60 | } 61 | } 62 | return false; 63 | } 64 | 65 | public static boolean isMatchMethod(ClassNode classNode, MethodNode methodNode) { 66 | 67 | // 不对抽象、native等方法注入 68 | if ((methodNode.access & ACC_ABSTRACT) != 0 69 | || (methodNode.access & ACC_NATIVE) != 0 70 | || (methodNode.access & ACC_BRIDGE) != 0 71 | || (methodNode.access & ACC_SYNTHETIC) != 0 72 | || (methodNode.access & ACC_VARARGS) != 0) { 73 | return false; 74 | } 75 | 76 | if (FILTER_METHODS.contains(methodNode.name)) { 77 | return false; 78 | } 79 | 80 | String className = StringUtils.replaceAll(classNode.name, "/", "."); 81 | 82 | Type methodType = Type.getMethodType(methodNode.desc); 83 | 84 | Type[] types = methodType.getArgumentTypes(); 85 | 86 | String[] args = new String[types.length]; 87 | 88 | for (int i = 0; i < types.length; i++) { 89 | args[i] = types[i].getClassName(); 90 | } 91 | 92 | for (EventListener listener : IocContainer.getComponents(EventListener.class)) { 93 | if (listener.filter(className) && listener.filter(methodNode.name, args)) { 94 | return true; 95 | } 96 | } 97 | 98 | return false; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /spring-profiler-core/src/main/java/io/github/linyimin0812/profiler/core/monitor/ApplicationRunMonitor.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.monitor; 2 | 3 | import io.github.linyimin0812.profiler.api.EventListener; 4 | import io.github.linyimin0812.profiler.api.event.AtExitEvent; 5 | import io.github.linyimin0812.profiler.api.event.Event; 6 | import io.github.linyimin0812.profiler.common.logger.LogFactory; 7 | import io.github.linyimin0812.profiler.common.logger.Logger; 8 | import io.github.linyimin0812.profiler.core.container.IocContainer; 9 | import org.kohsuke.MetaInfServices; 10 | 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | /** 15 | * @author linyimin 16 | **/ 17 | @MetaInfServices 18 | public class ApplicationRunMonitor implements EventListener { 19 | 20 | private final Logger logger = LogFactory.getStartupLogger(); 21 | 22 | @Override 23 | public boolean filter(String className) { 24 | return "org.springframework.boot.SpringApplication".equals(className); 25 | } 26 | 27 | @Override 28 | public boolean filter(String methodName, String[] methodTypes) { 29 | 30 | if (!"run".equals(methodName) || methodTypes == null) { 31 | return false; 32 | } 33 | 34 | if (methodTypes.length == 1) { 35 | return "java.lang.String[]".equals(methodTypes[0]); 36 | } 37 | 38 | if (methodTypes.length == 2) { 39 | return ("java.lang.Class[]".equals(methodTypes[0]) || "java.lang.Object[]".equals(methodTypes[0])) && "java.lang.String[]".equals(methodTypes[1]); 40 | } 41 | 42 | return false; 43 | 44 | } 45 | 46 | @Override 47 | public void onEvent(Event event) { 48 | if (! (event instanceof AtExitEvent)) { 49 | return; 50 | } 51 | 52 | IocContainer.stop(); 53 | } 54 | 55 | @Override 56 | public List listen() { 57 | return Collections.singletonList(Event.Type.AT_EXIT); 58 | } 59 | 60 | @Override 61 | public void start() { 62 | logger.info(ApplicationRunMonitor.class, "=============ApplicationRunMonitor start============="); 63 | } 64 | 65 | @Override 66 | public void stop() { 67 | logger.info(ApplicationRunMonitor.class, "=============ApplicationRunMonitor stop============="); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /spring-profiler-core/src/main/java/io/github/linyimin0812/profiler/core/monitor/StartupMonitor.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.monitor; 2 | 3 | import io.github.linyimin0812.profiler.common.logger.LogFactory; 4 | import io.github.linyimin0812.profiler.common.logger.Logger; 5 | import io.github.linyimin0812.profiler.core.container.IocContainer; 6 | import io.github.linyimin0812.profiler.core.monitor.check.AppStatus; 7 | import io.github.linyimin0812.profiler.core.monitor.check.AppStatusCheckService; 8 | import io.github.linyimin0812.profiler.api.Lifecycle; 9 | import org.kohsuke.MetaInfServices; 10 | 11 | /** 12 | * 应用启动检测 13 | * @author linyimin 14 | **/ 15 | @MetaInfServices 16 | public class StartupMonitor implements Lifecycle { 17 | 18 | private final Logger logger = LogFactory.getStartupLogger(); 19 | 20 | private void checkStatus() { 21 | 22 | int count = 0; 23 | 24 | while (true) { 25 | boolean isRunning = IocContainer.getComponents(AppStatusCheckService.class).stream().anyMatch(service -> service.check() == AppStatus.running); 26 | if (isRunning) { 27 | break; 28 | } 29 | 30 | if (count++ % 10 == 0) { 31 | logger.info(StartupMonitor.class, "app initializing {} s", count); 32 | } 33 | 34 | try { 35 | Thread.sleep(1000); 36 | } catch (InterruptedException e) { 37 | logger.error(StartupMonitor.class, "sleep interrupt", e); 38 | Thread.currentThread().interrupt(); 39 | break; 40 | } 41 | } 42 | 43 | // 应用启动结束后容器终止 44 | IocContainer.stop(); 45 | } 46 | 47 | @Override 48 | public void start() { 49 | logger.info(StartupMonitor.class, "==========StartupMonitor start========"); 50 | Thread startupMonitorThread = new Thread(this::checkStatus); 51 | startupMonitorThread.setName("StartupMonitor-Thread"); 52 | startupMonitorThread.start(); 53 | } 54 | 55 | @Override 56 | public void stop() { 57 | // 应用启动结束后容器终止 58 | logger.info(StartupMonitor.class, "==========StartupMonitor stop========"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /spring-profiler-core/src/main/java/io/github/linyimin0812/profiler/core/monitor/check/AppStatus.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.monitor.check; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public enum AppStatus { 7 | /** 8 | * 启动中 9 | */ 10 | initializing, 11 | /** 12 | * 运行中 13 | */ 14 | running, 15 | 16 | /** 17 | * 启动失败 18 | */ 19 | failed 20 | 21 | } 22 | -------------------------------------------------------------------------------- /spring-profiler-core/src/main/java/io/github/linyimin0812/profiler/core/monitor/check/AppStatusCheckService.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.monitor.check; 2 | 3 | /** 4 | * 检查应用状态 5 | * @author linyimin 6 | **/ 7 | public interface AppStatusCheckService { 8 | 9 | default void init() {} 10 | 11 | AppStatus check(); 12 | } 13 | -------------------------------------------------------------------------------- /spring-profiler-core/src/main/java/io/github/linyimin0812/profiler/core/monitor/check/EndpointCheckService.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.monitor.check; 2 | 3 | import io.github.linyimin0812.profiler.common.logger.LogFactory; 4 | import io.github.linyimin0812.profiler.common.logger.Logger; 5 | import io.github.linyimin0812.profiler.common.settings.ProfilerSettings; 6 | import okhttp3.OkHttpClient; 7 | import okhttp3.Request; 8 | import okhttp3.Response; 9 | import org.kohsuke.MetaInfServices; 10 | 11 | import java.io.IOException; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | import static java.net.HttpURLConnection.HTTP_OK; 17 | 18 | /** 19 | * 通过endpoint检查app状态 20 | * @author linyimin 21 | **/ 22 | @MetaInfServices 23 | public class EndpointCheckService implements AppStatusCheckService { 24 | 25 | private final Logger logger = LogFactory.getStartupLogger(); 26 | 27 | private List healthEndpoints; 28 | 29 | private OkHttpClient client; 30 | 31 | @Override 32 | public void init() { 33 | client = new OkHttpClient().newBuilder().callTimeout(3, TimeUnit.SECONDS).build(); 34 | String endpoints = ProfilerSettings.getProperty("spring-startup-analyzer.app.health.check.endpoints", "http://127.0.0.1:7002/health"); 35 | healthEndpoints = Arrays.asList(endpoints.split(",")); 36 | logger.info(EndpointCheckService.class, "endpoints: {}", healthEndpoints); 37 | } 38 | 39 | @Override 40 | public AppStatus check() { 41 | 42 | for (String endpoint : healthEndpoints) { 43 | Request request = new Request.Builder().url(endpoint).build(); 44 | try (Response response = client.newCall(request).execute()) { 45 | return response.code() == HTTP_OK ? AppStatus.running : AppStatus.initializing; 46 | } catch (IOException ignored) { 47 | } 48 | } 49 | return AppStatus.initializing; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /spring-profiler-core/src/main/java/io/github/linyimin0812/profiler/core/monitor/check/TimeoutCheckService.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.monitor.check; 2 | 3 | 4 | import io.github.linyimin0812.profiler.common.logger.LogFactory; 5 | import io.github.linyimin0812.profiler.common.logger.Logger; 6 | import io.github.linyimin0812.profiler.common.settings.ProfilerSettings; 7 | import org.kohsuke.MetaInfServices; 8 | 9 | import java.time.Duration; 10 | import java.time.Instant; 11 | 12 | /** 13 | * 启动超时检查 14 | * @author linyimin 15 | **/ 16 | @MetaInfServices 17 | public class TimeoutCheckService implements AppStatusCheckService { 18 | 19 | private final Logger logger = LogFactory.getStartupLogger(); 20 | /** 21 | * 超时时间: 默认20分钟 22 | */ 23 | private Duration duration; 24 | private Instant startInstant; 25 | 26 | @Override 27 | public void init() { 28 | long minutes = Long.parseLong(ProfilerSettings.getProperty("spring-startup-analyzer.app.health.check.timeout", "20")); 29 | duration = Duration.ofMinutes(minutes); 30 | startInstant = Instant.now(); 31 | logger.info(TimeoutCheckService.class, "timeout duration: {} minutes", minutes); 32 | } 33 | 34 | @Override 35 | public AppStatus check() { 36 | Duration runDuration = Duration.between(startInstant, Instant.now()); 37 | 38 | // 超时, 默认应用以启动成功 39 | if (duration.compareTo(runDuration) <= 0) { 40 | return AppStatus.running; 41 | } 42 | 43 | return AppStatus.initializing; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spring-profiler-core/src/main/resources/spring-startup-analyzer.properties: -------------------------------------------------------------------------------- 1 | # enter the path where the log will be recorded. if not entered, the default is '~/spring-startup-analyzer/logs'. 2 | spring-startup-analyzer.log.path= 3 | 4 | # app health check timeout, unit is minute 5 | spring-startup-analyzer.app.health.check.timeout=20 6 | # health check endpoint, support multiple endpoints, separated by comma. 7 | spring-startup-analyzer.app.health.check.endpoints=http://localhost:7002/actuator/health 8 | spring-startup-analyzer.admin.http.server.port=8065 9 | 10 | # support configuring multiple methods, separated by | between methods 11 | spring-startup-analyzer.invoke.count.methods=org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(java.lang.String,org.springframework.beans.factory.support.RootBeanDefinition,java.lang.Object[])\ 12 | |java.net.URLClassLoader.findResource(java.lang.String) 13 | 14 | # support two types of profiler: async_profiler | jvm_profiler 15 | spring-startup-analyzer.linux.and.mac.profiler=jvm_profiler 16 | 17 | spring-startup-analyzer.async.profiler.sample.thread.names=main 18 | spring-startup-analyzer.async.profiler.interval.millis=5 19 | -------------------------------------------------------------------------------- /spring-profiler-core/src/test/java/io/github/linyimin0812/profiler/core/container/EventListenerTest.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.container; 2 | 3 | import io.github.linyimin0812.profiler.api.EventListener; 4 | import io.github.linyimin0812.profiler.api.event.AtEnterEvent; 5 | import io.github.linyimin0812.profiler.api.event.AtExceptionExitEvent; 6 | import io.github.linyimin0812.profiler.api.event.AtExitEvent; 7 | import io.github.linyimin0812.profiler.api.event.Event; 8 | import org.kohsuke.MetaInfServices; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * @author linyimin 15 | * only for IocContainer test 16 | **/ 17 | @MetaInfServices 18 | public class EventListenerTest implements EventListener { 19 | 20 | public static AtEnterEvent atEnterEvent; 21 | public static AtExitEvent atExitEvent; 22 | public static AtExceptionExitEvent atExceptionExitEvent; 23 | 24 | @Override 25 | public boolean filter(String className) { 26 | return "org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory".equals(className) 27 | || "java.net.URLClassLoader".equals(className); 28 | } 29 | 30 | @Override 31 | public boolean filter(String methodName, String[] methodTypes) { 32 | if (!"createBean".equals(methodName) && !"findResource".equals(methodName)) { 33 | return false; 34 | } 35 | 36 | if (methodTypes == null) { 37 | return false; 38 | } 39 | 40 | if (methodTypes.length == 3) { 41 | return "java.lang.String".equals(methodTypes[0]) && "org.springframework.beans.factory.support.RootBeanDefinition".equals(methodTypes[1]) && "java.lang.Object[]".equals(methodTypes[2]); 42 | } 43 | 44 | if (methodTypes.length == 1) { 45 | return "java.lang.String".equals(methodTypes[0]); 46 | } 47 | 48 | return false; 49 | 50 | } 51 | 52 | @Override 53 | public void onEvent(Event event) { 54 | if (event instanceof AtEnterEvent) { 55 | atEnterEvent = (AtEnterEvent) event; 56 | } 57 | 58 | if (event instanceof AtExitEvent) { 59 | atExitEvent = (AtExitEvent) event; 60 | } 61 | 62 | if (event instanceof AtExceptionExitEvent) { 63 | atExceptionExitEvent = (AtExceptionExitEvent) event; 64 | } 65 | } 66 | 67 | @Override 68 | public List listen() { 69 | 70 | List types = new ArrayList<>(); 71 | types.add(Event.Type.AT_ENTER); 72 | types.add(Event.Type.AT_EXIT); 73 | types.add(Event.Type.AT_EXCEPTION_EXIT); 74 | 75 | return types; 76 | } 77 | 78 | @Override 79 | public void start() { 80 | 81 | } 82 | 83 | @Override 84 | public void stop() { 85 | 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /spring-profiler-core/src/test/java/io/github/linyimin0812/profiler/core/container/IocContainerSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.container 2 | 3 | import io.github.linyimin0812.profiler.api.EventListener 4 | import io.github.linyimin0812.profiler.api.Lifecycle 5 | import io.github.linyimin0812.profiler.core.http.SimpleHttpServer 6 | import io.github.linyimin0812.profiler.core.http.SimpleHttpServerSpec 7 | import spock.lang.Specification 8 | import spock.lang.Stepwise 9 | 10 | import java.nio.file.Files 11 | import java.nio.file.Path 12 | import java.nio.file.Paths 13 | /** 14 | * @author linyimin 15 | * */ 16 | @Stepwise 17 | class IocContainerSpec extends Specification { 18 | 19 | def setupSpec() { 20 | IocContainer.clean() 21 | } 22 | 23 | def "test copyFile"() { 24 | 25 | given: 26 | URL srcURL = IocContainerSpec.class.getClassLoader().getResource("src/empty.txt") 27 | Path destPath = Paths.get(srcURL.toURI()).getParent().getParent() 28 | Path destFilePath = Paths.get(destPath.toString(), "empty.txt") 29 | Files.deleteIfExists(destFilePath) 30 | 31 | when: 32 | IocContainer.copyFile(srcURL.getPath(), destPath.toString() + '/empty.txt') 33 | 34 | then: 35 | Files.exists(destFilePath) 36 | 37 | cleanup: 38 | Files.deleteIfExists(destFilePath) 39 | } 40 | 41 | def "test start"() { 42 | when: 43 | IocContainer.start() 44 | 45 | then: 46 | IocContainer.getComponent(LifecycleTest.class) != null 47 | IocContainer.isStarted() 48 | 49 | cleanup: 50 | try { 51 | IocContainer.stop() 52 | } catch (Exception ignored) { 53 | 54 | } 55 | 56 | } 57 | 58 | def "test getComponent"() { 59 | when: 60 | LifecycleTest lifecycleTest = IocContainer.getComponent(LifecycleTest.class) 61 | 62 | then: 63 | lifecycleTest != null 64 | 65 | when: 66 | EventListenerTest eventListenerTest = IocContainer.getComponent(EventListenerTest.class) 67 | 68 | then: 69 | eventListenerTest != null 70 | } 71 | 72 | def "test getComponents"() { 73 | when: 74 | List lifecycleList = IocContainer.getComponents(Lifecycle.class) 75 | 76 | then: 77 | lifecycleList.size() == 2 78 | 79 | when: 80 | List eventListeners = IocContainer.getComponents(EventListener.class) 81 | 82 | then: 83 | eventListeners.size() == 2 84 | } 85 | 86 | def "test stop"() { 87 | when: 88 | LifecycleTest lifecycleTest = IocContainer.getComponent(LifecycleTest.class) 89 | 90 | 91 | then: 92 | lifecycleTest != null 93 | 94 | when: 95 | try { 96 | IocContainer.stop() 97 | } catch (Exception ignored) { 98 | 99 | } 100 | 101 | then: 102 | IocContainer.isStopped() 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /spring-profiler-core/src/test/java/io/github/linyimin0812/profiler/core/container/LifecycleTest.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.container; 2 | 3 | import io.github.linyimin0812.profiler.api.Lifecycle; 4 | import org.kohsuke.MetaInfServices; 5 | 6 | /** 7 | * @author linyimin 8 | * only for IocContainer test 9 | **/ 10 | @MetaInfServices 11 | public class LifecycleTest implements Lifecycle { 12 | @Override 13 | public void start() { 14 | 15 | } 16 | 17 | @Override 18 | public void stop() { 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-profiler-core/src/test/java/io/github/linyimin0812/profiler/core/enhance/EventDispatcherSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.enhance 2 | 3 | import io.github.linyimin0812.profiler.core.container.EventListenerTest 4 | import io.github.linyimin0812.profiler.core.container.IocContainer 5 | import spock.lang.Shared 6 | import spock.lang.Specification 7 | 8 | /** 9 | * @author linyimin 10 | * */ 11 | class EventDispatcherSpec extends Specification { 12 | 13 | @Shared 14 | EventDispatcher eventDispatcher = new EventDispatcher() 15 | 16 | def setupSpec() { 17 | IocContainer.clean() 18 | IocContainer.start() 19 | } 20 | 21 | def cleanupSpec() { 22 | try { 23 | IocContainer.stop() 24 | } catch(Exception ignored) { 25 | 26 | } 27 | } 28 | 29 | def "test atEnter and atExit"() { 30 | when: 31 | eventDispatcher.atEnter(URLClassLoader, null, 'findResource', '(Ljava/lang/String;)Ljava/net/URL;', null) 32 | 33 | then: 34 | EventListenerTest.atEnterEvent != null 35 | 36 | when: 37 | eventDispatcher.atExit(URLClassLoader, null, 'findResource', '(Ljava/lang/String;)Ljava/net/URL;', null, null) 38 | 39 | then: 40 | EventListenerTest.atExitEvent != null 41 | EventListenerTest.atExitEvent.invokeId == EventListenerTest.atEnterEvent.invokeId 42 | 43 | } 44 | 45 | def "test atEnter and atExceptionExit"() { 46 | when: 47 | eventDispatcher.atEnter(URLClassLoader, null, 'findResource', '(Ljava/lang/String;)Ljava/net/URL;', null) 48 | 49 | then: 50 | EventListenerTest.atEnterEvent != null 51 | 52 | then: 53 | eventDispatcher.atExceptionExit(URLClassLoader, null, 'findResource', '(Ljava/lang/String;)Ljava/net/URL;', null, null) 54 | 55 | then: 56 | EventListenerTest.atExceptionExitEvent != null 57 | EventListenerTest.atEnterEvent.invokeId == EventListenerTest.atExceptionExitEvent.invokeId 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /spring-profiler-core/src/test/java/io/github/linyimin0812/profiler/core/enhance/MatcherSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.enhance 2 | 3 | import com.alibaba.deps.org.objectweb.asm.tree.ClassNode 4 | import com.alibaba.deps.org.objectweb.asm.tree.MethodNode 5 | import io.github.linyimin0812.profiler.core.container.IocContainer 6 | import spock.lang.Specification 7 | 8 | /** 9 | * 10 | * @author linyimin 11 | * 12 | * */ 13 | class MatcherSpec extends Specification { 14 | 15 | def setupSpec() { 16 | IocContainer.start() 17 | } 18 | 19 | def cleanupSpec() { 20 | try { 21 | IocContainer.stop() 22 | } catch(Exception ignored) { 23 | 24 | } 25 | } 26 | 27 | def "IsJavaProfilerFamily"() { 28 | when: 29 | ClassNode classNode = new ClassNode(name: name, interfaces: interfaces) 30 | 31 | then: 32 | Matcher.isJavaProfilerFamily(classNode) == result 33 | 34 | where: 35 | name | interfaces || result 36 | 'com/github/linyimin/profiler' | ['io/github/linyimin0812/profiler/api/EventListener', 'io/github/linyimin0812/profiler/api/Lifecycle'] || true 37 | 'java/lang/String' | ['java/lang/String'] || false 38 | } 39 | 40 | def "IsMatchClass"() { 41 | when: 42 | ClassNode classNode = new ClassNode(name: name) 43 | 44 | then: 45 | Matcher.isMatchClass(classNode) == result 46 | 47 | where: 48 | name || result 49 | 'org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory' || true 50 | 'java/lang/String' || false 51 | } 52 | 53 | def "TestIsMatchClass"() { 54 | when: 55 | def className = name 56 | 57 | then: 58 | Matcher.isMatchClass(className) == result 59 | 60 | where: 61 | name || result 62 | 'org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory' || true 63 | 'java.lang.String' || false 64 | } 65 | 66 | def "test isMatchMethod with access"() { 67 | 68 | given: 69 | ClassNode classNode = new ClassNode(); 70 | MethodNode methodNode = new MethodNode() 71 | 72 | when: 73 | methodNode.access = access 74 | 75 | then: 76 | Matcher.isMatchMethod(classNode, methodNode) == result 77 | 78 | where: 79 | access || result 80 | 0x0400 || false 81 | 0x0100 || false 82 | 0x0040 || false 83 | 0x1000 || false 84 | 0x0080 || false 85 | } 86 | 87 | def "test isMatchMethod with method name"() { 88 | 89 | when: 90 | ClassNode classNode = new ClassNode(); 91 | MethodNode methodNode = new MethodNode(name: methodName) 92 | 93 | then: 94 | Matcher.isMatchMethod(classNode, methodNode) == result 95 | 96 | where: 97 | methodName || result 98 | '' || false 99 | '' || false 100 | } 101 | 102 | def "test isMatchMethod with method name and method types"() { 103 | 104 | when: 105 | ClassNode classNode = new ClassNode(name: className); 106 | MethodNode methodNode = new MethodNode(name: methodName, desc: desc) 107 | 108 | then: 109 | Matcher.isMatchMethod(classNode, methodNode) == result 110 | 111 | where: 112 | className | methodName | desc || result 113 | 'org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory' | 'createBean' | '(Ljava/lang/String;Lorg/springframework/beans/factory/support/RootBeanDefinition;[Ljava/lang/Object;)Ljava/lang/Object;' || true 114 | 'org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory' | 'createBean' | '(Ljava/lang/Class;)Ljava/lang/Object;' || false 115 | } 116 | 117 | 118 | } 119 | -------------------------------------------------------------------------------- /spring-profiler-core/src/test/java/io/github/linyimin0812/profiler/core/enhance/ProfilerClassFileTransformerSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.enhance 2 | 3 | import spock.lang.Shared 4 | import spock.lang.Specification 5 | 6 | /** 7 | * @author linyimin 8 | * */ 9 | class ProfilerClassFileTransformerSpec extends Specification { 10 | 11 | @Shared 12 | ProfilerClassFileTransformer transformer = new ProfilerClassFileTransformer() 13 | 14 | def "test acquireJavaVersion"() { 15 | when: 16 | String version = transformer.acquireJavaVersion(); 17 | 18 | then: 19 | version != null 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /spring-profiler-core/src/test/java/io/github/linyimin0812/profiler/core/http/SimpleHttpServerSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.http 2 | 3 | import io.github.linyimin0812.profiler.common.settings.ProfilerSettings 4 | import spock.lang.Specification 5 | import spock.lang.Stepwise 6 | 7 | /** 8 | * @author linyimin 9 | * */ 10 | @Stepwise 11 | class SimpleHttpServerSpec extends Specification { 12 | 13 | def "test start"() { 14 | 15 | given: 16 | SimpleHttpServer.stop() 17 | 18 | when: 19 | SimpleHttpServer.start() 20 | 21 | then: 22 | isURLAvailable(SimpleHttpServer.endpoint() + "/hello") 23 | 24 | cleanup: 25 | SimpleHttpServer.stop() 26 | } 27 | 28 | def "test stop"() { 29 | when: 30 | SimpleHttpServer.stop(); 31 | 32 | then: 33 | !isURLAvailable(SimpleHttpServer.endpoint() + "/hello") 34 | 35 | cleanup: 36 | SimpleHttpServer.stop() 37 | 38 | } 39 | 40 | def "test getPort default"() { 41 | when: 42 | ProfilerSettings.clear() 43 | 44 | then: 45 | SimpleHttpServer.getPort() == 8065 46 | } 47 | 48 | def "test getPort from properties"() { 49 | 50 | given: 51 | URL configurationURL = SimpleHttpServerSpec.class.getClassLoader().getResource("spring-startup-analyzer.properties"); 52 | 53 | 54 | when: 55 | ProfilerSettings.loadProperties(configurationURL.getPath()); 56 | 57 | then: 58 | SimpleHttpServer.getPort() == 8066 59 | 60 | } 61 | 62 | def "test endpoint default"() { 63 | when: 64 | ProfilerSettings.clear(); 65 | 66 | then: 67 | SimpleHttpServer.endpoint() == 'http://localhost:8065' 68 | } 69 | 70 | def "test endpoint from properties"() { 71 | 72 | given: 73 | URL configurationURL = SimpleHttpServerSpec.class.getClassLoader().getResource("spring-startup-analyzer.properties"); 74 | 75 | when: 76 | ProfilerSettings.loadProperties(configurationURL.getPath()); 77 | 78 | then: 79 | SimpleHttpServer.endpoint() == 'http://localhost:8066' 80 | 81 | } 82 | 83 | static boolean isURLAvailable(String endpoint) { 84 | try { 85 | URL url = new URL(endpoint); 86 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 87 | connection.setRequestMethod("GET"); 88 | connection.setConnectTimeout(3_000); 89 | connection.setReadTimeout(3_000); 90 | 91 | return connection.getResponseCode() == HttpURLConnection.HTTP_OK; 92 | } catch (IOException ignored) { 93 | return false; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /spring-profiler-core/src/test/java/io/github/linyimin0812/profiler/core/monitor/ApplicationRunMonitorSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.monitor 2 | 3 | import io.github.linyimin0812.profiler.api.event.AtEnterEvent 4 | import io.github.linyimin0812.profiler.api.event.Event 5 | import spock.lang.Shared 6 | import spock.lang.Specification 7 | 8 | /** 9 | * @author linyimin 10 | * */ 11 | class ApplicationRunMonitorSpec extends Specification { 12 | 13 | @Shared 14 | ApplicationRunMonitor applicationRunMonitor = new ApplicationRunMonitor(); 15 | 16 | def "test filter with class name"() { 17 | when: 18 | def filter = applicationRunMonitor.filter(className) 19 | 20 | then: 21 | filter == result 22 | 23 | where: 24 | className || result 25 | 'org.springframework.boot.SpringApplication' || true 26 | 'org.springframework.boot.ApplicationContext' || false 27 | } 28 | 29 | def "test filter both with class name and method name"() { 30 | 31 | when: 32 | def filterResult = applicationRunMonitor.filter(methodName, methodTypes as String[]) 33 | 34 | then: 35 | filterResult == result 36 | 37 | where: 38 | methodName | methodTypes || result 39 | 'filter' | null || false 40 | 'filter' | ['java.lang.Object[]', 'java.lang.String[]'] || false 41 | 'run' | ['java.lang.Object[]', 'java.lang.String[]'] || true 42 | 'run' | ['java.lang.Class[]', 'java.lang.String[]'] || true 43 | 44 | } 45 | 46 | 47 | def "test onEvent"() { 48 | 49 | given: 50 | AtEnterEvent event = new AtEnterEvent(0L, 0L, null, null, null, null, null) 51 | 52 | when: 53 | applicationRunMonitor.onEvent(event) 54 | 55 | then: 56 | applicationRunMonitor.filter("org.springframework.boot.SpringApplication") 57 | 58 | } 59 | 60 | def "test listen"() { 61 | when: 62 | List events = applicationRunMonitor.listen() 63 | 64 | then: 65 | events.size() == 1 66 | events.get(0) == Event.Type.AT_EXIT 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /spring-profiler-core/src/test/java/io/github/linyimin0812/profiler/core/monitor/check/EndpointCheckServiceSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.monitor.check 2 | 3 | import com.sun.net.httpserver.HttpServer 4 | import io.github.linyimin0812.profiler.common.settings.ProfilerSettings 5 | import spock.lang.Shared 6 | import spock.lang.Specification 7 | import spock.lang.Stepwise 8 | 9 | import java.lang.reflect.Field 10 | 11 | /** 12 | * @author linyimin 13 | * */ 14 | @Stepwise 15 | class EndpointCheckServiceSpec extends Specification { 16 | 17 | @Shared 18 | static HttpServer server; 19 | @Shared 20 | EndpointCheckService endpointCheckService = new EndpointCheckService(); 21 | 22 | def "test init"() { 23 | 24 | given: 25 | URL configUrl = EndpointCheckServiceSpec.class.getClassLoader().getResource("spring-startup-analyzer.properties"); 26 | 27 | when: 28 | ProfilerSettings.loadProperties(configUrl.getPath()); 29 | endpointCheckService.init(); 30 | Field healthEndpointsField = endpointCheckService.getClass().getDeclaredField("healthEndpoints"); 31 | healthEndpointsField.setAccessible(true); 32 | 33 | List healthEndpoints = (List) healthEndpointsField.get(endpointCheckService); 34 | 35 | then: 36 | healthEndpoints != null 37 | healthEndpoints.size() == 1 38 | healthEndpoints.get(0) == 'http://localhost:12346' 39 | } 40 | 41 | 42 | def "test check after init"() { 43 | when: 44 | endpointCheckService.init(); 45 | 46 | then: 47 | AppStatus.initializing == endpointCheckService.check() 48 | } 49 | 50 | def "test check after start"() { 51 | when: 52 | start() 53 | 54 | then: 55 | AppStatus.running == endpointCheckService.check() 56 | 57 | cleanup: 58 | stop() 59 | } 60 | 61 | def "test check after stop"() { 62 | when: 63 | stop() 64 | 65 | then: 66 | AppStatus.initializing == endpointCheckService.check() 67 | } 68 | 69 | private static void start() { 70 | 71 | int port = 12346; 72 | 73 | try { 74 | server = HttpServer.create(new InetSocketAddress(port), 0); 75 | server.createContext("/", httpExchange -> { 76 | try { 77 | httpExchange.sendResponseHeaders(200, 0); 78 | httpExchange.getResponseBody().close(); 79 | } catch (IOException e) { 80 | throw new RuntimeException(e); 81 | } 82 | }); 83 | server.setExecutor(null); 84 | server.start(); 85 | System.out.println("Server listening on port " + port); 86 | } catch (IOException e) { 87 | throw new RuntimeException(e); 88 | } 89 | } 90 | 91 | private static void stop() { 92 | server.stop(0); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /spring-profiler-core/src/test/java/io/github/linyimin0812/profiler/core/monitor/check/TimeoutCheckServiceSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.core.monitor.check 2 | 3 | import io.github.linyimin0812.profiler.common.settings.ProfilerSettings 4 | import spock.lang.Shared 5 | import spock.lang.Specification 6 | 7 | import java.lang.reflect.Field 8 | import java.time.Duration 9 | 10 | /** 11 | * @author linyimin 12 | * */ 13 | class TimeoutCheckServiceSpec extends Specification { 14 | 15 | @Shared 16 | final TimeoutCheckService timeoutCheckService = new TimeoutCheckService(); 17 | 18 | def "test init"() { 19 | 20 | given: 21 | URL configUrl = TimeoutCheckServiceSpec.class.getClassLoader().getResource("spring-startup-analyzer.properties") 22 | ProfilerSettings.loadProperties(configUrl.getPath()) 23 | 24 | when: 25 | timeoutCheckService.init() 26 | 27 | Field durationField = timeoutCheckService.getClass().getDeclaredField("duration") 28 | durationField.setAccessible(true) 29 | 30 | Duration duration = (Duration) durationField.get(timeoutCheckService) 31 | 32 | then: 33 | duration != null 34 | duration.getSeconds() == 60 35 | } 36 | 37 | def "test check"() { 38 | when: 39 | timeoutCheckService.init() 40 | 41 | then: 42 | AppStatus.initializing == timeoutCheckService.check() 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /spring-profiler-core/src/test/resources/spring-startup-analyzer.properties: -------------------------------------------------------------------------------- 1 | spring-startup-analyzer.admin.http.server.port=8066 2 | spring-startup-analyzer.app.health.check.endpoints=http://localhost:12346 3 | spring-startup-analyzer.app.health.check.timeout=1 -------------------------------------------------------------------------------- /spring-profiler-core/src/test/resources/src/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/ecee7021024d3ade8f5fb1ab0bc84ad6a551b15e/spring-profiler-core/src/test/resources/src/empty.txt -------------------------------------------------------------------------------- /spring-profiler-extension/async-profiler/libasyncProfiler-linux-arm64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/ecee7021024d3ade8f5fb1ab0bc84ad6a551b15e/spring-profiler-extension/async-profiler/libasyncProfiler-linux-arm64.so -------------------------------------------------------------------------------- /spring-profiler-extension/async-profiler/libasyncProfiler-linux-musl-arm64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/ecee7021024d3ade8f5fb1ab0bc84ad6a551b15e/spring-profiler-extension/async-profiler/libasyncProfiler-linux-musl-arm64.so -------------------------------------------------------------------------------- /spring-profiler-extension/async-profiler/libasyncProfiler-linux-musl-x64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/ecee7021024d3ade8f5fb1ab0bc84ad6a551b15e/spring-profiler-extension/async-profiler/libasyncProfiler-linux-musl-x64.so -------------------------------------------------------------------------------- /spring-profiler-extension/async-profiler/libasyncProfiler-linux-x64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/ecee7021024d3ade8f5fb1ab0bc84ad6a551b15e/spring-profiler-extension/async-profiler/libasyncProfiler-linux-x64.so -------------------------------------------------------------------------------- /spring-profiler-extension/async-profiler/libasyncProfiler-mac.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/ecee7021024d3ade8f5fb1ab0bc84ad6a551b15e/spring-profiler-extension/async-profiler/libasyncProfiler-mac.so -------------------------------------------------------------------------------- /spring-profiler-extension/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | io.github.linyimin0812 8 | spring-profiler-starter 9 | ${revision} 10 | ../spring-profiler-starter/pom.xml 11 | 12 | 4.0.0 13 | 14 | spring-profiler-extension 15 | 16 | spring-profiler-extension 17 | 18 | 19 | 20 | 21 | com.google.code.gson 22 | gson 23 | 24 | 25 | 26 | org.apache.commons 27 | commons-lang3 28 | 29 | 30 | 31 | com.google.guava 32 | guava 33 | 32.1.1-jre 34 | 35 | 36 | 37 | com.alibaba 38 | bytekit-core 39 | 0.0.8 40 | provided 41 | 42 | 43 | 44 | com.squareup.okhttp3 45 | okhttp 46 | 47 | 48 | 49 | org.spockframework 50 | spock-core 51 | test 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-antrun-plugin 61 | 1.8 62 | 63 | 64 | process-resources 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | run 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /spring-profiler-extension/src/main/java/io/github/linyimin0812/profiler/extension/enhance/sample/AsyncProfilerListener.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.extension.enhance.sample; 2 | 3 | import io.github.linyimin0812.profiler.api.EventListener; 4 | import io.github.linyimin0812.profiler.api.event.Event; 5 | import io.github.linyimin0812.profiler.common.logger.LogFactory; 6 | import io.github.linyimin0812.profiler.common.logger.Logger; 7 | import io.github.linyimin0812.profiler.common.settings.ProfilerSettings; 8 | import io.github.linyimin0812.profiler.common.utils.OSUtil; 9 | import io.github.linyimin0812.profiler.extension.enhance.sample.asyncprofiler.AsyncProfiler; 10 | import io.github.linyimin0812.profiler.extension.enhance.sample.jvmprofiler.StacktraceProfiler; 11 | import org.apache.commons.lang3.StringUtils; 12 | import org.kohsuke.MetaInfServices; 13 | import java.util.*; 14 | 15 | /** 16 | * @author linyimin 17 | **/ 18 | @MetaInfServices(EventListener.class) 19 | public class AsyncProfilerListener implements EventListener { 20 | 21 | private final Logger logger = LogFactory.getStartupLogger(); 22 | 23 | private Profiler profiler; 24 | 25 | @Override 26 | public boolean filter(String className) { 27 | // 28 | return false; 29 | } 30 | 31 | @Override 32 | public void onEvent(Event event) { 33 | 34 | } 35 | 36 | @Override 37 | public List listen() { 38 | return Collections.emptyList(); 39 | } 40 | 41 | @Override 42 | public void start() { 43 | logger.info(AsyncProfilerListener.class, "==============AsyncProfilerListener start========================"); 44 | logger.info(AsyncProfilerListener.class, "platform:{}, arch: {}", OSUtil.platform(), OSUtil.arch()); 45 | 46 | if (OSUtil.isWindows()) { 47 | profiler = new StacktraceProfiler(); 48 | } else { 49 | String type = ProfilerSettings.getProperty("spring-startup-analyzer.linux.and.mac.profiler", ProfilerType.ASYNC_PROFILER.name()); 50 | if (StringUtils.endsWithIgnoreCase(type, ProfilerType.ASYNC_PROFILER.name())) { 51 | profiler = new AsyncProfiler(); 52 | } else { 53 | profiler = new StacktraceProfiler(); 54 | } 55 | } 56 | 57 | profiler.start(); 58 | 59 | } 60 | 61 | @Override 62 | public void stop() { 63 | logger.info(AsyncProfilerListener.class, "==============AsyncProfilerListener stop========================"); 64 | 65 | profiler.stop(); 66 | 67 | } 68 | 69 | enum ProfilerType { 70 | JVM_PROFILER, 71 | ASYNC_PROFILER 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /spring-profiler-extension/src/main/java/io/github/linyimin0812/profiler/extension/enhance/sample/Profiler.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.extension.enhance.sample; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public interface Profiler { 7 | 8 | String SAMPLE_THREAD_NAME_CONFIG_ID = "spring-startup-analyzer.async.profiler.sample.thread.names"; 9 | String SAMPLE_INTERVAL_MILLIS_CONFIG_ID = "spring-startup-analyzer.async.profiler.interval.millis"; 10 | 11 | void start(); 12 | void stop(); 13 | } 14 | -------------------------------------------------------------------------------- /spring-profiler-extension/src/main/java/io/github/linyimin0812/profiler/extension/enhance/sample/asyncprofiler/one/AsyncProfilerMXBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.linyimin0812.profiler.extension.enhance.sample.asyncprofiler.one; 18 | 19 | /** 20 | * AsyncProfiler interface for JMX server. 21 | * How to register AsyncProfiler MBean: 22 | * 23 | *
{@code
24 |  *     ManagementFactory.getPlatformMBeanServer().registerMBean(
25 |  *             AsyncProfiler.getInstance(),
26 |  *             new ObjectName("one.profiler:type=AsyncProfiler")
27 |  *     );
28 |  * }
29 | */ 30 | public interface AsyncProfilerMXBean { 31 | void start(String event, long interval) throws IllegalStateException; 32 | void resume(String event, long interval) throws IllegalStateException; 33 | void stop() throws IllegalStateException; 34 | 35 | long getSamples(); 36 | String getVersion(); 37 | 38 | String execute(String command) throws IllegalArgumentException, IllegalStateException, java.io.IOException; 39 | 40 | String dumpCollapsed(Counter counter); 41 | String dumpTraces(int maxTraces); 42 | String dumpFlat(int maxMethods); 43 | } 44 | -------------------------------------------------------------------------------- /spring-profiler-extension/src/main/java/io/github/linyimin0812/profiler/extension/enhance/sample/asyncprofiler/one/Counter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.linyimin0812.profiler.extension.enhance.sample.asyncprofiler.one; 18 | 19 | /** 20 | * Which metrics to use when generating profile in collapsed stack traces format. 21 | */ 22 | public enum Counter { 23 | SAMPLES, 24 | TOTAL 25 | } 26 | -------------------------------------------------------------------------------- /spring-profiler-extension/src/main/java/io/github/linyimin0812/profiler/extension/enhance/sample/asyncprofiler/one/Events.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Andrei Pangin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.linyimin0812.profiler.extension.enhance.sample.asyncprofiler.one; 18 | 19 | /** 20 | * Predefined event names to use in {@link AsyncProfiler#start(String, long)} 21 | */ 22 | public class Events { 23 | public static final String CPU = "cpu"; 24 | public static final String ALLOC = "alloc"; 25 | public static final String LOCK = "lock"; 26 | public static final String WALL = "wall"; 27 | public static final String ITIMER = "itimer"; 28 | } 29 | -------------------------------------------------------------------------------- /spring-profiler-extension/src/main/java/io/github/linyimin0812/profiler/extension/enhance/sample/asyncprofiler/one/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * from https://github.com/jvm-profiling-tools/async-profiler 4 | */ 5 | package io.github.linyimin0812.profiler.extension.enhance.sample.asyncprofiler.one; 6 | -------------------------------------------------------------------------------- /spring-profiler-extension/src/main/java/io/github/linyimin0812/profiler/extension/enhance/springbean/PersistentThreadLocal.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.extension.enhance.springbean; 2 | 3 | import java.util.Collection; 4 | import java.util.Collections; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.function.Supplier; 8 | 9 | /** 10 | * @author linyimin 11 | **/ 12 | public class PersistentThreadLocal extends ThreadLocal { 13 | 14 | final Map allValues; 15 | final Supplier valueGetter; 16 | 17 | public PersistentThreadLocal(Supplier initialValue) { 18 | this(0, initialValue); 19 | } 20 | 21 | public PersistentThreadLocal(int numThread, Supplier initialValue) { 22 | allValues = Collections.synchronizedMap( 23 | numThread > 0 ? new HashMap<>(numThread) : new HashMap<>() 24 | ); 25 | 26 | valueGetter = initialValue; 27 | } 28 | 29 | @Override 30 | protected T initialValue() { 31 | T value = valueGetter != null ? valueGetter.get() : super.initialValue(); 32 | allValues.put(Thread.currentThread(), value); 33 | return value; 34 | } 35 | 36 | @Override 37 | public void set(T value) { 38 | super.set(value); 39 | allValues.put(Thread.currentThread(), value); 40 | } 41 | 42 | @Override 43 | public void remove() { 44 | super.remove(); 45 | allValues.remove(Thread.currentThread()); 46 | } 47 | 48 | public Collection getAll() { 49 | return allValues.values(); 50 | } 51 | 52 | public void clear() { 53 | allValues.clear(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /spring-profiler-extension/src/test/java/io/github/linyimin0812/profiler/extension/enhance/invoke/InvokeDetailListenerSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.extension.enhance.invoke 2 | 3 | import io.github.linyimin0812.profiler.api.event.Event 4 | import io.github.linyimin0812.profiler.common.settings.ProfilerSettings 5 | import io.github.linyimin0812.profiler.common.ui.MethodInvokeDetail 6 | import spock.lang.Shared 7 | import spock.lang.Specification 8 | 9 | import java.lang.reflect.Field 10 | 11 | /** 12 | * @author linyimin 13 | * */ 14 | class InvokeDetailListenerSpec extends Specification { 15 | 16 | @Shared 17 | final InvokeDetailListener invokeDetailListener = new InvokeDetailListener() 18 | 19 | def setup() { 20 | URL configurationURL = InvokeDetailListenerSpec.class.getClassLoader().getResource("spring-startup-analyzer.properties") 21 | assert configurationURL != null 22 | ProfilerSettings.loadProperties(configurationURL.getPath()) 23 | 24 | invokeDetailListener.start() 25 | } 26 | 27 | def "test filter with class name"() { 28 | when: 29 | def filter = invokeDetailListener.filter(className) 30 | 31 | then: 32 | filter == result 33 | 34 | where: 35 | className || result 36 | 'java.net.URLClassLoader' || true 37 | 'java.lang.String' || false 38 | } 39 | 40 | def "test filter with method name and method types"() { 41 | when: 42 | def filter = invokeDetailListener.filter(methodName, methodTypes) 43 | 44 | then: 45 | filter == result 46 | 47 | where: 48 | methodName | methodTypes || result 49 | 'findResource' | new String[] {'java.lang.String'} || true 50 | 'findResource' | new String[] {} || false 51 | } 52 | 53 | def "test listen"() { 54 | when: 55 | List list = invokeDetailListener.listen() 56 | 57 | then: 58 | list.size() == 2 59 | } 60 | 61 | def "test start"() { 62 | when: 63 | Field field = invokeDetailListener.getClass().getDeclaredField("methodQualifiers") 64 | field.setAccessible(true) 65 | List methodQualifiers = (List) field.get(invokeDetailListener) 66 | 67 | then: 68 | methodQualifiers.size() == 1 69 | methodQualifiers.get(0) == 'java.net.URLClassLoader.findResource(java.lang.String)' 70 | } 71 | 72 | def "test stop"() { 73 | when: 74 | invokeDetailListener.stop(); 75 | Field field = invokeDetailListener.getClass().getDeclaredField("methodQualifiers") 76 | field.setAccessible(true) 77 | List methodQualifiers = (List) field.get(invokeDetailListener) 78 | 79 | field = invokeDetailListener.getClass().getDeclaredField("INVOKE_DETAIL_MAP") 80 | field.setAccessible(true) 81 | Map map = (Map) field.get(invokeDetailListener) 82 | 83 | then: 84 | methodQualifiers.isEmpty() 85 | map.isEmpty() 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /spring-profiler-extension/src/test/java/io/github/linyimin0812/profiler/extension/enhance/sample/AsyncProfilerListenerSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.extension.enhance.sample 2 | 3 | import io.github.linyimin0812.profiler.api.event.Event 4 | import spock.lang.Shared 5 | import spock.lang.Specification 6 | 7 | /** 8 | * @author linyimin 9 | * */ 10 | class AsyncProfilerListenerSpec extends Specification { 11 | 12 | @Shared 13 | AsyncProfilerListener asyncProfilerListener = new AsyncProfilerListener(); 14 | 15 | def "test filter with class name"() { 16 | when: 17 | def filter = asyncProfilerListener.filter("") 18 | 19 | then: 20 | !filter 21 | } 22 | 23 | def "test filter with method name and method types"() { 24 | when: 25 | def filter = asyncProfilerListener.filter("", new String[] {}) 26 | 27 | then: 28 | filter 29 | } 30 | 31 | def "test listen"() { 32 | when: 33 | List list = asyncProfilerListener.listen() 34 | 35 | then: 36 | list.isEmpty() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spring-profiler-extension/src/test/java/io/github/linyimin0812/profiler/extension/enhance/sample/jvmprofiler/FlameGraphSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.extension.enhance.sample.jvmprofiler 2 | 3 | import spock.lang.Shared 4 | import spock.lang.Specification 5 | 6 | import java.nio.file.Files 7 | import java.nio.file.Path 8 | import java.nio.file.Paths 9 | 10 | /** 11 | * @author linyimin 12 | * */ 13 | class FlameGraphSpec extends Specification { 14 | 15 | @Shared 16 | final Map TRACE_MAP = new HashMap<>() 17 | 18 | def setup() { 19 | URL profilerURL = FlameGraphSpec.class.getClassLoader().getResource("profiler.txt") 20 | assert profilerURL != null 21 | 22 | InputStreamReader input = new InputStreamReader(Files.newInputStream(Paths.get(profilerURL.getPath()))); 23 | try (BufferedReader br = new BufferedReader(input)) { 24 | for (String line; (line = br.readLine()) != null;) { 25 | int spaceIndex = line.indexOf(" ") 26 | if (spaceIndex < 0) { 27 | continue 28 | } 29 | 30 | String trace = line.substring(0, spaceIndex) 31 | int ticks = Integer.parseInt(line.substring(spaceIndex + 1)) 32 | 33 | TRACE_MAP.put(trace, ticks) 34 | } 35 | } 36 | } 37 | 38 | def "test parse"() { 39 | 40 | given: 41 | FlameGraph fg = new FlameGraph() 42 | URL templateURL = FlameGraphSpec.class.getClassLoader().getResource("flame-graph.html") 43 | String destPath = Paths.get(templateURL.toURI()).getParent().toString() + "/result-flame-graph.html" 44 | 45 | when: 46 | fg.parse(templateURL.getPath(), destPath, TRACE_MAP) 47 | Path resultPath = Paths.get(destPath) 48 | 49 | then: 50 | Files.exists(resultPath) 51 | 52 | cleanup: 53 | Files.deleteIfExists(resultPath) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /spring-profiler-extension/src/test/java/io/github/linyimin0812/profiler/extension/enhance/springbean/BeanCreateListenerSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.profiler.extension.enhance.springbean 2 | 3 | import spock.lang.Shared 4 | import spock.lang.Specification 5 | 6 | /** 7 | * @author linyimin 8 | * */ 9 | class BeanCreateListenerSpec extends Specification { 10 | 11 | @Shared 12 | final BeanCreateListener beanCreateListener = new BeanCreateListener(); 13 | 14 | def "test filter with class name"() { 15 | when: 16 | def filter = beanCreateListener.filter(className) 17 | then: 18 | filter == result 19 | where: 20 | className || result 21 | 'org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory' || true 22 | 'AbstractAutowireCapableBeanFactory' || false 23 | } 24 | 25 | 26 | def "test filter with method name and method types"() { 27 | when: 28 | def filter = beanCreateListener.filter(methodName, methodTypes) 29 | 30 | then: 31 | filter == result 32 | 33 | where: 34 | methodName | methodTypes || result 35 | 'createBean' | new String[] { "java.lang.String", "org.springframework.beans.factory.support.RootBeanDefinition", "java.lang.Object[]"} || true 36 | 'createBean' | new String[] {} || false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spring-profiler-extension/src/test/resources/spring-startup-analyzer.properties: -------------------------------------------------------------------------------- 1 | spring-startup-analyzer.invoke.count.methods=java.net.URLClassLoader.findResource(java.lang.String) -------------------------------------------------------------------------------- /spring-profiler-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-startup-analyzer 7 | io.github.linyimin0812 8 | ${revision} 9 | 10 | 4.0.0 11 | 12 | 13 | ${user.home}/spring-startup-analyzer/lib/extension/ 14 | 15 | 16 | spring-profiler-starter 17 | pom 18 | 19 | 20 | 21 | io.github.linyimin0812 22 | spring-profiler-api 23 | provided 24 | 25 | 26 | 27 | io.github.linyimin0812 28 | spring-profiler-common 29 | provided 30 | 31 | 32 | 33 | org.picocontainer 34 | picocontainer 35 | provided 36 | 37 | 38 | 39 | org.kohsuke.metainf-services 40 | metainf-services 41 | 1.9 42 | compile 43 | 44 | 45 | 46 | 47 | ${project.artifactId} 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-assembly-plugin 52 | 53 | 54 | jar-with-dependencies 55 | 56 | single 57 | 58 | package 59 | 60 | ${project.artifactId} 61 | false 62 | ${extension.output.directory} 63 | 64 | jar-with-dependencies 65 | 66 | 67 | 68 | ${project.name} 69 | ${project.version} 70 | ${project.name} 71 | ${project.version} 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/cli/command/ClearScreen.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.cli.command; 2 | 3 | import org.jline.utils.InfoCmp; 4 | import picocli.CommandLine; 5 | 6 | import java.util.concurrent.Callable; 7 | 8 | /** 9 | * @author linyimin 10 | **/ 11 | @CommandLine.Command( 12 | name = "clear", 13 | mixinStandardHelpOptions = true, 14 | description = "Clears the screen", version = "1.0" 15 | ) 16 | public class ClearScreen implements Callable { 17 | 18 | @CommandLine.ParentCommand 19 | CliCommands parent; 20 | 21 | ClearScreen() {} 22 | 23 | public Void call() { 24 | if (parent.getTerminal() != null) { parent.getTerminal().puts(InfoCmp.Capability.clear_screen); } 25 | return null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/cli/command/CliCommands.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.cli.command; 2 | 3 | import io.github.linyimin0812.spring.startup.recompile.ModifiedFileProcessor; 4 | import io.github.linyimin0812.spring.startup.recompile.ModifiedFileWatcher; 5 | import org.jline.reader.LineReader; 6 | import org.jline.terminal.Terminal; 7 | import picocli.CommandLine; 8 | 9 | import java.io.IOException; 10 | import java.io.PrintWriter; 11 | 12 | /** 13 | * @author linyimin 14 | **/ 15 | @CommandLine.Command( 16 | name = "", 17 | subcommands = { 18 | ClearScreen.class, 19 | Reload.class, 20 | Config.class 21 | } 22 | ) 23 | public class CliCommands implements Runnable { 24 | 25 | private LineReader reader; 26 | private PrintWriter out; 27 | 28 | private final Configuration configuration = new Configuration(); 29 | 30 | private final ModifiedFileWatcher watcher; 31 | private final ModifiedFileProcessor processor; 32 | 33 | public CliCommands() throws IOException { 34 | this.processor = new ModifiedFileProcessor(); 35 | this.watcher = new ModifiedFileWatcher(this.processor); 36 | } 37 | 38 | public void setReader(LineReader reader){ 39 | out = reader.getTerminal().writer(); 40 | this.reader = reader; 41 | } 42 | 43 | public void run() { 44 | out.println(new CommandLine(this).getUsageMessage()); 45 | } 46 | 47 | public Terminal getTerminal() { 48 | return this.reader.getTerminal(); 49 | } 50 | 51 | public LineReader getReader() { 52 | return this.reader; 53 | } 54 | 55 | public ModifiedFileProcessor getProcessor() { 56 | return processor; 57 | } 58 | 59 | public String getBranch() { 60 | return configuration.getBranch(); 61 | } 62 | 63 | public void setBranch(String branch) { 64 | this.configuration.setBranch(branch); 65 | } 66 | 67 | public String getHost() { 68 | return configuration.getHost(); 69 | } 70 | 71 | public void setHost(String host) { 72 | this.configuration.setHost(host); 73 | } 74 | 75 | public Integer getPort() { 76 | return configuration.getPort(); 77 | } 78 | 79 | public void setPort(Integer port) { 80 | this.configuration.setPort(port); 81 | } 82 | 83 | public void close() throws IOException { 84 | this.out.close(); 85 | this.watcher.close(); 86 | this.reader.getTerminal().close(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/cli/command/Config.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.cli.command; 2 | 3 | import io.github.linyimin0812.spring.startup.utils.GitUtil; 4 | import io.github.linyimin0812.spring.startup.utils.StringUtil; 5 | import org.jline.reader.MaskingCallback; 6 | import picocli.CommandLine; 7 | 8 | import static io.github.linyimin0812.spring.startup.constant.Constants.OUT; 9 | 10 | /** 11 | * @author linyimin 12 | **/ 13 | @CommandLine.Command( 14 | name = "config", 15 | description = "configuration setting, use 'config -h' for more information", 16 | version = "1.0", 17 | mixinStandardHelpOptions = true 18 | ) 19 | public class Config implements Runnable { 20 | 21 | @CommandLine.ParentCommand 22 | CliCommands parent; 23 | 24 | @CommandLine.Command( 25 | version = "1.0", 26 | description = "config deployment branch, remote debug jvm host and port", 27 | mixinStandardHelpOptions = true 28 | ) 29 | public void set() { 30 | 31 | setBranch(); 32 | setHost(); 33 | setPort(); 34 | 35 | OUT.print("[INFO] configuration setting success. configuration - "); 36 | OUT.printf("branch: %s, host: %s, port: %s\n", parent.getBranch(), parent.getHost(), parent.getPort()); 37 | 38 | } 39 | 40 | @CommandLine.Command( 41 | version = "1.0", 42 | description = "view config information", 43 | mixinStandardHelpOptions = true 44 | ) 45 | public void view() { 46 | OUT.printf("[INFO] configuration - branch: %s, host: %s, port: %s", parent.getBranch(), parent.getHost(), parent.getPort()); 47 | } 48 | 49 | @Override 50 | public void run() { 51 | OUT.println("configuration setting, use 'config -h' for more information"); 52 | } 53 | 54 | private void setBranch() { 55 | 56 | String line = parent.getReader().readLine(currentBranchPrompt(), null, (MaskingCallback) null, null); 57 | if (StringUtil.isNotEmpty(line)) { 58 | this.parent.setBranch(line); 59 | } 60 | } 61 | 62 | private void setHost() { 63 | String line = parent.getReader().readLine("jvm host (12.0.0.1) : ", null, (MaskingCallback) null, null); 64 | if (StringUtil.isNotEmpty(line)) { 65 | this.parent.setHost(line); 66 | } 67 | } 68 | 69 | private void setPort() { 70 | String line = parent.getReader().readLine("jvm port (5005) : ", null, (MaskingCallback) null, null); 71 | if (StringUtil.isNotEmpty(line)) { 72 | this.parent.setPort(Integer.parseInt(line)); 73 | } 74 | } 75 | 76 | private String currentBranchPrompt() { 77 | 78 | String prompt = "deployment branch"; 79 | 80 | String currentBranch = GitUtil.currentBranch(); 81 | if (StringUtil.isNotEmpty(currentBranch)) { 82 | return prompt + " (" + currentBranch + ") : "; 83 | } 84 | 85 | return prompt + " : "; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/cli/command/Configuration.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.cli.command; 2 | 3 | import io.github.linyimin0812.spring.startup.utils.GitUtil; 4 | 5 | /** 6 | * @author linyimin 7 | **/ 8 | public class Configuration { 9 | private String branch = GitUtil.currentBranch(); 10 | 11 | private String host = "127.0.0.1"; 12 | 13 | private Integer port = 5005; 14 | 15 | public String getBranch() { 16 | return branch; 17 | } 18 | 19 | public void setBranch(String branch) { 20 | this.branch = branch; 21 | } 22 | 23 | public String getHost() { 24 | return host; 25 | } 26 | 27 | public void setHost(String host) { 28 | this.host = host; 29 | } 30 | 31 | public Integer getPort() { 32 | return port; 33 | } 34 | 35 | public void setPort(Integer port) { 36 | this.port = port; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/constant/Constants.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.constant; 2 | 3 | import java.io.PrintStream; 4 | 5 | /** 6 | * @author linyimin 7 | **/ 8 | public class Constants { 9 | 10 | public static final String SOURCE_DIR = "src/main/java"; 11 | public static final String MAVEN_COMPILE_DIR = "target/classes"; 12 | 13 | public static final String GRADLE_COMPILE_DIR = "build/classes"; 14 | 15 | public static final String SOURCE_PREFIX = ".java"; 16 | public static final String COMPILE_PREFIX = ".class"; 17 | 18 | public static final String DOT = "."; 19 | public static final String DOLLAR = "$"; 20 | public static final String EMPTY_STRING = ""; 21 | public static final String SPACE = " "; 22 | 23 | public static final String USER_DIR = "user.dir"; 24 | public static final String POM_XML = "pom.xml"; 25 | public static final String UNIX_MVNW = "mvnw"; 26 | public static final String WIN_NVMW = "mvnw.cmd"; 27 | 28 | public static final String BUILD_GRADLE = "build.gradle"; 29 | public static final String UNIX_GRADLEW = "gradlew"; 30 | public static final String WIN_GRADLEW = "gradlew.bat"; 31 | 32 | public static final String PATH = "PATH"; 33 | 34 | public static final String CLI_NAME = "spring-startup-cli"; 35 | 36 | public static final PrintStream OUT = System.out; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/JDWPClient.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.jdwp; 2 | 3 | 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.OutputStream; 7 | import java.net.Socket; 8 | import java.nio.ByteBuffer; 9 | import java.nio.charset.StandardCharsets; 10 | 11 | import static io.github.linyimin0812.spring.startup.constant.Constants.OUT; 12 | 13 | public class JDWPClient { 14 | 15 | private final Socket socket; 16 | private final String host; 17 | private final Integer port; 18 | 19 | public final static int LENGTH_SIZE = 4; 20 | 21 | public JDWPClient(String host, int port) throws IOException { 22 | this.host = host; 23 | this.port = port; 24 | this.socket = new Socket(host, port); 25 | } 26 | 27 | public boolean start() throws IOException { 28 | 29 | // 发送 JDWP 握手请求 30 | String handshakeCommand = "JDWP-Handshake"; 31 | 32 | InputStream in = socket.getInputStream(); 33 | OutputStream out = socket.getOutputStream(); 34 | 35 | out.write(handshakeCommand.getBytes(StandardCharsets.UTF_8)); 36 | byte[] handshakeResponseBytes = new byte[14]; 37 | int bytesRead = in.read(handshakeResponseBytes); 38 | 39 | boolean isConnected = bytesRead == handshakeResponseBytes.length && handshakeCommand.equals(new String(handshakeResponseBytes, StandardCharsets.UTF_8)); 40 | 41 | if (isConnected) { 42 | OUT.printf("[INFO] Connected to the target VM, address: '%s:%s', transport: 'socket'\n", host, port); 43 | } 44 | 45 | return isConnected; 46 | } 47 | 48 | public void close() throws IOException { 49 | if (!this.socket.isClosed()) { 50 | this.socket.close(); 51 | } 52 | } 53 | 54 | public synchronized ByteBuffer execute(byte[] command) throws IOException { 55 | 56 | OutputStream out = this.socket.getOutputStream(); 57 | InputStream in = this.socket.getInputStream(); 58 | 59 | out.write(command); 60 | 61 | byte[] replyBytes = new byte[LENGTH_SIZE]; 62 | int bytesRead = 0; 63 | 64 | while (bytesRead!= LENGTH_SIZE) { 65 | bytesRead += in.read(replyBytes, bytesRead, LENGTH_SIZE - bytesRead); 66 | } 67 | 68 | int length = ByteBuffer.wrap(replyBytes).getInt(); 69 | 70 | ByteBuffer buffer = ByteBuffer.allocate(length); 71 | 72 | buffer.putInt(length); 73 | 74 | while (bytesRead != length) { 75 | replyBytes = new byte[4096]; 76 | int currentBytesRead = in.read(replyBytes); 77 | buffer.put(replyBytes, 0, currentBytesRead); 78 | bytesRead += currentBytesRead; 79 | } 80 | 81 | buffer.flip(); 82 | 83 | return buffer; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/AllClassesCommand.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.jdwp.command; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public class AllClassesCommand extends CommandPackage { 7 | 8 | 9 | public AllClassesCommand() { 10 | super(CommandPackage.nextId(), (byte) 0, (byte) 1, (byte) 3); 11 | } 12 | 13 | @Override 14 | public byte[] dataBytes() { 15 | return new byte[0]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/AllClassesReplyPackage.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.jdwp.command; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * @author linyimin 10 | **/ 11 | public class AllClassesReplyPackage extends ReplyPackage> { 12 | 13 | public AllClassesReplyPackage(ByteBuffer buffer) { 14 | super(buffer); 15 | } 16 | 17 | @Override 18 | List parseData(ByteBuffer buffer) { 19 | // Number of reference types that follow 20 | int classes = buffer.getInt(); 21 | 22 | List list = new ArrayList<>(classes); 23 | 24 | for (int i = 0; i < classes; i++) { 25 | byte refTypeTag = buffer.get(); 26 | long referenceTypeId = buffer.getLong(); 27 | 28 | int stringLength = buffer.getInt(); 29 | byte[] signatureBytes = new byte[stringLength]; 30 | buffer.get(signatureBytes, 0, stringLength); 31 | String signature = new String(signatureBytes, StandardCharsets.UTF_8); 32 | 33 | int status = buffer.getInt(); 34 | 35 | list.add(new Data(refTypeTag, referenceTypeId, signature, status)); 36 | } 37 | 38 | return list; 39 | } 40 | 41 | 42 | public static class Data { 43 | 44 | private final byte refTypeTag; 45 | private final long referenceTypeId; 46 | private final String signature; 47 | private final int status; 48 | 49 | public Data(byte refTypeTag, long referenceTypeId, String signature, int status) { 50 | this.refTypeTag = refTypeTag; 51 | this.referenceTypeId = referenceTypeId; 52 | this.signature = signature; 53 | this.status = status; 54 | } 55 | 56 | public byte getRefTypeTag() { 57 | return refTypeTag; 58 | } 59 | 60 | public long getReferenceTypeId() { 61 | return referenceTypeId; 62 | } 63 | 64 | public String getSignature() { 65 | return signature; 66 | } 67 | 68 | public int getStatus() { 69 | return status; 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/CommandCache.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.jdwp.command; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | import java.util.concurrent.ConcurrentMap; 5 | 6 | /** 7 | * @author linyimin 8 | **/ 9 | public class CommandCache { 10 | 11 | private static final ConcurrentMap> COMMAND_CACHE = new ConcurrentHashMap<>(); 12 | 13 | public static synchronized void cache(Integer id, CommandPackage command) { 14 | COMMAND_CACHE.put(id, command); 15 | } 16 | 17 | public static synchronized CommandPackage poll(Integer id) { 18 | return COMMAND_CACHE.remove(id); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/CommandPackage.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.jdwp.command; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | /** 7 | * @author linyimin 8 | **/ 9 | public abstract class CommandPackage extends Package { 10 | 11 | private static final AtomicInteger ID_GENERATOR = new AtomicInteger(0); 12 | 13 | private final byte commandSet; 14 | private final byte command; 15 | 16 | public CommandPackage(int id, byte flag, byte commandSet, byte command) { 17 | this(11, id, flag, commandSet, command, null); 18 | } 19 | 20 | public CommandPackage(int length, int id, byte flag, byte commandSet, byte command, T data) { 21 | super(length, id, flag, data); 22 | this.commandSet = commandSet; 23 | this.command = command; 24 | 25 | CommandCache.cache(id, this); 26 | 27 | } 28 | 29 | public byte[] toBytes() { 30 | 31 | ByteBuffer buffer = ByteBuffer.allocate(getLength()); 32 | buffer.putInt(getLength()); 33 | buffer.putInt(getId()); 34 | buffer.put(getFlag()); 35 | buffer.put(commandSet); 36 | buffer.put(command); 37 | buffer.put(dataBytes()); 38 | return buffer.array(); 39 | } 40 | 41 | abstract byte[] dataBytes(); 42 | 43 | public static int nextId() { 44 | return ID_GENERATOR.incrementAndGet(); 45 | } 46 | 47 | public byte getCommandSet() { 48 | return commandSet; 49 | } 50 | 51 | public byte getCommand() { 52 | return command; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/Package.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.jdwp.command; 2 | 3 | /** 4 | * @author linyimin 5 | **/ 6 | public abstract class Package { 7 | 8 | private final int length; 9 | private final int id; 10 | private final byte flag; 11 | 12 | private T data; 13 | 14 | public Package(int length, int id, byte flag) { 15 | this(length, id, flag, null); 16 | } 17 | 18 | public Package(int length, int id, byte flag, T data) { 19 | this.length = length; 20 | this.id = id; 21 | this.flag = flag; 22 | this.data = data; 23 | } 24 | 25 | public int getLength() { 26 | return length; 27 | } 28 | 29 | public int getId() { 30 | return id; 31 | } 32 | 33 | public byte getFlag() { 34 | return flag; 35 | } 36 | 37 | public T getData() { 38 | return data; 39 | } 40 | 41 | public void setData(T data) { 42 | this.data = data; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/RedefineClassesCommand.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.jdwp.command; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.List; 5 | 6 | /** 7 | * @author linyimin 8 | **/ 9 | public class RedefineClassesCommand extends CommandPackage { 10 | 11 | public RedefineClassesCommand(Data data) { 12 | super(11 + data.length(), CommandPackage.nextId(), (byte) 0, (byte) 1, (byte) 18, data); 13 | } 14 | 15 | @Override 16 | byte[] dataBytes() { 17 | return getData().toBytes(); 18 | } 19 | 20 | public static class Data { 21 | // Number of reference types that follow 22 | private final int classes; 23 | 24 | private final List redefineClasses; 25 | 26 | public Data(List redefineClasses) { 27 | this.classes = redefineClasses.size(); 28 | this.redefineClasses = redefineClasses; 29 | } 30 | 31 | public int length() { 32 | return 4 + redefineClasses.stream().mapToInt(RedefineClass::length).sum(); 33 | } 34 | 35 | public byte[] toBytes() { 36 | 37 | ByteBuffer buffer = ByteBuffer.allocate(length()); 38 | 39 | buffer.putInt(classes); 40 | 41 | for (RedefineClass redefineClass : redefineClasses) { 42 | buffer.put(redefineClass.toBytes()); 43 | } 44 | 45 | return buffer.array(); 46 | } 47 | 48 | public int getClasses() { 49 | return classes; 50 | } 51 | } 52 | 53 | public static class RedefineClass { 54 | // The reference type 55 | private final long referenceTypeId; 56 | 57 | // Number of bytes defining class (below) 58 | private final int numOfBytes; 59 | 60 | // byte in JVM class file format 61 | private final byte[] classBytes; 62 | 63 | 64 | public RedefineClass(long referenceTypeId, byte[] classBytes) { 65 | this.referenceTypeId = referenceTypeId; 66 | this.numOfBytes = classBytes.length; 67 | this.classBytes = classBytes; 68 | } 69 | 70 | public int length() { 71 | return 8 + 4 + numOfBytes; 72 | } 73 | 74 | public byte[] toBytes() { 75 | 76 | ByteBuffer buffer = ByteBuffer.allocate(length()); 77 | 78 | buffer.putLong(referenceTypeId); 79 | buffer.putInt(numOfBytes); 80 | buffer.put(classBytes); 81 | 82 | return buffer.array(); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/RedefineClassesReplyPackage.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.jdwp.command; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | /** 6 | * @author linyimin 7 | **/ 8 | public class RedefineClassesReplyPackage extends ReplyPackage { 9 | public RedefineClassesReplyPackage(ByteBuffer buffer) { 10 | super(buffer); 11 | } 12 | 13 | @Override 14 | Void parseData(ByteBuffer buffer) { 15 | return null; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/jdwp/command/ReplyPackage.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.jdwp.command; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | import static io.github.linyimin0812.spring.startup.constant.Constants.OUT; 6 | 7 | /** 8 | * @author linyimin 9 | **/ 10 | public abstract class ReplyPackage extends Package { 11 | 12 | private final short errorCode; 13 | 14 | public ReplyPackage(ByteBuffer buffer) { 15 | super(buffer.getInt(), buffer.getInt(), buffer.get()); 16 | this.errorCode = buffer.getShort(); 17 | 18 | if (this.errorCode != 0) { 19 | CommandPackage command = CommandCache.poll(this.getId()); 20 | OUT.printf("commandSet: %s, command: %s, errorCode: %s", command.getCommandSet(), command.getCommand(), this.errorCode); 21 | } 22 | 23 | this.setData(parseData(buffer)); 24 | } 25 | 26 | public boolean isSuccess() { 27 | return errorCode == 0; 28 | } 29 | 30 | public short getErrorCode() { 31 | return errorCode; 32 | } 33 | 34 | abstract T parseData(ByteBuffer buffer); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/recompile/ModifiedFileWatcher.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.recompile; 2 | 3 | import io.github.linyimin0812.spring.startup.constant.Constants; 4 | import io.github.linyimin0812.spring.startup.utils.ModuleUtil; 5 | import io.github.linyimin0812.spring.startup.utils.StringUtil; 6 | import io.methvin.watcher.DirectoryWatcher; 7 | 8 | import java.io.IOException; 9 | import java.nio.file.*; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | /** 14 | * @author linyimin 15 | **/ 16 | public class ModifiedFileWatcher { 17 | 18 | private final DirectoryWatcher watcher; 19 | 20 | public boolean running = false; 21 | 22 | public ModifiedFileWatcher(ModifiedFileProcessor processor) throws IOException { 23 | this(System.getProperty(Constants.USER_DIR), processor); 24 | } 25 | 26 | /** 27 | * Creates a WatchService and registers the given directory 28 | * @param dir watch directory 29 | * @param processor ModifiedFileProcessor 30 | * @throws IOException IO Exception 31 | */ 32 | public ModifiedFileWatcher(String dir, ModifiedFileProcessor processor) throws IOException { 33 | 34 | Path path = Paths.get(dir); 35 | 36 | List moduleHomes = ModuleUtil.getModulePaths(path); 37 | 38 | List moduleSourceDirs = moduleHomes.stream().map(moduleHome -> moduleHome.resolve(Constants.SOURCE_DIR)).filter(Files::exists).collect(Collectors.toList()); 39 | 40 | this.watcher = DirectoryWatcher.builder() 41 | .paths(moduleSourceDirs) 42 | .listener(processor::onEvent) 43 | .build(); 44 | 45 | int longest = moduleHomes.stream().map(Path::toString).map(String::length).max(Integer::compareTo).orElse(0) + 32; 46 | 47 | for (Path moduleHome : moduleHomes) { 48 | System.out.format("[INFO] %s WATCHING\n", StringUtil.rightPad(moduleHome.toString() + Constants.SPACE, longest, ".")); 49 | } 50 | 51 | new Thread(watcher::watch).start(); 52 | 53 | running = true; 54 | } 55 | 56 | public void close() throws IOException { 57 | running = false; 58 | this.watcher.close(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/utils/GitUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.utils; 2 | 3 | import io.github.linyimin0812.spring.startup.constant.Constants; 4 | 5 | /** 6 | * @author linyimin 7 | **/ 8 | public class GitUtil { 9 | public static boolean isGit() { 10 | ShellUtil.Result result = ShellUtil.execute(new String[] { "git", "status" }); 11 | return result.code == 0; 12 | } 13 | 14 | public static String currentBranch() { 15 | ShellUtil.Result result = ShellUtil.execute(new String[] {"git", "branch", "--show-current"}); 16 | if (result.code == 0) { 17 | return result.content; 18 | } 19 | 20 | return Constants.EMPTY_STRING; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/utils/OSUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.utils; 2 | 3 | import java.util.Locale; 4 | 5 | /** 6 | * @author linyimin 7 | **/ 8 | public class OSUtil { 9 | public static boolean isUnix() { 10 | 11 | String os = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); 12 | 13 | return os.startsWith("linux") || os.startsWith("mac") || os.startsWith("darwin"); 14 | } 15 | 16 | public static boolean isWindows() { 17 | String os = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); 18 | return os.startsWith("windows"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/utils/ShellUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.utils; 2 | 3 | import io.github.linyimin0812.spring.startup.constant.Constants; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | 9 | import static io.github.linyimin0812.spring.startup.constant.Constants.OUT; 10 | 11 | /** 12 | * @author linyimin 13 | **/ 14 | public class ShellUtil { 15 | public static Result execute(String[] cmdArray) { 16 | return execute(cmdArray, false); 17 | } 18 | 19 | public static Result execute(String[] cmdArray, boolean print) { 20 | try { 21 | 22 | Process process = Runtime.getRuntime().exec(cmdArray); 23 | 24 | int code = process.waitFor(); 25 | 26 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(code == 0 ? process.getInputStream() : process.getErrorStream()))) { 27 | 28 | StringBuilder sb = new StringBuilder(); 29 | 30 | String line; 31 | while ((line = reader.readLine()) != null) { 32 | sb.append(line).append('\n'); 33 | if (print) { 34 | OUT.println(line); 35 | } 36 | } 37 | 38 | String content = sb.length() > 0 ? sb.substring(0, sb.length() - 1) : Constants.EMPTY_STRING; 39 | 40 | return new Result(code, content); 41 | } 42 | 43 | } catch (IOException | InterruptedException e) { 44 | if (e instanceof InterruptedException) { 45 | Thread.currentThread().interrupt(); 46 | } 47 | return new Result(-1, e.getMessage()); 48 | } 49 | } 50 | 51 | public static class Result { 52 | 53 | public int code; 54 | public String content; 55 | 56 | public Result(int code, String content) { 57 | this.code = code; 58 | this.content = content; 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return "Result{" + 64 | "code=" + code + 65 | ", content='" + content + '\'' + 66 | '}'; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /spring-startup-cli/src/main/java/io/github/linyimin0812/spring/startup/utils/StringUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.utils; 2 | 3 | import io.github.linyimin0812.spring.startup.constant.Constants; 4 | 5 | /** 6 | * @author linyimin 7 | **/ 8 | public class StringUtil { 9 | public static boolean isEmpty(String text) { 10 | return text == null || text.isEmpty(); 11 | } 12 | 13 | public static boolean isNotEmpty(String text) { 14 | return text != null && !text.isEmpty(); 15 | } 16 | 17 | public static String rightPad(final String str, final int size, String padStr) { 18 | if (str == null) { 19 | return null; 20 | } 21 | 22 | padStr = (padStr == null || padStr.isEmpty()) ? Constants.SPACE : padStr; 23 | 24 | final int padLen = padStr.length(); 25 | final int strLen = str.length(); 26 | final int pads = size - strLen; 27 | if (pads <= 0) { 28 | return str; 29 | } 30 | 31 | if (pads == padLen) { 32 | return str.concat(padStr); 33 | } else if (pads < padLen) { 34 | return str.concat(padStr.substring(0, pads)); 35 | } else { 36 | final char[] padding = new char[pads]; 37 | final char[] padChars = padStr.toCharArray(); 38 | for (int i = 0; i < pads; i++) { 39 | padding[i] = padChars[i % padLen]; 40 | } 41 | return str.concat(new String(padding)); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /spring-startup-cli/src/test/java/io/github/linyimin0812/spring/startup/recompile/ModifiedFileWatcherSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.recompile 2 | 3 | import spock.lang.Shared 4 | import spock.lang.Specification 5 | import spock.lang.Stepwise 6 | 7 | import java.lang.reflect.Field 8 | 9 | /** 10 | * @author linyimin 11 | * */ 12 | @Stepwise 13 | class ModifiedFileWatcherSpec extends Specification { 14 | @Shared 15 | ModifiedFileProcessor processor = new ModifiedFileProcessor() 16 | 17 | @Shared 18 | ModifiedFileWatcher watcher = new ModifiedFileWatcher(processor) 19 | 20 | def "test watcher"() { 21 | when: 22 | Field runningField = watcher.getClass().getDeclaredField("running") 23 | runningField.setAccessible(true) 24 | 25 | then: 26 | runningField.getBoolean(watcher) 27 | } 28 | 29 | def "test close"() { 30 | when: 31 | Field runningField = watcher.getClass().getDeclaredField("running") 32 | runningField.setAccessible(true) 33 | watcher.close() 34 | 35 | then: 36 | !runningField.getBoolean(watcher) 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spring-startup-cli/src/test/java/io/github/linyimin0812/spring/startup/utils/GitUtilSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.utils 2 | 3 | import spock.lang.Specification 4 | 5 | /** 6 | * @author linyimin 7 | * */ 8 | class GitUtilSpec extends Specification { 9 | 10 | def "test isGit"() { 11 | when: 12 | def isGit = GitUtil.isGit() 13 | 14 | then: 15 | isGit 16 | } 17 | 18 | def "test currentBranch"() { 19 | when: 20 | def branch = GitUtil.currentBranch() 21 | 22 | then: 23 | branch != null 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /spring-startup-cli/src/test/java/io/github/linyimin0812/spring/startup/utils/ModuleUtilSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.utils 2 | 3 | import io.github.linyimin0812.spring.startup.constant.Constants 4 | import spock.lang.Shared 5 | import spock.lang.Specification 6 | 7 | import java.nio.file.Path 8 | import java.nio.file.Paths 9 | 10 | /** 11 | * @author linyimin 12 | * */ 13 | class ModuleUtilSpec extends Specification { 14 | 15 | 16 | @Shared 17 | final Path home = Paths.get(System.getProperty(Constants.USER_DIR)); 18 | 19 | def "test getModulePaths"() { 20 | when: 21 | List paths = ModuleUtil.getModulePaths() 22 | 23 | then: 24 | paths.size() == 1 25 | paths.get(0).getFileName().toString() == 'spring-startup-cli' 26 | } 27 | 28 | def "test compile"() { 29 | when: 30 | def isCompile = ModuleUtil.compile(home.getParent()) 31 | 32 | then: 33 | isCompile 34 | } 35 | 36 | def "test isMaven"() { 37 | when: 38 | def isMaven = ModuleUtil.isMaven(home) 39 | 40 | then: 41 | isMaven 42 | } 43 | 44 | def "test isGradle"() { 45 | when: 46 | def isGradle = ModuleUtil.isGradle(home) 47 | 48 | 49 | then: 50 | !isGradle 51 | } 52 | 53 | def "test hasMvnW"() { 54 | when: 55 | def isMvnW = ModuleUtil.hasGradleW(home) 56 | 57 | then: 58 | !isMvnW 59 | } 60 | 61 | def "test hasGradleW"() { 62 | when: 63 | def hasGradleW = ModuleUtil.hasGradleW(home) 64 | 65 | then: 66 | !hasGradleW 67 | } 68 | 69 | def "test buildWithMaven"() { 70 | when: 71 | def isBuildWithMaven = ModuleUtil.buildWithMaven(home.getParent()) 72 | 73 | then: 74 | isBuildWithMaven 75 | } 76 | 77 | def "test buildWithGradle"() { 78 | when: 79 | def isBuildWithGradle = ModuleUtil.buildWithGradle(home.getParent()) 80 | 81 | then: 82 | !isBuildWithGradle 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /spring-startup-cli/src/test/java/io/github/linyimin0812/spring/startup/utils/OSUtilSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.utils 2 | 3 | import spock.lang.Specification 4 | 5 | /** 6 | * @author linyimin 7 | * */ 8 | class OSUtilSpec extends Specification { 9 | 10 | def "test isUnix"() { 11 | when: 12 | System.setProperty('os.name', 'linux') 13 | 14 | then: 15 | OSUtil.isUnix() 16 | } 17 | 18 | def "test isWindows"() { 19 | when: 20 | System.setProperty('os.name', 'windows') 21 | 22 | then: 23 | OSUtil.isWindows() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /spring-startup-cli/src/test/java/io/github/linyimin0812/spring/startup/utils/ShellUtilSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.utils 2 | 3 | import spock.lang.Specification 4 | 5 | /** 6 | * @author linyimin 7 | * */ 8 | class ShellUtilSpec extends Specification { 9 | 10 | def "test execute"() { 11 | 12 | when: 13 | def result = null 14 | if (OSUtil.isUnix()) { 15 | result = ShellUtil.execute(new String[] { "ls", "-al" }); 16 | } else if (OSUtil.isWindows()) { 17 | result = ShellUtil.execute(new String[] { "dir" }); 18 | } 19 | 20 | then: 21 | result.code == 0 22 | 23 | } 24 | 25 | def "test execute with print"() { 26 | when: 27 | def result = null 28 | if (OSUtil.isUnix()) { 29 | result = ShellUtil.execute(new String[] { "ls", "-al" }, true); 30 | } else if (OSUtil.isWindows()) { 31 | result = ShellUtil.execute(new String[] { "dir" }, true); 32 | } 33 | 34 | then: 35 | result.code == 0 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /spring-startup-cli/src/test/java/io/github/linyimin0812/spring/startup/utils/StringUtilSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.github.linyimin0812.spring.startup.utils 2 | 3 | import io.github.linyimin0812.spring.startup.constant.Constants 4 | import spock.lang.Specification 5 | 6 | /** 7 | * @author linyimin 8 | * */ 9 | class StringUtilSpec extends Specification { 10 | 11 | def "test isEmpty"() { 12 | when: 13 | def isEmpty = StringUtil.isEmpty(text) 14 | 15 | then: 16 | isEmpty == result 17 | 18 | where: 19 | text || result 20 | '' || true 21 | 'text' || false 22 | 23 | } 24 | 25 | def "test isNotEmpty"() { 26 | when: 27 | def isNotEmpty = StringUtil.isNotEmpty(text) 28 | 29 | then: 30 | isNotEmpty == reuslt 31 | 32 | where: 33 | text || reuslt 34 | '' || false 35 | 'text' || true 36 | } 37 | 38 | def "test rightPad"() { 39 | 40 | given: 41 | String content = "test" 42 | 43 | when: 44 | String rightPad = StringUtil.rightPad(content + Constants.SPACE, 10, ".") 45 | 46 | then: 47 | 'test .....' == rightPad 48 | } 49 | } 50 | --------------------------------------------------------------------------------