Skip to content

Commit

Permalink
[GR-28897] Agent support for generating configuration with origin inf…
Browse files Browse the repository at this point in the history
…ormation.

PullRequest: graal/8174
  • Loading branch information
Aleksandar Gradinac committed Jun 10, 2021
2 parents 2f4838d + 2683ba6 commit 2f811ae
Show file tree
Hide file tree
Showing 35 changed files with 1,436 additions and 344 deletions.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -36,27 +36,33 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;

import com.oracle.svm.agent.stackaccess.InterceptedState;
import com.oracle.svm.agent.stackaccess.EagerlyLoadedJavaStackAccess;
import com.oracle.svm.agent.stackaccess.OnDemandJavaStackAccess;
import com.oracle.svm.agent.tracing.TraceFileWriter;
import com.oracle.svm.agent.tracing.ConfigurationResultWriter;
import com.oracle.svm.agent.tracing.core.Tracer;
import com.oracle.svm.agent.tracing.core.TracingResultWriter;
import com.oracle.svm.core.configure.ConfigurationFile;
import org.graalvm.nativeimage.ProcessProperties;
import org.graalvm.nativeimage.hosted.Feature;

import com.oracle.svm.agent.predicatedconfig.MethodInfoRecordKeeper;
import com.oracle.svm.agent.predicatedconfig.ConfigurationWithOriginsResultWriter;
import com.oracle.svm.configure.config.ConfigurationSet;
import com.oracle.svm.configure.filters.FilterConfigurationParser;
import com.oracle.svm.configure.filters.RuleNode;
import com.oracle.svm.configure.json.JsonPrintable;
import com.oracle.svm.configure.json.JsonWriter;
import com.oracle.svm.configure.trace.AccessAdvisor;
import com.oracle.svm.configure.trace.TraceProcessor;
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.configure.ConfigurationFiles;
import com.oracle.svm.driver.NativeImage;
import com.oracle.svm.jni.nativeapi.JNIEnvironment;
import com.oracle.svm.jni.nativeapi.JNIJavaVM;
Expand All @@ -74,12 +80,11 @@ public final class NativeImageAgent extends JvmtiAgentBase<NativeImageAgentJNIHa

private ScheduledThreadPoolExecutor periodicConfigWriterExecutor = null;

private TraceWriter traceWriter;
private Tracer tracer;
private TracingResultWriter tracingResultWriter;

private Path configOutputDirPath;

private AccessAdvisor accessAdvisor;

private static String getTokenValue(String token) {
return token.substring(token.indexOf('=') + 1);
}
Expand All @@ -106,6 +111,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
boolean experimentalClassLoaderSupport = true;
boolean experimentalClassDefineSupport = false;
boolean build = false;
boolean configurationWithOrigins = false;
int configWritePeriod = -1; // in seconds
int configWritePeriodInitialDelay = 1; // in seconds

Expand Down Expand Up @@ -166,6 +172,8 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
build = true;
} else if (token.startsWith("build=")) {
build = Boolean.parseBoolean(getTokenValue(token));
} else if (token.equals("experimental-configuration-with-origins")) {
configurationWithOrigins = true;
} else {
return usage(1, "unknown option: '" + token + "'.");
}
Expand All @@ -176,6 +184,15 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
inform("no output/build options provided, tracking dynamic accesses and writing configuration to directory: " + configOutputDir);
}

if (configurationWithOrigins && !mergeConfigs.isEmpty()) {
configurationWithOrigins = false;
inform("using configuration with origins with configuration merging is currently unsupported. Disabling configuration with origins mode.");
}

if (configurationWithOrigins) {
warn("using experimental configuration with origins mode. Note that native-image cannot process these files, and this flag may change or be removed without a warning!");
}

RuleNode callerFilter = null;
if (!builtinCallerFilter) {
callerFilter = RuleNode.createRoot();
Expand All @@ -198,6 +215,10 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
}
}

final MethodInfoRecordKeeper recordKeeper = new MethodInfoRecordKeeper(configurationWithOrigins);
final Supplier<InterceptedState> interceptedStateSupplier = configurationWithOrigins ? EagerlyLoadedJavaStackAccess.stackAccessSupplier()
: OnDemandJavaStackAccess.stackAccessSupplier();

if (configOutputDir != null) {
if (traceOutputFile != null) {
return usage(1, "can only once specify exactly one of trace-output=, config-output-dir= or config-merge-dir=.");
Expand All @@ -215,18 +236,28 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
return e; // rethrow
};
AccessAdvisor advisor = createAccessAdvisor(builtinHeuristicFilter, callerFilter, accessFilter);
Path[] predefinedClassDestinationDirs = {configOutputDirPath.resolve(ConfigurationFiles.PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR)};
TraceProcessor processor = new TraceProcessor(advisor, mergeConfigs.loadJniConfig(handler), mergeConfigs.loadReflectConfig(handler),
mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler), mergeConfigs.loadSerializationConfig(handler),
mergeConfigs.loadPredefinedClassesConfig(predefinedClassDestinationDirs, handler));
traceWriter = new TraceProcessorWriterAdapter(processor);
Path[] predefinedClassDestinationDirs = {configOutputDirPath.resolve(ConfigurationFile.PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR)};
if (configurationWithOrigins) {
ConfigurationWithOriginsResultWriter writer = new ConfigurationWithOriginsResultWriter(advisor, recordKeeper);
tracer = writer;
tracingResultWriter = writer;
} else {
TraceProcessor processor = new TraceProcessor(advisor, mergeConfigs.loadJniConfig(handler), mergeConfigs.loadReflectConfig(handler),
mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler), mergeConfigs.loadSerializationConfig(handler),
mergeConfigs.loadPredefinedClassesConfig(predefinedClassDestinationDirs, handler));
ConfigurationResultWriter writer = new ConfigurationResultWriter(processor);
tracer = writer;
tracingResultWriter = writer;
}
} catch (Throwable t) {
return error(2, t.toString());
}
} else if (traceOutputFile != null) {
try {
Path path = Paths.get(transformPath(traceOutputFile));
traceWriter = new TraceFileWriter(path);
TraceFileWriter writer = new TraceFileWriter(path);
tracer = writer;
tracingResultWriter = writer;
} catch (Throwable t) {
return error(2, t.toString());
}
Expand All @@ -240,14 +271,13 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
return status;
}

accessAdvisor = createAccessAdvisor(builtinHeuristicFilter, callerFilter, accessFilter);
try {
BreakpointInterceptor.onLoad(jvmti, callbacks, traceWriter, this, experimentalClassLoaderSupport, experimentalClassDefineSupport);
BreakpointInterceptor.onLoad(jvmti, callbacks, tracer, this, interceptedStateSupplier, experimentalClassLoaderSupport, experimentalClassDefineSupport);
} catch (Throwable t) {
return error(3, t.toString());
}
try {
JniCallInterceptor.onLoad(traceWriter, this);
JniCallInterceptor.onLoad(tracer, this, interceptedStateSupplier);
} catch (Throwable t) {
return error(4, t.toString());
}
Expand Down Expand Up @@ -309,7 +339,7 @@ private static boolean parseFilterFiles(RuleNode filter, List<String> filterFile
}

private void setupExecutorServiceForPeriodicConfigurationCapture(int writePeriod, int initialDelay) {
if (traceWriter == null || configOutputDirPath == null) {
if (tracingResultWriter == null || configOutputDirPath == null || !tracingResultWriter.supportsPeriodicTraceWriting()) {
return;
}

Expand Down Expand Up @@ -388,26 +418,24 @@ private static String transformPath(String path) {

@Override
protected void onVMInitCallback(JvmtiEnv jvmti, JNIEnvironment jni, JNIObjectHandle thread) {
accessAdvisor.setInLivePhase(true);
BreakpointInterceptor.onVMInit(jvmti, jni);
if (traceWriter != null) {
traceWriter.tracePhaseChange("live");
if (tracer != null) {
tracer.tracePhaseChange("live");
}
}

@Override
protected void onVMStartCallback(JvmtiEnv jvmti, JNIEnvironment jni) {
JniCallInterceptor.onVMStart(jvmti);
if (traceWriter != null) {
traceWriter.tracePhaseChange("start");
if (tracer != null) {
tracer.tracePhaseChange("start");
}
}

@Override
protected void onVMDeathCallback(JvmtiEnv jvmti, JNIEnvironment jni) {
accessAdvisor.setInLivePhase(false);
if (traceWriter != null) {
traceWriter.tracePhaseChange("dead");
if (tracer != null) {
tracer.tracePhaseChange("dead");
}
}

Expand All @@ -419,27 +447,12 @@ private void writeConfigurationFiles() {
final Path tempDirectory = configOutputDirPath.toFile().exists()
? Files.createTempDirectory(configOutputDirPath, "tempConfig-")
: Files.createTempDirectory("tempConfig-");
TraceProcessor p = ((TraceProcessorWriterAdapter) traceWriter).getProcessor();

Map<String, JsonPrintable> allConfigFiles = new HashMap<>();
allConfigFiles.put(ConfigurationFiles.REFLECTION_NAME, p.getReflectionConfiguration());
allConfigFiles.put(ConfigurationFiles.JNI_NAME, p.getJniConfiguration());
allConfigFiles.put(ConfigurationFiles.DYNAMIC_PROXY_NAME, p.getProxyConfiguration());
allConfigFiles.put(ConfigurationFiles.RESOURCES_NAME, p.getResourceConfiguration());
allConfigFiles.put(ConfigurationFiles.SERIALIZATION_NAME, p.getSerializationConfiguration());
allConfigFiles.put(ConfigurationFiles.PREDEFINED_CLASSES_NAME, p.getPredefinedClassesConfiguration());

for (Map.Entry<String, JsonPrintable> configFile : allConfigFiles.entrySet()) {
Path tempPath = tempDirectory.resolve(configFile.getKey());
try (JsonWriter writer = new JsonWriter(tempPath)) {
configFile.getValue().printJson(writer);
}
}
List<Path> writtenFilePaths = tracingResultWriter.writeToDirectory(tempDirectory);

for (Map.Entry<String, JsonPrintable> configFile : allConfigFiles.entrySet()) {
Path source = tempDirectory.resolve(configFile.getKey());
Path target = configOutputDirPath.resolve(configFile.getKey());
tryAtomicMove(source, target);
for (Path writtenFilePath : writtenFilePaths) {
Path fileName = tempDirectory.relativize(writtenFilePath);
Path target = configOutputDirPath.resolve(fileName);
tryAtomicMove(writtenFilePath, target);
}

/*
Expand Down Expand Up @@ -499,14 +512,18 @@ protected int onUnloadCallback(JNIJavaVM vm) {
}
}

if (traceWriter != null) {
traceWriter.tracePhaseChange("unload");
traceWriter.close();
if (configOutputDirPath != null) {
writeConfigurationFiles();
configOutputDirPath = null;
if (tracer != null) {
tracer.tracePhaseChange("unload");
}

if (tracingResultWriter != null) {
tracingResultWriter.close();
if (tracingResultWriter.supportsOnUnloadTraceWriting()) {
if (configOutputDirPath != null) {
writeConfigurationFiles();
configOutputDirPath = null;
}
}
traceWriter = null;
}

/*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.agent.predicatedconfig;

import com.oracle.svm.jni.nativeapi.JNIMethodId;
import com.oracle.svm.jvmtiagentbase.Support;
import jdk.vm.ci.meta.MetaUtil;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.c.type.CCharPointerPointer;
import org.graalvm.word.WordFactory;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

import static com.oracle.svm.jvmtiagentbase.Support.check;
import static org.graalvm.word.WordFactory.nullPointer;

class ClassInfo {

final String className;
final Map<String, MethodInfo> nameAndSignatureToMethodInfoMap;

ClassInfo(String classSignature) {
this.className = MetaUtil.internalNameToJava(classSignature, true, false);
this.nameAndSignatureToMethodInfoMap = new ConcurrentHashMap<>();
}

MethodInfo findOrCreateMethodInfo(long rawJMethodIdValue) {
JNIMethodId jMethodId = WordFactory.pointer(rawJMethodIdValue);

CCharPointerPointer methodNamePtr = StackValue.get(CCharPointerPointer.class);
CCharPointerPointer methodSignaturePtr = StackValue.get(CCharPointerPointer.class);

check(Support.jvmtiFunctions().GetMethodName().invoke(Support.jvmtiEnv(), jMethodId, methodNamePtr, methodSignaturePtr, nullPointer()));
String methodName = MethodInfoRecordKeeper.getJavaStringAndFreeNativeString(methodNamePtr.read());
String methodSignature = MethodInfoRecordKeeper.getJavaStringAndFreeNativeString(methodSignaturePtr.read());
String methodNameAndSignature = combineMethodNameAndSignature(methodName, methodSignature);

nameAndSignatureToMethodInfoMap.computeIfAbsent(methodNameAndSignature, nameAndSignature -> new MethodInfo(methodName, methodSignature, this));
return nameAndSignatureToMethodInfoMap.get(methodNameAndSignature);
}

private static String combineMethodNameAndSignature(String methodName, String methodSignature) {
return methodName + "-" + methodSignature;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ClassInfo classInfo = (ClassInfo) o;
return className.equals(classInfo.className);
}

@Override
public int hashCode() {
return Objects.hash(className);
}
}
Loading

0 comments on commit 2f811ae

Please sign in to comment.