Compare commits

...

1 Commits

Author SHA1 Message Date
Andrei Pangin
bc03471093 JFR provider PoC 2022-07-31 17:15:00 +03:00
7 changed files with 340 additions and 0 deletions

View File

@@ -69,6 +69,7 @@ static const Multiplier UNIVERSAL[] = {{'n', 1}, {'u', 1000}, {'m', 1000000}, {'
// tree - produce call tree in HTML format
// jfr - dump events in Java Flight Recorder format
// jfrsync[=CONFIG] - start Java Flight Recording with the given config along with the profiler
// jfrprovider - register async-profiler as a Flight Recording provider
// traces[=N] - dump top N call traces
// flat[=N] - dump top N methods (aka flat profile)
// samples - count the number of samples (default)
@@ -174,6 +175,9 @@ Error Arguments::parse(const char* args) {
_jfr_options = JFR_SYNC_OPTS;
_jfr_sync = value == NULL ? "default" : value;
CASE("jfrprovider")
_jfr_provider = true;
CASE("traces")
_output = OUTPUT_TEXT;
_dump_traces = value == NULL ? INT_MAX : atoi(value);

View File

@@ -150,6 +150,7 @@ class Arguments {
int _include;
int _exclude;
unsigned char _mcache;
bool _jfr_provider;
bool _loop;
bool _threads;
bool _sched;
@@ -195,6 +196,7 @@ class Arguments {
_include(0),
_exclude(0),
_mcache(0),
_jfr_provider(false),
_loop(false),
_threads(false),
_sched(false),

Binary file not shown.

View File

@@ -0,0 +1,312 @@
/*
* Copyright 2022 Andrei Pangin
*
* 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 one.profiler;
import jdk.management.jfr.ConfigurationInfo;
import jdk.management.jfr.EventTypeInfo;
import jdk.management.jfr.FlightRecorderMXBean;
import jdk.management.jfr.RecordingInfo;
import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeType;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.management.ManagementFactory;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FlightRecorderMXBeanImpl implements FlightRecorderMXBean, CompositeData {
private final Map<String, Object> cd = new HashMap<>();
private boolean oldver;
private long id;
private String fileName;
private long duration;
private long startTime;
private long stopTime;
private RandomAccessFile raf;
private Map<String, String> options = Collections.emptyMap();
private Map<String, String> settings = Collections.emptyMap();
private FlightRecorderMXBeanImpl() throws JMException {
try {
RecordingInfo.class.getDeclaredField("toDisk");
} catch (NoSuchFieldException e) {
oldver = true;
}
ObjectName name = getObjectName();
try {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(name);
} catch (InstanceNotFoundException e) {
// ignore
}
ManagementFactory.getPlatformMBeanServer().registerMBean(this, name);
}
@Override
public long newRecording() {
return ++id;
}
@Override
public long takeSnapshot() {
return ++id;
}
@Override
public long cloneRecording(long recordingId, boolean stop) {
return ++id;
}
@Override
public synchronized void startRecording(long recordingId) {
try {
File f = File.createTempFile("asprof" + id + "-", ".jfr");
fileName = f.getAbsolutePath();
f.deleteOnExit();
duration = parseDuration(options.get("duration"));
StringBuilder sb = new StringBuilder("start,jfr,file=").append(fileName);
for (Map.Entry<String, String> e : settings.entrySet()) {
if (e.getKey().startsWith("settings.AsyncProfiler#") && e.getValue() != null && !e.getValue().isEmpty()) {
sb.append(',').append(e.getKey().substring(23)).append('=').append(e.getValue());
}
}
execute0(sb.toString());
startTime = System.currentTimeMillis();
stopTime = 0;
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
private long parseDuration(String s) {
if (s == null || s.isEmpty()) {
return 0;
}
long value = 0;
for (int i = 0, len = s.length(); i < len; i++) {
char c = s.charAt(i);
if (c >= '0' && c <= '9') {
value = value * 10 + (c - '0');
} else if (c > ' ') {
switch (Character.toLowerCase(c)) {
case 's':
return value;
case 'm':
return value * 60;
case 'h':
return value * 3600;
case 'd':
return value * 86400;
}
throw new IllegalArgumentException("Not a valid duration: " + s);
}
}
return value;
}
@Override
public synchronized boolean stopRecording(long recordingId) {
try {
execute0("stop");
stopTime = System.currentTimeMillis();
return true;
} catch (IOException e) {
return false;
}
}
@Override
public void closeRecording(long recordingId) {
}
@Override
public synchronized long openStream(long recordingId, Map<String, String> streamOptions) throws IOException {
raf = new RandomAccessFile(fileName, "r");
return recordingId;
}
@Override
public synchronized void closeStream(long streamId) throws IOException {
if (raf != null) {
try {
raf.close();
} finally {
raf = null;
}
}
}
@Override
public synchronized byte[] readStream(long streamId) throws IOException {
if (raf == null) {
throw new IOException("Stream closed");
}
byte[] buf = new byte[65536];
int bytes = raf.read(buf);
return bytes <= 0 ? null : bytes == buf.length ? buf : Arrays.copyOf(buf, bytes);
}
@Override
public Map<String, String> getRecordingOptions(long recordingId) {
return options;
}
@Override
public Map<String, String> getRecordingSettings(long recordingId) {
return settings;
}
@Override
public void setConfiguration(long recordingId, String contents) {
}
@Override
public void setPredefinedConfiguration(long recordingId, String configurationName) {
}
@Override
public void setRecordingSettings(long recordingId, Map<String, String> settings) {
this.settings = settings;
}
@Override
public void setRecordingOptions(long recordingId, Map<String, String> options) {
this.options = options;
}
@Override
public synchronized List<RecordingInfo> getRecordings() {
if (id == 0) {
return Collections.emptyList();
}
if (stopTime == 0 && duration > 0 && System.currentTimeMillis() >= startTime + duration * 1000) {
stopRecording(id);
}
return Collections.singletonList(newRecordingInfo());
}
private RecordingInfo newRecordingInfo() {
cd.clear();
cd.put("id", oldver ? (Object) (int) id : (Object) id);
cd.put("name", options.get("name"));
cd.put("state", stopTime > 0 ? "STOPPED" : "RUNNING");
cd.put("dumpOnExit", true);
cd.put("size", new File(fileName).length());
cd.put("disk", true);
cd.put("maxAge", 0L);
cd.put("maxSize", 0L);
cd.put("startTime", startTime);
cd.put("stopTime", stopTime);
cd.put("destination", fileName);
cd.put("duration", duration);
return RecordingInfo.from(this);
}
@Override
public List<ConfigurationInfo> getConfigurations() {
return Collections.singletonList(newConfigurationInfo("async-profiler"));
}
private ConfigurationInfo newConfigurationInfo(String name) {
cd.clear();
cd.put("name", name);
cd.put("label", name);
cd.put("provider", "async-profiler");
cd.put("contents", "<configuration version=\"2.0\" label=\"" + name + "\" provider=\"async-profiler\">\n" +
" <event name=\"settings.AsyncProfiler\">\n" +
" <setting name=\"event\">cpu</setting>\n" +
" <setting name=\"interval\">10ms</setting>\n" +
" <setting name=\"alloc\"></setting>\n" +
" <setting name=\"lock\"></setting>\n" +
" <setting name=\"cstack\"></setting>\n" +
" <setting name=\"alluser\"></setting>\n" +
" </event>\n" +
"</configuration>\n");
return ConfigurationInfo.from(this);
}
@Override
public List<EventTypeInfo> getEventTypes() {
return Collections.emptyList();
}
@Override
public void copyTo(long recordingId, String outputFile) throws IOException {
Files.copy(Paths.get(fileName), Paths.get(outputFile), StandardCopyOption.REPLACE_EXISTING);
}
@Override
public ObjectName getObjectName() {
try {
return new ObjectName("jdk.management.jfr:type=FlightRecorder");
} catch (MalformedObjectNameException e) {
throw new AssertionError(e);
}
}
@Override
public CompositeType getCompositeType() {
throw new UnsupportedOperationException();
}
@Override
public Object get(String key) {
return cd.get(key);
}
@Override
public Object[] getAll(String[] keys) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsKey(String key) {
return cd.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return cd.containsValue(value);
}
@Override
public Collection<?> values() {
return cd.values();
}
private native String execute0(String command) throws IllegalArgumentException, IllegalStateException, IOException;
}

View File

@@ -26,6 +26,7 @@
INCBIN(SERVER_CLASS, "one/profiler/Server.class")
INCBIN(MXBEAN_CLASS, "one/profiler/FlightRecorderMXBeanImpl.class")
static void throwNew(JNIEnv* env, const char* exception_class, const char* message) {
@@ -194,3 +195,15 @@ bool JavaAPI::startHttpServer(jvmtiEnv* jvmti, JNIEnv* jni, const char* address)
jni->ExceptionDescribe();
return false;
}
bool JavaAPI::registerJfrProvider(jvmtiEnv* jvmti, JNIEnv* jni) {
jclass cls = jni->DefineClass(NULL, NULL, (const jbyte*)MXBEAN_CLASS, INCBIN_SIZEOF(MXBEAN_CLASS));
if (cls != NULL && jni->RegisterNatives(cls, execute0, 1) == 0) {
if (jni->NewObject(cls, jni->GetMethodID(cls, "<init>", "()V")) != NULL) {
return true;
}
}
jni->ExceptionDescribe();
return false;
}

View File

@@ -24,6 +24,7 @@ class JavaAPI {
public:
static void registerNatives(jvmtiEnv* jvmti, JNIEnv* jni);
static bool startHttpServer(jvmtiEnv* jvmti, JNIEnv* jni, const char* address);
static bool registerJfrProvider(jvmtiEnv* jvmti, JNIEnv* jni);
};
#endif // _JAVAAPI_H

View File

@@ -330,6 +330,14 @@ void JNICALL VM::VMInit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
}
}
if (_agent_args._jfr_provider) {
if (JavaAPI::registerJfrProvider(jvmti, jni)) {
Log::info("Registered JFR provider");
} else {
Log::error("Failed to register JFR provider");
}
}
// Delayed start of profiler if agent has been loaded at VM bootstrap
Error error = Profiler::instance()->run(_agent_args);
if (error) {