8361639: JFR: Incorrect top frame for I/O events

Reviewed-by: mgronlun
Backport-of: 1a6cbe421facab0de1c7162f2762258664338814
This commit is contained in:
Erik Gahlin 2025-07-17 12:20:22 +00:00
parent e989c1d138
commit 331adac38e
6 changed files with 402 additions and 17 deletions

View File

@ -38,7 +38,13 @@ import jdk.jfr.internal.MirrorEvent;
@Label("File Read")
@Category("Java Application")
@Description("Reading data from a file")
@StackFilter({"java.io.FileInputStream", "java.io.RandomAccessFile", "sun.nio.ch.FileChannelImpl"})
@StackFilter({"java.nio.channels.FileChannel",
"java.io.DataInputStream",
"java.io.FileInputStream",
"java.io.InputStream",
"java.io.RandomAccessFile",
"sun.nio.ch.ChannelInputStream",
"sun.nio.ch.FileChannelImpl"})
@Throttle
public final class FileReadEvent extends MirrorEvent {

View File

@ -38,7 +38,13 @@ import jdk.jfr.internal.MirrorEvent;
@Label("File Write")
@Category("Java Application")
@Description("Writing data to a file")
@StackFilter({"java.io.FileOutputStream", "java.io.RandomAccessFile", "sun.nio.ch.FileChannelImpl"})
@StackFilter({"java.nio.channels.FileChannel",
"java.io.DataOutputStream",
"java.io.FileOutputStream",
"java.io.OutputStream",
"java.io.RandomAccessFile",
"sun.nio.ch.ChannelOutputStream",
"sun.nio.ch.FileChannelImpl"})
@Throttle
public final class FileWriteEvent extends MirrorEvent {

View File

@ -39,6 +39,10 @@ import jdk.jfr.internal.Type;
@Label("Socket Read")
@Category("Java Application")
@Description("Reading data from a socket")
@StackFilter({"java.io.InputStream",
"java.net.Socket$SocketInputStream",
"java.nio.channels.SocketChannel",
"sun.nio.ch.SocketInputStream"})
@Throttle
public final class SocketReadEvent extends MirrorEvent {

View File

@ -38,6 +38,10 @@ import jdk.jfr.internal.Type;
@Label("Socket Write")
@Category("Java Application")
@Description("Writing data to a socket")
@StackFilter({"java.io.OutputStream",
"java.net.Socket$SocketOutputStream",
"java.nio.channels.SocketChannel",
"sun.nio.ch.SocketOutputStream"})
@Throttle
public final class SocketWriteEvent extends MirrorEvent {

View File

@ -95,18 +95,6 @@ public final class PlatformEventType extends Type {
return false;
}
private boolean isStaticCommit() {
switch (getName()) {
case Type.EVENT_NAME_PREFIX + "SocketRead" :
case Type.EVENT_NAME_PREFIX + "SocketWrite" :
case Type.EVENT_NAME_PREFIX + "FileRead" :
case Type.EVENT_NAME_PREFIX + "FileWrite" :
case Type.EVENT_NAME_PREFIX + "FileForce" :
return true;
}
return false;
}
private int determineStackTraceOffset() {
if (isJDK) {
// Order matters
@ -116,9 +104,14 @@ public final class PlatformEventType extends Type {
if (getModification() == Modification.TRACING) {
return 5;
}
if (isStaticCommit()) {
return 3;
}
return switch (getName()) {
case Type.EVENT_NAME_PREFIX + "SocketRead",
Type.EVENT_NAME_PREFIX + "SocketWrite",
Type.EVENT_NAME_PREFIX + "FileRead",
Type.EVENT_NAME_PREFIX + "FileWrite" -> 6;
case Type.EVENT_NAME_PREFIX + "FileForce" -> 5;
default -> 3;
};
}
return 3;
}

View File

@ -0,0 +1,372 @@
/*
* Copyright (c) 2025, 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.
*
* 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 jdk.jfr.event.io;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.WRITE;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import jdk.jfr.Recording;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedMethod;
import jdk.jfr.consumer.RecordedStackTrace;
import jdk.jfr.consumer.RecordingFile;
/**
* @test TestIOTopFrame
* @summary Tests that the top frames of I/O events are as expected.
* @requires vm.flagless
* @requires vm.hasJFR
* @library /test/lib /test/jdk
* @run main/othervm jdk.jfr.event.io.TestIOTopFrame
*/
// Lines commented with a number indicate that they are the nth event for that
// top frame. The invocation of the methods RandomAccessFile::readUTF(),
// SocketInputStream::readNBytes(...) and FileInputStream::readAllBytes()
// results in 2-3 events.
public class TestIOTopFrame {
private static final String EVENT_FILE_READ = "jdk.FileRead";
private static final String EVENT_FILE_FORCE = "jdk.FileForce";
private static final String EVENT_FILE_WRITE = "jdk.FileWrite";
private static final String EVENT_SOCKET_READ = "jdk.SocketRead";
private static final String EVENT_SOCKET_WRITE = "jdk.SocketWrite";
public static void main(String... args) throws Exception {
testFileRead();
testFileWrite();
testSocketStreams();
testSocketChannels();
}
private static void testFileRead() throws Exception {
printTestDescription(EVENT_FILE_READ, "RandomAccessFile, FileInputStream, Files.newInputStream and Files.newByteChannel");
File f1 = new File("testFileRead-1.bin");
writeRAF(f1);
File f2 = new File("testFileRead-2.bin");
writeFileStream(f2);
Path p = Path.of("testFileRead-3.bin");
writeFilesNew(p);
try (Recording r = new Recording()) {
r.enable(EVENT_FILE_READ).withStackTrace();
r.start();
readRAF(f1);
readFileStream(f2);
readFilesNew(p);
r.stop();
assertTopFrames(r, "readFilesNew", 1, "readRAF", 20, "readStream", 15);
}
}
private static void readFileStream(File f) throws Exception {
try (FileInputStream fis = new FileInputStream(f)) {
readStream(fis);
}
}
private static void writeFileStream(File f) throws Exception {
try (FileOutputStream fos = new FileOutputStream(f)) {
writeStream(fos);
}
}
private static void readFilesNew(Path p) throws Exception {
ByteBuffer b = ByteBuffer.allocateDirect(1000);
try (SeekableByteChannel channel = Files.newByteChannel(p)) {
channel.read(b); // 1
}
try (InputStream is = Files.newInputStream(p)) {
readStream(is);
}
}
private static void testFileWrite() throws Exception {
printTestDescription(EVENT_FILE_WRITE + ", " + EVENT_FILE_FORCE, "RandomAccessFile, FileInputStream, Files.newOutputStream and Files.newByteChanneland");
File f = new File("testFileWrite.bin");
try (Recording r = new Recording()) {
r.enable(EVENT_FILE_WRITE).withStackTrace();
r.enable(EVENT_FILE_FORCE).withStackTrace();
r.start();
writeRAF(f);
writeFileStream(f);
writeAsync(f);
writeFilesNew(f.toPath());
r.stop();
assertTopFrames(r, "writeFilesNew", 1, "writeRAF", 17, "writeStream", 6, "writeAsync", 1);
}
}
private static void writeFilesNew(Path p) throws Exception {
ByteBuffer b = ByteBuffer.allocateDirect(1000);
OpenOption[] options = { StandardOpenOption.CREATE, StandardOpenOption.WRITE };
try (SeekableByteChannel channel = Files.newByteChannel(p, options)) {
channel.write(b); // 1
}
try (OutputStream os = Files.newOutputStream(p, options)) {
writeStream(os);
}
}
private static void writeAsync(File file) throws Exception {
AsynchronousFileChannel channel = AsynchronousFileChannel.open(file.toPath(), READ, WRITE);
ByteBuffer[] buffers = createBuffers();
channel.force(true);
}
private static void writeStream(OutputStream os) throws Exception {
byte[] bytes = new byte[200];
os.write(67); // 1
os.write(bytes); // 2
os.write(bytes, 0, 1); // 3
}
private static void writeRAF(File file) throws Exception {
ByteBuffer[] buffers = createBuffers();
byte[] bytes = new byte[100];
try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
raf.writeUTF("o\n"); // 1
raf.writeUTF("hello"); // 2
raf.write(23); // 3
raf.write(bytes); // 4
raf.write(bytes, 0, 50); // 5
raf.writeBoolean(true); // 6
raf.writeByte(23); // 7
raf.writeBytes("hello"); // 8
raf.writeChar('h'); // 9
raf.writeChars("hello"); // 10
raf.writeDouble(76.0); // 11
raf.writeFloat(21.7f); // 12
raf.writeInt(4711); // 13
raf.writeLong(Long.MAX_VALUE); // 14
FileChannel fc = raf.getChannel();
fc.write(buffers[0]); // 15
fc.write(buffers); // 16
fc.force(true); // 17
}
}
private static void readStream(InputStream is) throws Exception {
byte[] bytes = new byte[10];
is.read(); // 1
is.read(bytes); // 2
is.read(bytes, 0, 3); // 3
is.readNBytes(2); // 4
is.readNBytes(bytes, 0, 1); // 5
byte[] leftOver = is.readAllBytes(); // 6, 7, 8 or 6, 7 for Files.newInputStream
if (leftOver.length < 1) {
throw new Exception("Expected some bytes to be read");
}
}
private static void readRAF(File file) throws Exception {
ByteBuffer[] buffers = createBuffers();
byte[] bytes = new byte[100];
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
raf.readLine(); // 1
raf.readUTF(); // 2, 3 (size and content)
raf.read(); // 4
raf.read(bytes); // 5
raf.read(bytes, 0, 0); // 6
raf.readBoolean(); // 7
raf.readByte(); // 8
raf.readChar(); // 9
raf.readDouble(); // 10
raf.readFloat(); // 11
raf.seek(0);
raf.readFully(bytes); // 12
raf.seek(0);
raf.readFully(bytes, 10, 10); // 13
raf.readInt(); // 14
raf.readLong(); // 15
raf.readShort(); // 16
raf.readUnsignedByte(); // 17
raf.readUnsignedShort(); // 18
FileChannel fc = raf.getChannel();
fc.read(buffers[0]); // 19
if (fc.read(buffers) < 1) { // 20
throw new Exception("Expected some bytes to be read");
};
}
}
private static void testSocketChannels() throws Exception {
printTestDescription(EVENT_SOCKET_READ + ", " + EVENT_SOCKET_WRITE, "SocketChannel and Socket adapters");
try (Recording r = new Recording()) {
try (ServerSocketChannel ssc = ServerSocketChannel.open()) {
ssc.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
r.enable(EVENT_SOCKET_READ).withStackTrace();
r.enable(EVENT_SOCKET_WRITE).withStackTrace();
r.start();
Thread readerThread = Thread.ofPlatform().start(() -> readSocketChannel(ssc));
writeSocketChannel(ssc);
readerThread.join();
r.stop();
assertTopFrames(r, "readSocket", 6, "readSocketChannel", 2, "writeSocket", 3, "writeSocketChannel", 2);
}
}
}
private static void readSocketChannel(ServerSocketChannel ssc) {
ByteBuffer[] buffers = createBuffers();
try (SocketChannel sc = ssc.accept()) {
sc.read(buffers[0]); // 1
sc.read(buffers); // 2
try (InputStream is = sc.socket().getInputStream()) {
readSocket(is);
}
} catch (Exception ioe) {
throw new RuntimeException(ioe);
}
}
private static void writeSocketChannel(ServerSocketChannel ssc) throws Exception {
ByteBuffer[] buffers = createBuffers();
try (SocketChannel sc = SocketChannel.open(ssc.getLocalAddress())) {
sc.write(buffers[0]); // 1
sc.write(buffers); // 2
try (OutputStream out = sc.socket().getOutputStream()) {
writeSocket(out);
}
}
}
private static void testSocketStreams() throws Exception {
printTestDescription(EVENT_SOCKET_READ + ", " + EVENT_SOCKET_WRITE, "SocketInputStream and SocketOutputStream");
try (ServerSocket serverSocket = new ServerSocket(0);
Socket client = new Socket("localhost", serverSocket.getLocalPort());
Socket server = serverSocket.accept();
OutputStream socketOut = client.getOutputStream();
InputStream socketIn = server.getInputStream();
Recording r = new Recording()) {
r.enable(EVENT_SOCKET_READ).withStackTrace();
r.enable(EVENT_SOCKET_WRITE).withStackTrace();
r.start();
Thread readerThread = Thread.ofPlatform().start(() -> readSocket(socketIn));
writeSocket(socketOut);
readerThread.join();
r.stop();
assertTopFrames(r, "readSocket", 6, "writeSocket", 3);
}
}
private static void writeSocket(OutputStream socketOut) throws Exception {
byte[] bytes = "hello, world!".getBytes();
socketOut.write(bytes); // 1
socketOut.write(4711); // 2
socketOut.write(bytes, 0, 3); // 3
}
private static void readSocket(InputStream socketIn) {
try {
byte[] bytes = new byte[100];
socketIn.read(); // 1
socketIn.read(bytes, 0, 3); // 2
socketIn.readNBytes(3); // 3, 4
socketIn.readNBytes(bytes, 0, 2); // 5
if (socketIn.read(bytes) < 1) { // 6
throw new RuntimeException("Expected some bytes to be read");
}
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
private static void assertTopFrames(Recording r, Object... frameCount) throws Exception {
TreeMap<String, Integer> expected = new TreeMap<>();
for (int i = 0; i < frameCount.length; i += 2) {
String method = TestIOTopFrame.class.getName() + "::" + frameCount[i];
Integer count = (Integer) frameCount[i + 1];
expected.put(method, count);
}
Path dumpFile = Path.of("test-top-frame-" + r.getId() + ".jfr");
r.dump(dumpFile);
List<RecordedEvent> events = RecordingFile.readAllEvents(dumpFile);
TreeMap<String, Integer> actual = new TreeMap<>();
for (RecordedEvent e : events) {
RecordedStackTrace st = e.getStackTrace();
RecordedMethod topMethod = st.getFrames().get(0).getMethod();
String methodName = topMethod.getType().getName() + "::" + topMethod.getName();
actual.merge(methodName, 1, Integer::sum);
}
printMap("Expected", expected);
printMap("Actual", actual);
if (!expected.equals(actual)) {
System.out.println(events);
throw new Exception("Top methods are not as expected");
}
Files.delete(dumpFile);
}
private static void printTestDescription(String eventNames, String components) {
String title = "Testing top frames for events: " + eventNames + " (" + components + ")";
System.out.println(title);
System.out.println("*".repeat(title.length()));
}
private static void printMap(String title, TreeMap<String, Integer> map) {
System.out.println(title + ":");
for (var entry : map.entrySet()) {
System.out.println(entry.getKey() + "\t" + entry.getValue());
}
System.out.println();
}
private static ByteBuffer[] createBuffers() {
byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
ByteBuffer buffer = ByteBuffer.wrap(data);
ByteBuffer[] buffers = new ByteBuffer[2];
buffers[0] = buffer.duplicate();
buffers[1] = buffer.duplicate();
return buffers;
}
}