Skip to content

Commit

Permalink
Hot-replace VM inspection argument
Browse files Browse the repository at this point in the history
Depending on the graal vm version in use, use the appropriate
native-image option to allow for VM inspection.

Closes: Karm#107
  • Loading branch information
jerboaa committed Sep 22, 2022
1 parent 827c218 commit c894655
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 15 deletions.
31 changes: 23 additions & 8 deletions testsuite/src/it/java/org/graalvm/tests/integration/JFRTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.graalvm.tests.integration.utils.Apps;
import org.graalvm.tests.integration.utils.Logs;
import org.graalvm.tests.integration.utils.versions.IfMandrelVersion;
import org.graalvm.tests.integration.utils.versions.GraalVersionProperty;
import org.graalvm.tests.integration.utils.Commands;
import org.jboss.logging.Logger;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -52,6 +54,7 @@
import static org.graalvm.tests.integration.utils.Commands.removeContainers;
import static org.graalvm.tests.integration.utils.Commands.runCommand;
import static org.graalvm.tests.integration.utils.Commands.stopAllRunningContainers;
import static org.graalvm.tests.integration.utils.Commands.isVersion22_3OrBetter;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
Expand All @@ -72,18 +75,20 @@ public class JFRTest {
@Tag("builder-image")
@Tag("jfr")
@IfMandrelVersion(min = "21.2", inContainer = true)
@GraalVersionProperty(inContainer = true)
public void jfrSmokeContainerTest(TestInfo testInfo) throws IOException, InterruptedException {
jfrSmoke(testInfo, Apps.JFR_SMOKE_BUILDER_IMAGE);
jfrSmoke(testInfo, Apps.JFR_SMOKE_BUILDER_IMAGE, System.getProperty(GraalVersionProperty.NAME));
}

@Test
@Tag("jfr")
@IfMandrelVersion(min = "21.2")
@GraalVersionProperty
public void jfrSmokeTest(TestInfo testInfo) throws IOException, InterruptedException {
jfrSmoke(testInfo, Apps.JFR_SMOKE);
jfrSmoke(testInfo, Apps.JFR_SMOKE, System.getProperty(GraalVersionProperty.NAME));
}

public void jfrSmoke(TestInfo testInfo, Apps app) throws IOException, InterruptedException {
public void jfrSmoke(TestInfo testInfo, Apps app, String graalVersion) throws IOException, InterruptedException {
LOGGER.info("Testing app: " + app);
Process process = null;
File processLog = null;
Expand All @@ -101,7 +106,11 @@ public void jfrSmoke(TestInfo testInfo, Apps app) throws IOException, Interrupte

// In this case, the two last commands are used for running the app; one in JVM mode and the other in Native mode.
// We should somehow capture this semantically in an Enum or something. This is fragile...
builderRoutine(app.buildAndRunCmds.cmds.length - 2, app, report, cn, mn, appDir, processLog);
Commands.JFROption jfrOpt = Commands.JFROption.MONITOR_21;
if (isVersion22_3OrBetter(graalVersion)) {
jfrOpt = Commands.JFROption.MONITOR_22;
}
builderRoutine(app.buildAndRunCmds.cmds.length - 2, app, report, cn, mn, appDir, processLog, jfrOpt);

final File inputData = new File(BASE_DIR + File.separator + app.dir + File.separator + "target" + File.separator + "test_data.txt");

Expand Down Expand Up @@ -146,15 +155,17 @@ public void jfrSmoke(TestInfo testInfo, Apps app) throws IOException, Interrupte
@Tag("builder-image")
@Tag("jfr")
@IfMandrelVersion(min = "21.2", inContainer = true)
@GraalVersionProperty(inContainer = true)
public void jfrOptionsSmokeContainerTest(TestInfo testInfo) throws IOException, InterruptedException {
jfrOptionsSmoke(testInfo, Apps.JFR_OPTIONS_BUILDER_IMAGE);
jfrOptionsSmoke(testInfo, Apps.JFR_OPTIONS_BUILDER_IMAGE, System.getProperty(GraalVersionProperty.NAME));
}

@Test
@Tag("jfr")
@IfMandrelVersion(min = "21.2")
@GraalVersionProperty
public void jfrOptionsSmokeTest(TestInfo testInfo) throws IOException, InterruptedException {
jfrOptionsSmoke(testInfo, Apps.JFR_OPTIONS);
jfrOptionsSmoke(testInfo, Apps.JFR_OPTIONS, System.getProperty(GraalVersionProperty.NAME));
}

/**
Expand All @@ -169,7 +180,7 @@ public void jfrOptionsSmokeTest(TestInfo testInfo) throws IOException, Interrupt
* @throws IOException
* @throws InterruptedException
*/
public void jfrOptionsSmoke(TestInfo testInfo, Apps app) throws IOException, InterruptedException {
public void jfrOptionsSmoke(TestInfo testInfo, Apps app, String graalVersion) throws IOException, InterruptedException {
LOGGER.info("Testing app: " + app);
File processLog = null;
final StringBuilder report = new StringBuilder();
Expand All @@ -184,7 +195,11 @@ public void jfrOptionsSmoke(TestInfo testInfo, Apps app) throws IOException, Int
// Build and run
processLog = new File(appDir.getAbsolutePath() + File.separator + "logs" + File.separator + "build-and-run.log");

builderRoutine(2, app, report, cn, mn, appDir, processLog);
Commands.JFROption jfrOpt = Commands.JFROption.MONITOR_21;
if (isVersion22_3OrBetter(graalVersion)) {
jfrOpt = Commands.JFROption.MONITOR_22;
}
builderRoutine(2, app, report, cn, mn, appDir, processLog, jfrOpt);

final Map<String[], Pattern> cmdOutput = new HashMap<>();
cmdOutput.put(new String[]{"./target/timezones",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import static org.graalvm.tests.integration.utils.Commands.CONTAINER_RUNTIME;
import static org.graalvm.tests.integration.utils.Commands.IS_THIS_WINDOWS;
import static org.graalvm.tests.integration.utils.Commands.QUARKUS_VERSION;
import static org.graalvm.tests.integration.utils.Commands.JFR_MONITORING_SWITCH_TOKEN;
import static org.graalvm.tests.integration.utils.Commands.getUnixUIDGID;

/**
Expand Down Expand Up @@ -149,7 +150,7 @@ public enum BuildAndRunCmds {
new String[]{"powershell", "-c", "\"Expand-Archive -Path test_data.txt.zip -DestinationPath target -Force\""}
:
new String[]{"unzip", "test_data.txt.zip", "-d", "target"},
new String[]{"native-image", "-H:+AllowVMInspection", "-jar", "target/debug-symbols-smoke.jar", "target/debug-symbols-smoke"},
new String[]{"native-image", JFR_MONITORING_SWITCH_TOKEN, "-jar", "target/debug-symbols-smoke.jar", "target/debug-symbols-smoke"},
new String[]{"java",
"-XX:+FlightRecorder",
"-XX:StartFlightRecording=filename=logs/flight-java.jfr",
Expand All @@ -166,7 +167,7 @@ public enum BuildAndRunCmds {
CONTAINER_RUNTIME, "run", "-u", IS_THIS_WINDOWS ? "" : getUnixUIDGID(),
"-t", "-v", BASE_DIR + File.separator + "apps" + File.separator + "debug-symbols-smoke:/project:z",
"--name", ContainerNames.JFR_SMOKE_BUILDER_IMAGE.name + "-build",
BUILDER_IMAGE, "-H:+AllowVMInspection", "-jar", "target/debug-symbols-smoke.jar", "target/debug-symbols-smoke"},
BUILDER_IMAGE, JFR_MONITORING_SWITCH_TOKEN, "-jar", "target/debug-symbols-smoke.jar", "target/debug-symbols-smoke"},
new String[]{
CONTAINER_RUNTIME, "run", "-u", IS_THIS_WINDOWS ? "" : getUnixUIDGID(),
"-i",
Expand All @@ -184,7 +185,7 @@ public enum BuildAndRunCmds {
}),
JFR_OPTIONS(new String[][]{
new String[]{"mvn", "package"},
new String[]{"native-image", "-H:+AllowVMInspection", "-jar", "target/timezones.jar", "target/timezones"}
new String[]{"native-image", JFR_MONITORING_SWITCH_TOKEN, "-jar", "target/timezones.jar", "target/timezones"}
// @see JFRTest.java
}),
JFR_OPTIONS_BUILDER_IMAGE(new String[][]{
Expand All @@ -193,7 +194,7 @@ public enum BuildAndRunCmds {
CONTAINER_RUNTIME, "run", "-u", IS_THIS_WINDOWS ? "" : getUnixUIDGID(),
"-t", "-v", BASE_DIR + File.separator + "apps" + File.separator + "timezones:/project:z",
"--name", ContainerNames.JFR_SMOKE_BUILDER_IMAGE.name + "-build",
BUILDER_IMAGE, "-H:+AllowVMInspection", "-jar", "target/timezones.jar", "target/timezones"}
BUILDER_IMAGE, JFR_MONITORING_SWITCH_TOKEN, "-jar", "target/timezones.jar", "target/timezones"}
// @see JFRTest.java
}),
RESLOCATIONS(new String[][]{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,25 @@
* @author Michal Karm Babacek <[email protected]>
*/
public class Commands {

public enum JFROption {
MONITOR_22("--enable-monitoring"),
MONITOR_21("-H:+AllowVMInspection"),
NONE("");

private String replacement;

JFROption(String rep) {
this.replacement = rep;
}

String replacement() {
return this.replacement;
}
}

private static final Logger LOGGER = Logger.getLogger(Commands.class.getName());
public static final String JFR_MONITORING_SWITCH_TOKEN = "<ALLOW_VM_INSPECTION>";
public static final String CONTAINER_RUNTIME = getProperty(
new String[]{"QUARKUS_NATIVE_CONTAINER_RUNTIME", "quarkus.native.container-runtime"},
"docker");
Expand Down Expand Up @@ -109,6 +127,23 @@ public static String getProperty(String[] alternatives, String defaultValue) {
return prop;
}

public static boolean isVersion22_3OrBetter(String version) {
if (version == null) {
return false;
}
try {
String[] tokens = version.split("\\.");
if (tokens.length < 2) {
return false;
}
int major = Integer.parseInt(tokens[0]);
int minor = Integer.parseInt(tokens[1]);
return major >= 22 && minor > 2;
} catch (NumberFormatException e) {
return false; // assume default of 21
}
}

public static String getUnixUIDGID() {
final UnixSystem s = new UnixSystem();
return s.getUid() + ":" + s.getGid();
Expand Down Expand Up @@ -694,13 +729,22 @@ public static boolean waitForBufferToMatch(StringBuffer stringBuffer, Pattern pa
}

public static void builderRoutine(int steps, Apps app, StringBuilder report, String cn, String mn, File appDir, File processLog, Map<String, String> env) throws InterruptedException {
builderRoutine(steps, app, report, cn, mn, appDir, processLog, env, JFROption.NONE);
}

public static void builderRoutine(int steps, Apps app, StringBuilder report, String cn, String mn, File appDir, File processLog, Map<String, String> env, JFROption jfrOpt) throws InterruptedException {
// The last command is reserved for running it
assertTrue(app.buildAndRunCmds.cmds.length > 1);
Logs.appendln(report, "# " + cn + ", " + mn);
for (int i = 0; i < steps; i++) {
// We cannot run commands in parallel, we need them to follow one after another
final ExecutorService buildService = Executors.newFixedThreadPool(1);
final List<String> cmd = Commands.getRunCommand(app.buildAndRunCmds.cmds[i]);
// Replace actual option for JFR enabled builds so that the appropriate command option
// gets passed and doesn't produce a deprecation warning
if (jfrOpt != JFROption.NONE) {
replaceMonitoringSwitch(cmd, jfrOpt.replacement);
}
buildService.submit(new Commands.ProcessRunner(appDir, processLog, cmd, 10, env)); // might take a long time....
Logs.appendln(report, (new Date()).toString());
Logs.appendln(report, appDir.getAbsolutePath());
Expand All @@ -710,6 +754,15 @@ public static void builderRoutine(int steps, Apps app, StringBuilder report, Str
assertTrue(processLog.exists());
}

private static void replaceMonitoringSwitch(List<String> cmd, String replacement) {
for (int i = 0; i < cmd.size(); i++) {
if (cmd.get(i).trim().equals(JFR_MONITORING_SWITCH_TOKEN)) {
cmd.set(i, replacement);
}
}
}


// Copied from
// https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ExecutorService.html
private static void shutdownAndAwaitTermination(ExecutorService pool, long timeout, TimeUnit unit) {
Expand All @@ -731,15 +784,19 @@ private static void shutdownAndAwaitTermination(ExecutorService pool, long timeo
}

public static void builderRoutine(Apps app, StringBuilder report, String cn, String mn, File appDir, File processLog) throws InterruptedException {
builderRoutine(app.buildAndRunCmds.cmds.length - 1, app, report, cn, mn, appDir, processLog, null);
builderRoutine(app.buildAndRunCmds.cmds.length - 1, app, report, cn, mn, appDir, processLog, null, JFROption.NONE);
}

public static void builderRoutine(Apps app, StringBuilder report, String cn, String mn, File appDir, File processLog, Map<String, String> env) throws InterruptedException {
builderRoutine(app.buildAndRunCmds.cmds.length - 1, app, report, cn, mn, appDir, processLog, env);
builderRoutine(app.buildAndRunCmds.cmds.length - 1, app, report, cn, mn, appDir, processLog, env, JFROption.NONE);
}

public static void builderRoutine(int steps, Apps app, StringBuilder report, String cn, String mn, File appDir, File processLog) throws InterruptedException {
builderRoutine(steps, app, report, cn, mn, appDir, processLog, null);
builderRoutine(steps, app, report, cn, mn, appDir, processLog, null, JFROption.NONE);
}

public static void builderRoutine(int steps, Apps app, StringBuilder report, String cn, String mn, File appDir, File processLog, JFROption jfrOpt) throws InterruptedException {
builderRoutine(steps, app, report, cn, mn, appDir, processLog, null, jfrOpt);
}

public static void replaceInSmallTextFile(Pattern search, String replace, Path file, Charset charset) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2022, Red Hat Inc. All rights reserved.
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.graalvm.tests.integration.utils.versions;

import org.graalvm.home.Version;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class GraalVersionPropSetter implements AfterEachCallback, BeforeEachCallback {

@Override
public void afterEach(ExtensionContext extensionContext) throws Exception {
final GraalVersionProperty annotation = extensionContext.getTestMethod().get().getAnnotation(GraalVersionProperty.class);
System.clearProperty(GraalVersionProperty.NAME);
}

@Override
public void beforeEach(ExtensionContext extensionContext) throws Exception {
final GraalVersionProperty annotation = extensionContext.getTestMethod().get().getAnnotation(GraalVersionProperty.class);
final Version usedVersion = UsedVersion.getVersion(annotation.inContainer());
System.setProperty(GraalVersionProperty.NAME, usedVersion.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2021, Red Hat Inc. All rights reserved.
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.graalvm.tests.integration.utils.versions;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Sets the property specified by {@code name} (default 'graal.vm.version')
* to the currently in-use Graal version for the annotated test run
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(GraalVersionPropSetter.class)
@Test
public @interface GraalVersionProperty {

public final static String NAME = "graal.vm.version";

boolean inContainer() default false;
}

0 comments on commit c894655

Please sign in to comment.