abstracta / jmeter-java-dsl

Simple JMeter performance tests API
https://abstracta.github.io/jmeter-java-dsl/
Apache License 2.0
477 stars 59 forks source link

Add support for Spring boot integration #147

Closed joaolrpaulo closed 2 years ago

joaolrpaulo commented 2 years ago

Me and my team are trying to integrate this into a project that includes Spring Boot, and whenever we run our FatJar we encounter multiple problems in the app bootprocess.

This is the current problems that we are facing:

java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:774) ~[spring-boot-2.7.4.jar!/:2.7.4]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:755) ~[spring-boot-2.7.4.jar!/:2.7.4]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.7.4.jar!/:2.7.4]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:577) ~[na:na]
    at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49) ~[loadgenerator.jar:na]
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:108) ~[loadgenerator.jar:na]
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:58) ~[loadgenerator.jar:na]
    at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:65) ~[loadgenerator.jar:na]
Caused by: java.nio.file.NoSuchFileException: /bin
    at jdk.zipfs/jdk.nio.zipfs.ZipPath.readAttributes(ZipPath.java:769) ~[jdk.zipfs:na]
    at jdk.zipfs/jdk.nio.zipfs.ZipPath.readAttributes(ZipPath.java:777) ~[jdk.zipfs:na]
    at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.readAttributes(ZipFileSystemProvider.java:276) ~[jdk.zipfs:na]
    at java.base/java.nio.file.Files.readAttributes(Files.java:1851) ~[na:na]
    at java.base/java.nio.file.FileTreeWalker.getAttributes(FileTreeWalker.java:220) ~[na:na]
    at java.base/java.nio.file.FileTreeWalker.visit(FileTreeWalker.java:277) ~[na:na]
    at java.base/java.nio.file.FileTreeWalker.walk(FileTreeWalker.java:323) ~[na:na]
    at java.base/java.nio.file.FileTreeIterator.<init>(FileTreeIterator.java:71) ~[na:na]
    at java.base/java.nio.file.Files.walk(Files.java:3919) ~[na:na]
    at java.base/java.nio.file.Files.walk(Files.java:3974) ~[na:na]
    at us.abstracta.jmeter.javadsl.core.engines.JmeterEnvironment.installConfig(JmeterEnvironment.java:98) ~[jmeter-java-dsl-1.0.jar!/:1.0]
    at us.abstracta.jmeter.javadsl.core.engines.JmeterEnvironment.<init>(JmeterEnvironment.java:41) ~[jmeter-java-dsl-1.0.jar!/:1.0]
    at us.abstracta.jmeter.javadsl.core.engines.EmbeddedJmeterEngine.run(EmbeddedJmeterEngine.java:93) ~[jmeter-java-dsl-1.0.jar!/:1.0]
    at us.abstracta.jmeter.javadsl.core.DslTestPlan.run(DslTestPlan.java:121) ~[jmeter-java-dsl-1.0.jar!/:1.0]

This first stacktrace we solved by extracting the /bin/ folder in the org.apache.jmeter:ApacheJMeter_config:5.5 package and manually adding it to our resources folder.

However the second one seems to imply that there are code changes that need to be applied in order to properly get around it.

EDIT: I was able to gather the URL that seems to not be hierarchical, jar:file:/Users/myuser/my_fancy.jar!/BOOT-INF/lib/ApacheJMeter_components-5.5.jar!/

java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:774) ~[spring-boot-2.7.4.jar!/:2.7.4]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:755) ~[spring-boot-2.7.4.jar!/:2.7.4]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.7.4.jar!/:2.7.4]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:577) ~[na:na]
    at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49) ~[loadgenerator.jar:na]
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:108) ~[loadgenerator.jar:na]
    at org.springframework.boot.loader.Launcher.launch(Launcher.java:58) ~[loadgenerator.jar:na]
    at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:65) ~[loadgenerator.jar:na]
Caused by: java.lang.IllegalArgumentException: URI is not hierarchical
    at java.base/java.io.File.<init>(File.java:420) ~[na:na]
    at us.abstracta.jmeter.javadsl.core.engines.JmeterEnvironment.getClassJarPath(JmeterEnvironment.java:85) ~[jmeter-java-dsl-1.0.jar!/:1.0]
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
    at java.base/java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1707) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682) ~[na:na]
    at us.abstracta.jmeter.javadsl.core.engines.JmeterEnvironment.updateSearchPath(JmeterEnvironment.java:69) ~[jmeter-java-dsl-1.0.jar!/:1.0]
    at us.abstracta.jmeter.javadsl.core.engines.EmbeddedJmeterEngine.runInEnv(EmbeddedJmeterEngine.java:107) ~[jmeter-java-dsl-1.0.jar!/:1.0]
    at us.abstracta.jmeter.javadsl.core.engines.EmbeddedJmeterEngine.run(EmbeddedJmeterEngine.java:93) ~[jmeter-java-dsl-1.0.jar!/:1.0]
    at us.abstracta.jmeter.javadsl.core.DslTestPlan.run(DslTestPlan.java:121) ~[jmeter-java-dsl-1.0.jar!/:1.0]

Current code sample being used:

testPlan(
    rpsThreadGroup()
        .maxThreads(100)
        .rampToAndHold(
            5,
            Duration.ofSeconds(60),
            Duration.ofSeconds(10))
        .children(sampler)
).run()

NOTE: sampler is a simple HTTPSampler to GET results from an endpoint.

rabelenda commented 2 years ago

Hello, thank you for asking about this matter and providing the info!

That logic is trying to solve jmeter search path which is used by jmeter to then properly load classes while executing the testplan.

I have generated uber jars with assembly plugin as described here under uber jar section.

As a side note: when I tried to use shade plugin, faced some limitations.

Spring boot uses a different mechanism to build jars, and in fact, as much as I remember from my springboot experience, uses a custom ClassLoader that allows it to solve classes in such jars (jars containing jars inside). This JMeter DSL logic, and JMeter itself, does not have support for such logic, and we would need to run experimentation to properly provide a solution, which might take a non trivial effort if we need to implement a custom classloader or something like that.

Now some questions πŸ™‚ :

Sounds like a really interesting problem.

Another workaround (instead of using maven assembly plugin) you may use is excluding such transitive dependencies from jmeter-java-dsl, copy them to a specific folder and add such folder to the classpath. That way the jar would not be inside another jar which is probably the reason for non hierarchical URI exception.

Hope to hear back soon πŸ™‚ .

joaolrpaulo commented 2 years ago

Hello πŸ‘‹πŸΌ In first place thanks for the quick response πŸ₯‡

Answering to your questions:

  1. Consistency across the board with all our stack
  2. We have a load tests pipeline (currently via jmeter + jmx files, + some scripts in lua/rust) that we run from time to time, and we are trying to deprecate them to a more consistent way of doing stuff. So we do not have the need of having expertise along with multiple tech stacks.
  3. We are using gradle atm, but I can see if there are any other alternatives for this.
  4. Going to a bit more detail on what was said in 2, we want a JAR that can run a spring boot application & that submits some kind of load in our applications via HTTP, since we were using JMeter before (and company-wide), we saw no problem on continuing to that, but exploring a DSL like way (ence the reason why we are using this project πŸ˜„). NOTE: Currently it isn't feasible to use Junit + normal CI Pipeline for this IIRC. (As you suggested in the linked discussion) Anyway I might give it a try.
  5. Sure thing, will try to arranje that as soon as possible.

About the workaround, that might actually be feasible, I will give it a try whenever possible.

rabelenda commented 2 years ago

Great, looking forward to see how this can be solved :).

joaolrpaulo commented 2 years ago

So, after a while without being able to follow-up on this, I was able to properly setup a sample repo that demonstrates the problem.

You can run the application in IJ IDE, via gradle bootRun task, however if you compile using bootJar and run the standalone JAR it fails with the error above.

rabelenda commented 2 years ago

Hello, thank you very much for the detailed example!

Just adding this to the build.gradle.kts (and the proper import) I got it working:

tasks.named<BootJar>("bootJar") {
    requiresUnpack("**/ApacheJMeter_*.jar", "**/jmeter-plugins-*.jar")
}

Here is spring-boot doc about it.

Let me know if that solves your issues as well.

Side notes:

rabelenda commented 2 years ago

Closing this as the above solution seems quite simple. If you see any issue with it please let us know.

joaolrpaulo commented 2 years ago

Hey πŸ‘‹πŸΌ

I would swear I have tested this before, but I think I might have forgotten to unpack with *, and only tried a couple of the jars 🀦🏼

Just tested your approach, and seems to be working, going to close the issue.

Much thanks for the help here πŸ™πŸΌ

EDIT: Didn't have refreshed the page, as we were testing this :D