From 13894b6ab1055d7654165f3a3461771056e50cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Corr=C3=A9?= Date: Thu, 3 Oct 2024 12:00:07 +0200 Subject: [PATCH] feat: replace current JS hack with an Java agent --- build-jvm.sh | 2 +- js/cli/src/commands/index.ts | 4 +- js/cli/src/commands/run.ts | 16 ++-- js/cli/src/commands/runOnly.ts | 56 ------------- js/cli/src/dependencies/coursier.ts | 12 ++- js/cli/src/enterprise.ts | 4 +- js/cli/src/run.ts | 5 +- .../io/gatling/js/JavaScriptLanguageHack.java | 78 +++++++++++++++++++ jvm/build.sbt | 30 ++++++- 9 files changed, 135 insertions(+), 72 deletions(-) delete mode 100644 js/cli/src/commands/runOnly.ts create mode 100644 jvm/agent/src/main/java/io/gatling/js/JavaScriptLanguageHack.java diff --git a/build-jvm.sh b/build-jvm.sh index 1ee0dd6..afcb349 100755 --- a/build-jvm.sh +++ b/build-jvm.sh @@ -6,4 +6,4 @@ root_dir="$(dirname "$(realpath -- "$0")")" # Publish JVM adapter cd "$root_dir/jvm" -sbt gatling-jvm-to-js-adapter/publishLocal +sbt gatling-js-agent/publishLocal gatling-jvm-to-js-adapter/publishLocal diff --git a/js/cli/src/commands/index.ts b/js/cli/src/commands/index.ts index 3188e93..dbc8043 100644 --- a/js/cli/src/commands/index.ts +++ b/js/cli/src/commands/index.ts @@ -2,7 +2,6 @@ import { Command } from "commander"; import registerInstallCommand from "./install"; import registerBuildCommand from "./build"; -import registerRunOnlyCommand from "./runOnly"; import registerRunCommand from "./run"; import registerRecorderCommand from "./recorder"; import registerEnterprisePackageCommand from "./enterprisePackage"; @@ -12,12 +11,11 @@ import { versions } from "../dependencies"; export const program: Command = new Command() .name("gatling-js-cli") - .version(versions.gatling.jsAdapter) + .version(versions.gatling.js) .description("The Gatling Javascript run & packaging tool"); registerInstallCommand(program); registerBuildCommand(program); -registerRunOnlyCommand(program); registerRunCommand(program); registerRecorderCommand(program); registerEnterprisePackageCommand(program); diff --git a/js/cli/src/commands/run.ts b/js/cli/src/commands/run.ts index 00851cf..a8cce53 100644 --- a/js/cli/src/commands/run.ts +++ b/js/cli/src/commands/run.ts @@ -26,6 +26,7 @@ import { installGatlingJs } from "../dependencies"; import { logger } from "../log"; import { bundle } from "../bundle"; import { runSimulation } from "../run"; +import { resolveGatlingJsAgent } from "../dependencies/coursier"; export default (program: Command): void => { program @@ -57,16 +58,19 @@ export default (program: Command): void => { const typescript = typescriptOptionValueWithDefaults(options, simulations); const simulation = simulationOptionValueWithDefaults(options, simulations, !nonInteractive); - const { graalvmHome, coursierBinary, jvmClasspath } = await installGatlingJs({ gatlingHome }); - logger.debug(`graalvmHome=${graalvmHome}`); - logger.debug(`coursierBinary=${coursierBinary}`); - logger.debug(`jvmClasspath=${jvmClasspath}`); + const resolvedDependencies = await installGatlingJs({ gatlingHome }); + logger.debug(`graalvmHome=${resolvedDependencies.graalvmHome}`); + logger.debug(`coursierBinary=${resolvedDependencies.coursierBinary}`); + logger.debug(`jvmClasspath=${resolvedDependencies.jvmClasspath}`); + const gatlingJsAgent = await resolveGatlingJsAgent(resolvedDependencies); + logger.debug(`gatlingJsAgent=${gatlingJsAgent}`); await bundle({ sourcesFolder, bundleFile, typescript, simulations }); await runSimulation({ - graalvmHome, - jvmClasspath, + graalvmHome: resolvedDependencies.graalvmHome, + jvmClasspath: resolvedDependencies.jvmClasspath, + jsAgent: gatlingJsAgent, simulation, bundleFile, resourcesFolder, diff --git a/js/cli/src/commands/runOnly.ts b/js/cli/src/commands/runOnly.ts deleted file mode 100644 index 944f69e..0000000 --- a/js/cli/src/commands/runOnly.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Command } from "commander"; - -import { - bundleFileOption, - bundleFileOptionValue, - graalvmHomeMandatoryOption, - graalvmHomeMandatoryOptionValue, - jvmClasspathMandatoryOption, - jvmClasspathMandatoryOptionValue, - memoryOption, - memoryOptionValue, - parseRunParametersArgument, - resourcesFolderOption, - resourcesFolderOptionValue, - resultsFolderOption, - resultsFolderOptionValue, - runParametersArgument, - simulationMandatoryOption, - simulationMandatoryOptionValue -} from "./options"; -import { runSimulation } from "../run"; - -export default (program: Command): void => { - program - .command("run-only") - .description("Run a Gatling simulation from an already built bundle") - .addOption(graalvmHomeMandatoryOption) - .addOption(jvmClasspathMandatoryOption) - .addOption(simulationMandatoryOption) - .addOption(bundleFileOption) - .addOption(resourcesFolderOption) - .addOption(resultsFolderOption) - .addOption(memoryOption) - .addArgument(runParametersArgument) - .action(async (args: string[], options) => { - const graalvmHome: string = graalvmHomeMandatoryOptionValue(options); - const jvmClasspath: string = jvmClasspathMandatoryOptionValue(options); - const simulation: string = simulationMandatoryOptionValue(options); - const bundleFile = bundleFileOptionValue(options); - const resourcesFolder: string = resourcesFolderOptionValue(options); - const resultsFolder: string = resultsFolderOptionValue(options); - const memory: number | undefined = memoryOptionValue(options); - const runParameters = parseRunParametersArgument(args); - - await runSimulation({ - graalvmHome, - jvmClasspath, - simulation: simulation, - bundleFile, - resourcesFolder, - resultsFolder, - memory, - runParameters - }); - }); -}; diff --git a/js/cli/src/dependencies/coursier.ts b/js/cli/src/dependencies/coursier.ts index 93a45b4..18c58d0 100644 --- a/js/cli/src/dependencies/coursier.ts +++ b/js/cli/src/dependencies/coursier.ts @@ -7,6 +7,7 @@ import { versions } from "./versions"; import { promisify } from "util"; import { exec } from "child_process"; import { osType } from "./os"; +import { ResolvedDependencies } from "./index"; export const installCoursier = async (gatlingHomeDir: string, downloadDir: string): Promise => { const coursierRootPath = `${gatlingHomeDir}/coursier/${versions.coursier}`; @@ -45,9 +46,18 @@ export const installCoursier = async (gatlingHomeDir: string, downloadDir: strin return coursierPath; }; +export const resolveGatlingJsAgent = async (dependencies: ResolvedDependencies): Promise => { + const gatlingJsAgentDep = `"io.gatling:gatling-js-agent:${versions.gatling.js}"`; + const command = `"${dependencies.coursierBinary}" fetch ${gatlingJsAgentDep}`; + + // TODO could add a timeout + const { stdout } = await execAsync(command, { env: { ...process.env, JAVA_HOME: dependencies.graalvmHome } }); + return stdout.split(/[\r\n]+/g)[0]; +}; + export const resolveGatlingJsDependencies = async (coursierPath: string, javaHome: string): Promise => { const gatlingDep = `"io.gatling.highcharts:gatling-charts-highcharts:${versions.gatling.core}"`; - const gatlingAdapterDep = `"io.gatling:gatling-jvm-to-js-adapter:${versions.gatling.jsAdapter}"`; + const gatlingAdapterDep = `"io.gatling:gatling-jvm-to-js-adapter:${versions.gatling.js}"`; const gatlingEnterprisePluginCommonsDep = `"io.gatling:gatling-enterprise-plugin-commons:${versions.gatling.enterprisePluginCommons}"`; const graalvmJsDep = `"org.graalvm.polyglot:js-community:${versions.graalvm.js}"`; diff --git a/js/cli/src/enterprise.ts b/js/cli/src/enterprise.ts index e2d9c65..3b159ab 100644 --- a/js/cli/src/enterprise.ts +++ b/js/cli/src/enterprise.ts @@ -52,7 +52,7 @@ const generateManifest = (simulationNames: string[]) => { "Gatling-Context: js", `Gatling-Version: ${versions.gatling.core}`, "Gatling-Packager: js-cli", - `Gatling-Packager-Version: ${versions.gatling.jsAdapter}`, + `Gatling-Packager-Version: ${versions.gatling.js}`, `Gatling-Simulations: ${simulationNames.join(",")}`, `Java-Version: ${versions.java.compilerRelease}` ]; @@ -147,7 +147,7 @@ const javaArgsFromPluginOptions = (options: EnterprisePluginOptions) => { javaArgs.push(`-Dgatling.enterprise.controlPlaneUrl=${options.controlPlaneUrl}`); } javaArgs.push("-Dgatling.enterprise.buildTool=js-cli"); - javaArgs.push(`-Dgatling.enterprise.pluginVersion=${versions.gatling.jsAdapter}`); + javaArgs.push(`-Dgatling.enterprise.pluginVersion=${versions.gatling.js}`); if (options.nonInteractive) { javaArgs.push(`-Dgatling.enterprise.batchMode=true`); diff --git a/js/cli/src/run.ts b/js/cli/src/run.ts index 119ac7a..6539c08 100644 --- a/js/cli/src/run.ts +++ b/js/cli/src/run.ts @@ -3,6 +3,7 @@ import { versions } from "./dependencies"; import { RunJavaProcessOptions, runJavaProcess, runNodeProcess } from "./java"; export interface RunSimulationOptions extends RunJavaProcessOptions { + jsAgent: string; simulation: string; bundleFile: string; resourcesFolder: string; @@ -29,11 +30,13 @@ export const runSimulation = async (options: RunSimulationOptions): Promise `--vm.D${key}=${value}`), + `--vm.javaagent:${options.jsAgent}`, `--vm.Dgatling.js.bundle.filePath=${options.bundleFile}`, `--vm.Dgatling.js.simulation=${options.simulation}`, ...jitTuningArgs, ...memoryArgs ]; + logger.debug(`javaArgs=${javaArgs}`); const simulationArgs = [ "--results-folder", options.resultsFolder, @@ -42,7 +45,7 @@ export const runSimulation = async (options: RunSimulationOptions): Promise classBeingRedefined, + ProtectionDomain protectionDomain, + byte[] classfileBuffer) + throws IllegalClassFormatException { + if (!className.equals("com/oracle/truffle/js/lang/JavaScriptLanguage")) { + return null; + } + ClassReader reader = new ClassReader(classfileBuffer); + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + reader.accept(new JavaScriptLanguageClassVisitor(writer), ClassReader.EXPAND_FRAMES); + return writer.toByteArray(); + } + } + + private static class JavaScriptLanguageClassVisitor extends ClassVisitor { + public JavaScriptLanguageClassVisitor(ClassVisitor cv) { + super(ASM9, cv); + } + + @Override + public void visitEnd() { + final var mv = + cv.visitMethod( + ACC_PROTECTED, "isThreadAccessAllowed", "(Ljava/lang/Thread;Z)Z", null, null); + mv.visitCode(); + mv.visitInsn(ICONST_1); + mv.visitInsn(IRETURN); + mv.visitMaxs(1, 2); + mv.visitEnd(); + + super.visitEnd(); + } + } +} diff --git a/jvm/build.sbt b/jvm/build.sbt index 0b4d641..8b6d065 100644 --- a/jvm/build.sbt +++ b/jvm/build.sbt @@ -42,12 +42,13 @@ lazy val adapter = (project in file("adapter")) libraryDependencies ++= Seq( "io.gatling.highcharts" % "gatling-charts-highcharts" % gatlingVersion, "org.graalvm.polyglot" % "js-community" % graalvmJsVersion, + // Dependency of js-agent kept here so we don't have to make a fat jar "io.gatling" % "gatling-asm-shaded" % "9.7.0" ), Compile / sourceGenerators += Def.task { // Bit of a hack, generate a file directly into the CLI project to share version numbers val path = (ThisBuild / baseDirectory).value / ".." / "js" / "cli" / "src" / "dependencies" / "versions.ts" - val jsAdapterVersion = version.value + val gatlingJsVersion = version.value val content = s"""export const versions = { | graalvm: { @@ -61,7 +62,7 @@ lazy val adapter = (project in file("adapter")) | gatling: { | core: "$gatlingVersion", | enterprisePluginCommons: "$gatlingEnterpriseComponentPluginVersion", - | jsAdapter: "$jsAdapterVersion" + | js: "$gatlingJsVersion", | } |}; |""".stripMargin @@ -73,6 +74,31 @@ lazy val adapter = (project in file("adapter")) Compile / packageSrc / mappings := Seq.empty ) +lazy val agent = (project in file("agent")) + .withId("gatling-js-agent") + .enablePlugins(GatlingOssPlugin) + .settings( + name := "gatling-js-agent", + gatlingCompilerRelease := compilerRelease, + Compile / javacOptions ++= Seq("-encoding", "utf8", "-Xdoclint:none"), // FIXME: see why -Xdoclint:none does not seem to work + Compile / packageBin / packageOptions += + Package.ManifestAttributes( + "Premain-Class" -> "io.gatling.js.JavaScriptLanguageHack" + ), + Test / javacOptions ++= Seq("-encoding", "utf8"), + spotless := SpotlessConfig( + applyOnCompile = !sys.env.getOrElse("CI", "false").toBoolean + ), + spotlessJava := JavaConfig( + googleJavaFormat = GoogleJavaFormatConfig() + ), + libraryDependencies ++= Seq( + "io.gatling" % "gatling-asm-shaded" % "9.7.0" + ), + Compile / packageDoc / mappings := Seq.empty, + Compile / packageSrc / mappings := Seq.empty + ) + lazy val java2ts = (project in file("java2ts")) .withId("gatling-java2ts") .settings(