From 491ecd423e84bab5aebd67923dba5135c3c5f483 Mon Sep 17 00:00:00 2001 From: erik_gahlin Date: Wed, 9 Jul 2025 07:46:19 +0200 Subject: [PATCH 1/5] Initial --- .../share/classes/sun/net/spi}/view.ini | 0 .../classes/jdk/jfr/events/FileReadEvent.java | 2 +- .../jdk/jfr/events/FileWriteEvent.java | 2 +- .../jdk/jfr/events/SocketReadEvent.java | 1 + .../jdk/jfr/events/SocketWriteEvent.java | 1 + .../jdk/jfr/internal/PlatformEventType.java | 25 +- test/jdk/jdk/jfr/event/io/TestIOTopFrame.java | 294 ++++++++++++++++++ 7 files changed, 309 insertions(+), 16 deletions(-) rename src/{jdk.jfr/share/classes/jdk/jfr/internal/query => java.base/share/classes/sun/net/spi}/view.ini (100%) create mode 100644 test/jdk/jdk/jfr/event/io/TestIOTopFrame.java diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini b/src/java.base/share/classes/sun/net/spi/view.ini similarity index 100% rename from src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini rename to src/java.base/share/classes/sun/net/spi/view.ini diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java index bd9926c08d38e..5ec112a59947b 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java @@ -38,7 +38,7 @@ @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.io.FileInputStream", "java.nio.channels.FileChannel", "java.io.DataInputStream", "java.io.InputStream", "java.io.RandomAccessFile", "sun.nio.ch.FileChannelImpl"}) @Throttle public final class FileReadEvent extends MirrorEvent { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java index e7861eef1b64b..c4837e56bbe2f 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java @@ -38,7 +38,7 @@ @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.io.FileOutputStream", "java.nio.channels.FileChannel", "java.io.DataOutputStream", "java.io.RandomAccessFile", "sun.nio.ch.FileChannelImpl"}) @Throttle public final class FileWriteEvent extends MirrorEvent { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java index b2cdee4f8dccf..d7f8b9bdad0b6 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java @@ -39,6 +39,7 @@ @Label("Socket Read") @Category("Java Application") @Description("Reading data from a socket") +@StackFilter({"java.io.InputStream", "java.nio.channels.SocketChannel", "java.net.Socket$SocketInputStream"}) @Throttle public final class SocketReadEvent extends MirrorEvent { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java index 661a4d1a68e2c..3ea297c50d887 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java @@ -38,6 +38,7 @@ @Label("Socket Write") @Category("Java Application") @Description("Writing data to a socket") +@StackFilter({"java.io.OutputStream", "java.nio.channels.SocketChannel", "java.net.Socket$SocketOutputStream"}) @Throttle public final class SocketWriteEvent extends MirrorEvent { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java index ff3e0238cf03b..82cf4c557109d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java @@ -95,18 +95,6 @@ private boolean isExceptionEvent() { 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,8 +104,17 @@ private int determineStackTraceOffset() { if (getModification() == Modification.TRACING) { return 5; } - if (isStaticCommit()) { - return 3; + switch (getName()) { + case Type.EVENT_NAME_PREFIX + "SocketRead" : + return 6; + case Type.EVENT_NAME_PREFIX + "SocketWrite" : + return 6; + case Type.EVENT_NAME_PREFIX + "FileRead" : + return 6; + case Type.EVENT_NAME_PREFIX + "FileWrite" : + return 6; + case Type.EVENT_NAME_PREFIX + "FileForce" : + return 5; } } return 3; diff --git a/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java b/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java new file mode 100644 index 0000000000000..33aeec2ee661f --- /dev/null +++ b/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java @@ -0,0 +1,294 @@ +/* + * 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.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +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 + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @run main/othervm jdk.jfr.event.io.TestIOTopFrame + */ +public class TestIOTopFrame { + + // Lines commented with a number indicate that they emit an event: + // The invocation of the 54following methods results in two events: + // RandomAccessFile::readUTF() + // SocketInputStream::readNBytes(...) + + public static void main(String... args) throws Exception { + testFileWrite(); + testFileRead(); + testSocketStreams(); + testSocketChannels(); + } + + private static void testSocketChannels() throws Exception { + try (Recording r = new Recording()) { + try (ServerSocketChannel ssc = ServerSocketChannel.open()) { + ssc.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + r.enable("jdk.SocketRead").withStackTrace(); + r.enable("jdk.SocketWrite").withStackTrace(); + r.start(); + Thread readerThread = Thread.ofPlatform().start(() -> readSocketChannel(ssc)); + writeSocketChannel(ssc); + readerThread.join(); + assertTopFrames(Map.of("readSocketChannel", 2, "writeSocketChannel", 2), r, "jdk.SocketRead and jdk.SocketWrite (SocketChannel)"); + } + } + } + + private static void writeSocketChannel(ServerSocketChannel ssc) throws IOException { + try (SocketChannel sc = SocketChannel.open(ssc.getLocalAddress())) { + ByteBuffer[] buffers = createBuffers(); + sc.write(buffers[0]); // 1 + sc.write(buffers); // 2 + } + } + + private static void readSocketChannel(ServerSocketChannel ssc) { + ByteBuffer[] buffers = createBuffers(); + try (SocketChannel sc = ssc.accept()) { + sc.read(buffers[0]); // 1 + sc.read(buffers); // 2 + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + + private static ByteBuffer[] createBuffers() { + ByteBuffer[] buffers = new ByteBuffer[2]; + buffers[0] = ByteBuffer.allocate(10); // 1 + buffers[1] = ByteBuffer.allocate(10); // 2 + return buffers; + } + + private static void testSocketStreams() throws Exception { + 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("jdk.SocketRead").withStackTrace(); + r.enable("jdk.SocketWrite").withStackTrace(); + byte[] bytes = "hello, world!".getBytes(); + r.start(); + writeSocket(socketOut, bytes); + readSocket(socketIn, bytes); + assertTopFrames(Map.of("readSocket", 6, "writeSocket", 3), r, "jdk.SocketRead and jdk.SocketWrite (SocketInputStream and SocketOutputStream)"); + } + } + + private static void readSocket(InputStream socketIn, byte[] bytes) throws IOException { + socketIn.read(); // 1 + socketIn.read(bytes, 0, 3); // 2 + socketIn.readNBytes(3); // 3, 4 + socketIn.readNBytes(bytes, 0, 2); // 5 + socketIn.read(bytes); // 6 + } + + private static void writeSocket(OutputStream socketOut, byte[] bytes) throws IOException { + socketOut.write(bytes); // 1 + socketOut.write(4711); // 2 + socketOut.write(bytes, 0, 3); // 3 + } + + private static void testFileRead() throws Exception { + File f1 = new File("testFileRead-1.bin"); + writeRAF(f1); + File f2 = new File("testFileRead-2.bin"); + writeStream(f2); + try (Recording r = new Recording()) { + r.enable("jdk.FileRead").withStackTrace(); + r.start(); + readRAF(f1); + readStream(f2); + r.stop(); + assertTopFrames(Map.of("readRAF", 20, "readStream", 6), r, "jdk.FileRead (RandomAccessFile and FileInputStream)"); + } + } + + private static void testFileWrite() throws Exception { + File f1 = new File("testFileWrite-1.bin"); + File f2 = new File("testFileWrite-2.bin"); + File f3 = new File("testFileWrite-3.bin"); + try (Recording r = new Recording()) { + r.enable("jdk.FileWrite").withStackTrace(); + r.enable("jdk.FileForce").withStackTrace(); + r.start(); + writeRAF(f1); + writeStream(f2); + writeAsync(f1); + r.stop(); + assertTopFrames(Map.of("writeRAF", 17, "writeStream", 3, "writeAsync", 1), r, "jdk.FileWrite and jdk.FileForce (RandomAccessFile and FileOutputStream)"); + } + } + + private static void writeAsync(File file) throws Exception { + AsynchronousFileChannel ch = AsynchronousFileChannel.open(file.toPath(), READ, WRITE); + ByteBuffer[] buffers = createBuffers(); + ch.force(true); + } + + private static void writeStream(File f) throws IOException { + byte[] bytes = new byte[2]; + try (FileOutputStream fos = new FileOutputStream(f)) { + fos.write(67); // 1 + fos.write(bytes); // 2 + fos.write(bytes, 0, 1); // 3 + } + } + + private static void writeRAF(File file) throws IOException { + 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(); + ByteBuffer[] buffers = createBuffers(); + fc.write(buffers[0]); // 15 + fc.write(buffers); // 16 + fc.force(true); // 17 + } + } + + private static void readStream(File f) throws IOException { + byte[] bytes = new byte[100]; + try (FileInputStream fis = new FileInputStream(f)) { + fis.read(); // 1 + fis.read(bytes); // 2 + fis.read(bytes, 0, 3); // 3 + fis.readNBytes(2); // 4 + fis.readNBytes(bytes, 0, 1); // 5 + fis.readAllBytes(); // 6 + } + } + + private static void readRAF(File file) throws IOException { + 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(); + ByteBuffer[] buffers = createBuffers(); + fc.read(buffers[0]); // 19 + fc.read(buffers); // 20 + } + } + + private static void assertTopFrames(Map map, Recording r, String testName) throws Exception { + TreeMap expected = new TreeMap<>(); + for (var e : map.entrySet()) { + expected.put(TestIOTopFrame.class.getName() + "::" + e.getKey(), e.getValue()); + } + Path recording = Path.of("test-top-frame-" + r.getId() + ".jfr"); + r.dump(recording); + List events = RecordingFile.readAllEvents(recording); + TreeMap 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); + } + String title = "Testing top frames for event " + testName; + System.out.println(title); + System.out.println("*".repeat(title.length())); + printMap("Expected", expected); + printMap("Actual", actual); + if (!expected.equals(actual)) { + System.out.println(events); + throw new Exception("Top methods not as expected"); + } + } + + private static void printMap(String title, TreeMap map) { + System.out.println(title + ":"); + for (var entry : map.entrySet()) { + System.out.println(entry.getKey() + "\t" + entry.getValue()); + } + System.out.println(); + } +} From 891df72ff2032472df657d240122b574e44c937b Mon Sep 17 00:00:00 2001 From: erik_gahlin Date: Wed, 9 Jul 2025 15:28:47 +0200 Subject: [PATCH 2/5] Fix whitespace --- test/jdk/jdk/jfr/event/io/TestIOTopFrame.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java b/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java index 33aeec2ee661f..d1f51549b4073 100644 --- a/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java +++ b/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java @@ -63,11 +63,10 @@ */ public class TestIOTopFrame { - // Lines commented with a number indicate that they emit an event: - // The invocation of the 54following methods results in two events: - // RandomAccessFile::readUTF() - // SocketInputStream::readNBytes(...) - + // Lines commented with a number indicate that they emit an event. + // The invocation of the following RandomAccessFile::readUTF() and + // SocketInputStream::readNBytes(...) result in two events. + // public static void main(String... args) throws Exception { testFileWrite(); testFileRead(); From 55a51f2b4612c3628a5a00d3cae35f59a8091fe3 Mon Sep 17 00:00:00 2001 From: erik_gahlin Date: Wed, 9 Jul 2025 17:39:11 +0200 Subject: [PATCH 3/5] Revert accidental move --- .../spi => jdk.jfr/share/classes/jdk/jfr/internal/query}/view.ini | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{java.base/share/classes/sun/net/spi => jdk.jfr/share/classes/jdk/jfr/internal/query}/view.ini (100%) diff --git a/src/java.base/share/classes/sun/net/spi/view.ini b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini similarity index 100% rename from src/java.base/share/classes/sun/net/spi/view.ini rename to src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini From c0e68b0a324bcadcb88cac4abe0c9b96f1d95191 Mon Sep 17 00:00:00 2001 From: erik_gahlin Date: Wed, 9 Jul 2025 17:46:17 +0200 Subject: [PATCH 4/5] Use switch expression --- .../jdk/jfr/internal/PlatformEventType.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java index 82cf4c557109d..945854fde4531 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java @@ -104,18 +104,14 @@ private int determineStackTraceOffset() { if (getModification() == Modification.TRACING) { return 5; } - switch (getName()) { - case Type.EVENT_NAME_PREFIX + "SocketRead" : - return 6; - case Type.EVENT_NAME_PREFIX + "SocketWrite" : - return 6; - case Type.EVENT_NAME_PREFIX + "FileRead" : - return 6; - case Type.EVENT_NAME_PREFIX + "FileWrite" : - return 6; - case Type.EVENT_NAME_PREFIX + "FileForce" : - return 5; - } + 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; } From eded9cdcd81021a23d0871d158363bc21cce54c4 Mon Sep 17 00:00:00 2001 From: erik_gahlin Date: Fri, 11 Jul 2025 11:19:09 +0200 Subject: [PATCH 5/5] Cleanup and socket adapters --- .../classes/jdk/jfr/events/FileReadEvent.java | 7 +- .../jdk/jfr/events/FileWriteEvent.java | 6 +- .../jdk/jfr/events/SocketReadEvent.java | 5 +- .../jdk/jfr/events/SocketWriteEvent.java | 5 +- test/jdk/jdk/jfr/event/io/TestIOTopFrame.java | 261 ++++++++++-------- 5 files changed, 171 insertions(+), 113 deletions(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java index 5ec112a59947b..0950a80d62593 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java @@ -38,7 +38,12 @@ @Label("File Read") @Category("Java Application") @Description("Reading data from a file") -@StackFilter({"java.io.FileInputStream", "java.nio.channels.FileChannel", "java.io.DataInputStream", "java.io.InputStream", "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.FileChannelImpl"}) @Throttle public final class FileReadEvent extends MirrorEvent { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java index c4837e56bbe2f..65391047c6973 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java @@ -38,7 +38,11 @@ @Label("File Write") @Category("Java Application") @Description("Writing data to a file") -@StackFilter({"java.io.FileOutputStream", "java.nio.channels.FileChannel", "java.io.DataOutputStream", "java.io.RandomAccessFile", "sun.nio.ch.FileChannelImpl"}) +@StackFilter({"java.nio.channels.FileChannel", + "java.io.DataOutputStream", + "java.io.FileOutputStream", + "java.io.RandomAccessFile", + "sun.nio.ch.FileChannelImpl"}) @Throttle public final class FileWriteEvent extends MirrorEvent { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java index d7f8b9bdad0b6..cb858d84e1857 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java @@ -39,7 +39,10 @@ @Label("Socket Read") @Category("Java Application") @Description("Reading data from a socket") -@StackFilter({"java.io.InputStream", "java.nio.channels.SocketChannel", "java.net.Socket$SocketInputStream"}) +@StackFilter({"java.io.InputStream", + "java.net.Socket$SocketInputStream", + "java.nio.channels.SocketChannel", + "sun.nio.ch.SocketInputStream"}) @Throttle public final class SocketReadEvent extends MirrorEvent { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java index 3ea297c50d887..c1ce3d63e876b 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java @@ -38,7 +38,10 @@ @Label("Socket Write") @Category("Java Application") @Description("Writing data to a socket") -@StackFilter({"java.io.OutputStream", "java.nio.channels.SocketChannel", "java.net.Socket$SocketOutputStream"}) +@StackFilter({"java.io.OutputStream", + "java.net.Socket$SocketOutputStream", + "java.nio.channels.SocketChannel", + "sun.nio.ch.SocketOutputStream"}) @Throttle public final class SocketWriteEvent extends MirrorEvent { diff --git a/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java b/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java index d1f51549b4073..ff39d66366acb 100644 --- a/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java +++ b/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java @@ -41,12 +41,16 @@ import java.nio.channels.FileChannel; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; +import java.nio.file.Files; import java.nio.file.Path; 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; @@ -56,134 +60,70 @@ /** * @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"; - // Lines commented with a number indicate that they emit an event. - // The invocation of the following RandomAccessFile::readUTF() and - // SocketInputStream::readNBytes(...) result in two events. - // public static void main(String... args) throws Exception { - testFileWrite(); testFileRead(); + testFileWrite(); testSocketStreams(); testSocketChannels(); } - private static void testSocketChannels() throws Exception { - try (Recording r = new Recording()) { - try (ServerSocketChannel ssc = ServerSocketChannel.open()) { - ssc.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); - r.enable("jdk.SocketRead").withStackTrace(); - r.enable("jdk.SocketWrite").withStackTrace(); - r.start(); - Thread readerThread = Thread.ofPlatform().start(() -> readSocketChannel(ssc)); - writeSocketChannel(ssc); - readerThread.join(); - assertTopFrames(Map.of("readSocketChannel", 2, "writeSocketChannel", 2), r, "jdk.SocketRead and jdk.SocketWrite (SocketChannel)"); - } - } - } - - private static void writeSocketChannel(ServerSocketChannel ssc) throws IOException { - try (SocketChannel sc = SocketChannel.open(ssc.getLocalAddress())) { - ByteBuffer[] buffers = createBuffers(); - sc.write(buffers[0]); // 1 - sc.write(buffers); // 2 - } - } - - private static void readSocketChannel(ServerSocketChannel ssc) { - ByteBuffer[] buffers = createBuffers(); - try (SocketChannel sc = ssc.accept()) { - sc.read(buffers[0]); // 1 - sc.read(buffers); // 2 - } catch (IOException ioe) { - ioe.printStackTrace(); - } - } - - private static ByteBuffer[] createBuffers() { - ByteBuffer[] buffers = new ByteBuffer[2]; - buffers[0] = ByteBuffer.allocate(10); // 1 - buffers[1] = ByteBuffer.allocate(10); // 2 - return buffers; - } - - private static void testSocketStreams() throws Exception { - 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("jdk.SocketRead").withStackTrace(); - r.enable("jdk.SocketWrite").withStackTrace(); - byte[] bytes = "hello, world!".getBytes(); - r.start(); - writeSocket(socketOut, bytes); - readSocket(socketIn, bytes); - assertTopFrames(Map.of("readSocket", 6, "writeSocket", 3), r, "jdk.SocketRead and jdk.SocketWrite (SocketInputStream and SocketOutputStream)"); - } - } - - private static void readSocket(InputStream socketIn, byte[] bytes) throws IOException { - socketIn.read(); // 1 - socketIn.read(bytes, 0, 3); // 2 - socketIn.readNBytes(3); // 3, 4 - socketIn.readNBytes(bytes, 0, 2); // 5 - socketIn.read(bytes); // 6 - } - - private static void writeSocket(OutputStream socketOut, byte[] bytes) throws IOException { - socketOut.write(bytes); // 1 - socketOut.write(4711); // 2 - socketOut.write(bytes, 0, 3); // 3 - } - private static void testFileRead() throws Exception { + printTestDescription(EVENT_FILE_READ, "RandomAccessFile and FileInputStream"); File f1 = new File("testFileRead-1.bin"); writeRAF(f1); File f2 = new File("testFileRead-2.bin"); writeStream(f2); try (Recording r = new Recording()) { - r.enable("jdk.FileRead").withStackTrace(); + r.enable(EVENT_FILE_READ).withStackTrace(); r.start(); readRAF(f1); readStream(f2); r.stop(); - assertTopFrames(Map.of("readRAF", 20, "readStream", 6), r, "jdk.FileRead (RandomAccessFile and FileInputStream)"); + assertTopFrames(r, "readRAF", 20, "readStream", 8); } } private static void testFileWrite() throws Exception { - File f1 = new File("testFileWrite-1.bin"); - File f2 = new File("testFileWrite-2.bin"); - File f3 = new File("testFileWrite-3.bin"); + printTestDescription(EVENT_FILE_WRITE + ", " + EVENT_FILE_FORCE, "RandomAccessFile and FileInputStream"); + File f = new File("testFileWrite.bin"); try (Recording r = new Recording()) { - r.enable("jdk.FileWrite").withStackTrace(); - r.enable("jdk.FileForce").withStackTrace(); + r.enable(EVENT_FILE_WRITE).withStackTrace(); + r.enable(EVENT_FILE_FORCE).withStackTrace(); r.start(); - writeRAF(f1); - writeStream(f2); - writeAsync(f1); + writeRAF(f); + writeStream(f); + writeAsync(f); r.stop(); - assertTopFrames(Map.of("writeRAF", 17, "writeStream", 3, "writeAsync", 1), r, "jdk.FileWrite and jdk.FileForce (RandomAccessFile and FileOutputStream)"); + assertTopFrames(r, "writeRAF", 17, "writeStream", 3, "writeAsync", 1); } } private static void writeAsync(File file) throws Exception { - AsynchronousFileChannel ch = AsynchronousFileChannel.open(file.toPath(), READ, WRITE); + AsynchronousFileChannel channel = AsynchronousFileChannel.open(file.toPath(), READ, WRITE); ByteBuffer[] buffers = createBuffers(); - ch.force(true); + channel.force(true); } - private static void writeStream(File f) throws IOException { - byte[] bytes = new byte[2]; + private static void writeStream(File f) throws Exception { + byte[] bytes = new byte[200]; try (FileOutputStream fos = new FileOutputStream(f)) { fos.write(67); // 1 fos.write(bytes); // 2 @@ -191,7 +131,8 @@ private static void writeStream(File f) throws IOException { } } - private static void writeRAF(File file) throws IOException { + 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 @@ -209,26 +150,29 @@ private static void writeRAF(File file) throws IOException { raf.writeInt(4711); // 13 raf.writeLong(Long.MAX_VALUE); // 14 FileChannel fc = raf.getChannel(); - ByteBuffer[] buffers = createBuffers(); fc.write(buffers[0]); // 15 fc.write(buffers); // 16 fc.force(true); // 17 } } - private static void readStream(File f) throws IOException { - byte[] bytes = new byte[100]; + private static void readStream(File f) throws Exception { + byte[] bytes = new byte[10]; try (FileInputStream fis = new FileInputStream(f)) { fis.read(); // 1 fis.read(bytes); // 2 fis.read(bytes, 0, 3); // 3 fis.readNBytes(2); // 4 fis.readNBytes(bytes, 0, 1); // 5 - fis.readAllBytes(); // 6 + byte[] leftOver = fis.readAllBytes(); // 6, 7, 8 + if (leftOver.length < 1) { + throw new Exception("Expected some bytes to be read"); + } } } - private static void readRAF(File file) throws IOException { + 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 @@ -251,20 +195,105 @@ private static void readRAF(File file) throws IOException { raf.readUnsignedByte(); // 17 raf.readUnsignedShort(); // 18 FileChannel fc = raf.getChannel(); - ByteBuffer[] buffers = createBuffers(); fc.read(buffers[0]); // 19 - fc.read(buffers); // 20 + if (fc.read(buffers) < 1) { // 20 + throw new Exception("Expected som 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 assertTopFrames(Map map, Recording r, String testName) throws Exception { + 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 expected = new TreeMap<>(); - for (var e : map.entrySet()) { - expected.put(TestIOTopFrame.class.getName() + "::" + e.getKey(), e.getValue()); + 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 recording = Path.of("test-top-frame-" + r.getId() + ".jfr"); - r.dump(recording); - List events = RecordingFile.readAllEvents(recording); + Path dumpFile = Path.of("test-top-frame-" + r.getId() + ".jfr"); + r.dump(dumpFile); + List events = RecordingFile.readAllEvents(dumpFile); TreeMap actual = new TreeMap<>(); for (RecordedEvent e : events) { RecordedStackTrace st = e.getStackTrace(); @@ -272,15 +301,20 @@ private static void assertTopFrames(Map map, Recording r, Strin String methodName = topMethod.getType().getName() + "::" + topMethod.getName(); actual.merge(methodName, 1, Integer::sum); } - String title = "Testing top frames for event " + testName; - System.out.println(title); - System.out.println("*".repeat(title.length())); + printMap("Expected", expected); printMap("Actual", actual); if (!expected.equals(actual)) { System.out.println(events); - throw new Exception("Top methods not as expected"); + 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 map) { @@ -290,4 +324,13 @@ private static void printMap(String title, TreeMap map) { } 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; + } }