diff --git a/src/java.base/share/classes/jdk/internal/event/SocketConnectEvent.java b/src/java.base/share/classes/jdk/internal/event/SocketConnectEvent.java new file mode 100644 index 0000000000000..c554349265839 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/event/SocketConnectEvent.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2024, 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 jdk.internal.event; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnixDomainSocketAddress; + +/** + * A JFR event for when a socket connection is established. This event is mirrored in + * {@code jdk.jfr.events.SocketConnectEvent} where the metadata for the event is + * provided with annotations. Some of the methods are replaced by generated + * methods when jfr is enabled. Note that the order of the arguments of the + * {@link #commit(long, long, String, String, int)} method must be the same as the + * order of the fields. + */ +public class SocketConnectEvent extends Event { + + // THE ORDER OF THE FOLLOWING FIELDS IS IMPORTANT! + // The order must match the argument order of the generated commit method. + public String host; + public String address; + public int port; + + /** + * Actually commit an event. The implementation is generated automatically. + * The order of the fields must be the same as the parameters in this method. + * {@code commit(..., String, String, int, long)} + * + * @param start timestamp of the start of the operation + * @param duration time in nanoseconds to complete the operation + * @param host remote host of the connection + * @param address remote address of the connection + * @param port remote port of the connection + */ + public static void commit(long start, long duration, String host, String address, int port) { + // Generated by JFR + } + + /** + * Determine if an event should be emitted. The duration of the operation + * must exceed some threshold in order to commit the event. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @param duration time in nanoseconds to complete the operation + * @return true if the event should be commited + */ + public static boolean shouldCommit(long duration) { + // Generated by JFR + return false; + } + + /** + * Determine if this kind of event is enabled. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @return true if event is enabled, false otherwise + */ + public static boolean enabled() { + // Generated by JFR + return false; + } + + /** + * Fetch the current timestamp in nanoseconds. This method is used + * to determine the start and end of an operation. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @return the current timestamp value + */ + public static long timestamp() { + // Generated by JFR + return 0L; + } + + /** + * Helper method to offer the data needed to potentially commit an event. + * The duration of the operation is computed using the current + * timestamp and the given start time. If the duration is meets + * or exceeds the configured value (determined by calling the generated method + * {@link #shouldCommit(long)}), an event will be emitted by calling + * {@link #commit(long, long, String, String, int)}. + * + * @param start the start time + * @param remote the address of the remote socket + */ + public static void offer(long start, SocketAddress remote) { + long duration = timestamp() - start; + if (shouldCommit(duration)) { + if (remote instanceof InetSocketAddress isa) { + commit(start, duration, isa.getHostString(), isa.getAddress().getHostAddress(), isa.getPort()); + } else if (remote instanceof UnixDomainSocketAddress udsa) { + String path = "[" + udsa.getPath().toString() + "]"; + commit(start, duration, "Unix domain socket", path, 0); + } + } + } + + /** + * Helper method to offer the data needed to potentially commit an event. + * The duration of the operation is computed using the current + * timestamp and the given start time. If the duration is meets + * or exceeds the configured value (determined by calling the generated method + * {@link #shouldCommit(long)}), an event will be emitted by calling + * {@code commit(long, long, String, String, int, long)} + * + * @param start timestamp of the start of the operation + * @param host remote host of the connection + * @param address remote address of the connection + * @param port remote port of the connection + */ + public static void offer(long start, String host, InetAddress address, int port) { + long duration = timestamp() - start; + if (shouldCommit(duration)) { + commit(start, duration, host, address.getHostAddress(), port); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/event/SocketConnectFailedEvent.java b/src/java.base/share/classes/jdk/internal/event/SocketConnectFailedEvent.java new file mode 100644 index 0000000000000..af9508ab1d502 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/event/SocketConnectFailedEvent.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2024, 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 jdk.internal.event; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnixDomainSocketAddress; + +/** + * A JFR event for when a socket connection cannot be established. This event is mirrored + * in {@code jdk.jfr.events.SocketConnectFailedEvent} where the metadata for the event is + * provided with annotations. Some of the methods are replaced by generated + * methods when jfr is enabled. Note that the order of the arguments of the + * {@link #commit(long, long, String, String, int, String)} method must be the same as + * the order of the fields. + */ +public class SocketConnectFailedEvent extends Event { + + // THE ORDER OF THE FOLLOWING FIELDS IS IMPORTANT! + // The order must match the argument order of the generated commit method. + public String host; + public String address; + public int port; + public String connectExceptionMessage; + + /** + * Actually commit an event. The implementation is generated automatically. + * The order of the fields must be the same as the parameters in this method. + * {@code commit(..., String, String, int, long)} + * + * @param start timestamp of the start of the operation + * @param duration time in nanoseconds to complete the operation + * @param host remote host of the connection + * @param address remote address of the connection + * @param port remote port of the connection + * @param connectEx the connect exception message + */ + public static void commit(long start, long duration, String host, String address, int port, String connectEx) { + // Generated by JFR + } + + /** + * Determine if an event should be emitted. The duration of the operation + * must exceed some threshold in order to commit the event. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @param duration time in nanoseconds to complete the operation + * @return true if the event should be commited + */ + public static boolean shouldCommit(long duration) { + // Generated by JFR + return false; + } + + /** + * Determine if this kind of event is enabled. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @return true if event is enabled, false otherwise + */ + public static boolean enabled() { + // Generated by JFR + return false; + } + + /** + * Fetch the current timestamp in nanoseconds. This method is used + * to determine the start and end of an operation. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @return the current timestamp value + */ + public static long timestamp() { + // Generated by JFR + return 0L; + } + + /** + * Helper method to offer the data needed to potentially commit an event. + * The duration of the operation is computed using the current + * timestamp and the given start time. If the duration is meets + * or exceeds the configured value (determined by calling the generated method + * {@link #shouldCommit(long)}), an event will be emitted by calling + * {@link #commit(long, long, String, String, int, String)}. + * + * @param start the start time + * @param remote the address of the remote socket + * @param connectEx the I/O exception thrown + */ + public static void offer(long start, SocketAddress remote, IOException connectEx) { + long duration = timestamp() - start; + if (shouldCommit(duration)) { + String msg = connectEx.toString(); + if (remote instanceof InetSocketAddress isa) { + commit(start, duration, isa.getHostString(), isa.getAddress().getHostAddress(), isa.getPort(), msg); + } else if (remote instanceof UnixDomainSocketAddress udsa) { + String path = "[" + udsa.getPath().toString() + "]"; + commit(start, duration, "Unix domain socket", path, 0, msg); + } + } + } + + /** + * Helper method to offer the data needed to potentially commit an event. + * The duration of the operation is computed using the current + * timestamp and the given start time. If the duration is meets + * or exceeds the configured value (determined by calling the generated method + * {@link #shouldCommit(long)}), an event will be emitted by calling + * {@code commit(long, long, String, String, int, long)} + * + * @param start timestamp of the start of the operation + * @param host remote host of the connection + * @param address remote address of the connection + * @param port remote port of the connection + * @param connectEx the I/O exception thrown + */ + public static void offer(long start, String host, InetAddress address, int port, IOException connectEx) { + long duration = timestamp() - start; + if (shouldCommit(duration)) { + commit(start, duration, host, address.getHostAddress(), port, connectEx.toString()); + } + } +} diff --git a/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java index 7c64d6721e2c7..71ee6c69d07d5 100644 --- a/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java +++ b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java @@ -53,6 +53,8 @@ import jdk.internal.access.JavaIOFileDescriptorAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.event.SocketConnectEvent; +import jdk.internal.event.SocketConnectFailedEvent; import jdk.internal.ref.CleanerFactory; import sun.net.ConnectionResetException; import sun.net.NetHooks; @@ -563,6 +565,9 @@ protected void connect(SocketAddress remote, int millis) throws IOException { address = InetAddress.getLocalHost(); int port = isa.getPort(); + long connectStart = 0L; + IOException connectEx = null; + ReentrantLock connectLock = readLock; try { connectLock.lock(); @@ -571,6 +576,7 @@ protected void connect(SocketAddress remote, int millis) throws IOException { FileDescriptor fd = beginConnect(address, port); try { configureNonBlockingIfNeeded(fd, millis > 0); + connectStart = SocketConnectEvent.timestamp(); int n = Net.connect(fd, address, port); if (n > 0) { // connection established @@ -600,14 +606,28 @@ protected void connect(SocketAddress remote, int millis) throws IOException { } catch (IOException ioe) { close(); if (ioe instanceof SocketTimeoutException) { - throw ioe; + connectEx = ioe; } else if (ioe instanceof InterruptedIOException) { assert Thread.currentThread().isVirtual(); - throw new SocketException("Closed by interrupt"); + connectEx = new SocketException("Closed by interrupt"); } else { - throw SocketExceptions.of(ioe, isa); + connectEx = SocketExceptions.of(ioe, isa); + } + } + + // record JFR event + if (connectStart != 0L) { + String hostname = isa.getHostString(); + if (connectEx == null && SocketConnectEvent.enabled()) { + SocketConnectEvent.offer(connectStart, hostname , address, port); + } else if (connectEx != null && SocketConnectFailedEvent.enabled()) { + SocketConnectFailedEvent.offer(connectStart, hostname, address, port, connectEx); } } + + if (connectEx != null) { + throw connectEx; + } } @Override diff --git a/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java b/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java index 893bd17ceed89..9d61cceff2e18 100644 --- a/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java +++ b/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java @@ -59,6 +59,8 @@ import static java.net.StandardProtocolFamily.INET6; import static java.net.StandardProtocolFamily.UNIX; +import jdk.internal.event.SocketConnectEvent; +import jdk.internal.event.SocketConnectFailedEvent; import jdk.internal.event.SocketReadEvent; import jdk.internal.event.SocketWriteEvent; import sun.net.ConnectionResetException; @@ -132,6 +134,10 @@ class SocketChannelImpl // preserve the semantics of blocking operations. private volatile boolean forcedNonBlocking; + // JFR support, start time of non-blocking connect + private long nonBlockingConnectStart; + + // -- End of fields protected by stateLock SocketChannelImpl(SelectorProvider sp) throws IOException { @@ -846,7 +852,7 @@ public boolean isConnectionPending() { /** * Marks the beginning of a connect operation that might block. * @param blocking true if configured blocking - * @param isa the remote address + * @param sa the remote socket address * @throws ClosedChannelException if the channel is closed * @throws AlreadyConnectedException if already connected * @throws ConnectionPendingException is a connection is pending @@ -934,6 +940,11 @@ private SocketAddress checkRemote(SocketAddress sa) { @Override public boolean connect(SocketAddress remote) throws IOException { SocketAddress sa = checkRemote(remote); + + boolean connected = false; + long connectStart = 0L; + IOException connectEx = null; + try { readLock.lock(); try { @@ -941,10 +952,10 @@ public boolean connect(SocketAddress remote) throws IOException { try { ensureOpen(); boolean blocking = isBlocking(); - boolean connected = false; try { beginConnect(blocking, sa); configureSocketNonBlockingIfVirtualThread(); + connectStart = SocketConnectEvent.timestamp(); int n; if (isUnixSocket()) { n = UnixDomainSockets.connect(fd, sa); @@ -961,11 +972,13 @@ public boolean connect(SocketAddress remote) throws IOException { polled = Net.pollConnectNow(fd); } connected = polled && isOpen(); + } else { + // non-blocking and not connected + this.nonBlockingConnectStart = connectStart; } } finally { endConnect(blocking, connected); } - return connected; } finally { writeLock.unlock(); } @@ -975,7 +988,23 @@ public boolean connect(SocketAddress remote) throws IOException { } catch (IOException ioe) { // connect failed, close the channel close(); - throw SocketExceptions.of(ioe, sa); + connectEx = SocketExceptions.of(ioe, sa); + } + + // record JFR event + if (connectStart != 0L) { + if (connected && SocketConnectEvent.enabled()) { + SocketConnectEvent.offer(connectStart, sa); + } else if (connectEx != null && SocketConnectFailedEvent.enabled()) { + SocketConnectFailedEvent.offer(connectStart, sa, connectEx); + } + } + + if (connectEx == null) { + return connected; + } else { + assert !connected; + throw connectEx; } } @@ -1029,6 +1058,9 @@ private void endFinishConnect(boolean blocking, boolean completed) @Override public boolean finishConnect() throws IOException { + long connectStart = 0L; + boolean connected = false; + IOException connectEx = null; try { readLock.lock(); try { @@ -1037,12 +1069,11 @@ public boolean finishConnect() throws IOException { // no-op if already connected if (isConnected()) return true; - ensureOpen(); boolean blocking = isBlocking(); - boolean connected = false; try { beginFinishConnect(blocking); + connectStart = this.nonBlockingConnectStart; boolean polled = Net.pollConnectNow(fd); if (blocking) { while (!polled && isOpen()) { @@ -1055,7 +1086,6 @@ public boolean finishConnect() throws IOException { endFinishConnect(blocking, connected); } assert (blocking && connected) ^ !blocking; - return connected; } finally { writeLock.unlock(); } @@ -1065,7 +1095,23 @@ public boolean finishConnect() throws IOException { } catch (IOException ioe) { // connect failed, close the channel close(); - throw SocketExceptions.of(ioe, remoteAddress); + connectEx = SocketExceptions.of(ioe, remoteAddress); + } + + // record JFR event + if (connectStart != 0L) { + if (connected && SocketConnectEvent.enabled()) { + SocketConnectEvent.offer(connectStart, remoteAddress()); + } else if (connectEx != null && SocketConnectFailedEvent.enabled()) { + SocketConnectFailedEvent.offer(connectStart, remoteAddress(), connectEx); + } + } + + if (connectEx != null) { + assert !connected; + throw connectEx; + } else { + return connected; } } @@ -1289,6 +1335,9 @@ private boolean finishTimedConnect(long nanos) throws IOException { */ void blockingConnect(SocketAddress remote, long nanos) throws IOException { SocketAddress sa = checkRemote(remote); + + long connectStart = 0L; + IOException connectEx = null; try { readLock.lock(); try { @@ -1302,6 +1351,7 @@ void blockingConnect(SocketAddress remote, long nanos) throws IOException { // change socket to non-blocking lockedConfigureBlocking(false); try { + connectStart = SocketConnectEvent.timestamp(); int n; if (isUnixSocket()) { n = UnixDomainSockets.connect(fd, sa); @@ -1325,7 +1375,20 @@ void blockingConnect(SocketAddress remote, long nanos) throws IOException { } catch (IOException ioe) { // connect failed, close the channel close(); - throw SocketExceptions.of(ioe, sa); + connectEx = SocketExceptions.of(ioe, sa); + } + + // record JFR event + if (connectStart != 0L) { + if (connectEx == null && SocketConnectEvent.enabled()) { + SocketConnectEvent.offer(connectStart, sa); + } else if (connectEx != null && SocketConnectFailedEvent.enabled()) { + SocketConnectFailedEvent.offer(connectStart, sa, connectEx); + } + } + + if (connectEx != null) { + throw connectEx; } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketConnectEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketConnectEvent.java new file mode 100644 index 0000000000000..d6058d1ececf6 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketConnectEvent.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, 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 jdk.jfr.events; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.internal.MirrorEvent; +import jdk.jfr.internal.Type; + +@Name(Type.EVENT_NAME_PREFIX + "SocketConnect") +@Label("Socket Connect") +@Category("Java Application") +@Description("Socket connection established") +public class SocketConnectEvent extends MirrorEvent { + + @Label("Remote Host") + public String host; + + @Label("Remote Address") + public String address; + + @Label("Remote Port") + public int port; +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketConnectFailedEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketConnectFailedEvent.java new file mode 100644 index 0000000000000..751e9193b2d7f --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketConnectFailedEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, 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 jdk.jfr.events; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.internal.MirrorEvent; +import jdk.jfr.internal.Type; + +@Name(Type.EVENT_NAME_PREFIX + "SocketConnectFailed") +@Label("Socket Connect Failed") +@Category("Java Application") +@Description("Socket connection could not be established") +public class SocketConnectFailedEvent extends MirrorEvent { + + @Label("Remote Host") + public String host; + + @Label("Remote Address") + public String address; + + @Label("Remote Port") + public int port; + + @Label("Connect Exception Message") + public String connectExceptionMessage; +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java index d0d186e2479d0..85b2f6d9baebd 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java @@ -62,6 +62,8 @@ public final class JDKEvents { jdk.internal.event.SecurityPropertyModificationEvent.class, jdk.internal.event.SecurityProviderServiceEvent.class, jdk.internal.event.SerializationMisdeclarationEvent.class, + jdk.internal.event.SocketConnectEvent.class, + jdk.internal.event.SocketConnectFailedEvent.class, jdk.internal.event.SocketReadEvent.class, jdk.internal.event.SocketWriteEvent.class, jdk.internal.event.ThreadSleepEvent.class, diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java index 48dc0d22cea3a..c3c65cdc91830 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java @@ -38,6 +38,8 @@ import jdk.jfr.events.SecurityPropertyModificationEvent; import jdk.jfr.events.SecurityProviderServiceEvent; import jdk.jfr.events.SerializationMisdeclarationEvent; +import jdk.jfr.events.SocketConnectEvent; +import jdk.jfr.events.SocketConnectFailedEvent; import jdk.jfr.events.SocketReadEvent; import jdk.jfr.events.SocketWriteEvent; import jdk.jfr.events.TLSHandshakeEvent; @@ -65,6 +67,8 @@ final class MirrorEvents { register("jdk.internal.event.SecurityPropertyModificationEvent", SecurityPropertyModificationEvent.class); register("jdk.internal.event.SecurityProviderServiceEvent", SecurityProviderServiceEvent.class); register("jdk.internal.event.SerializationMisdeclarationEvent", SerializationMisdeclarationEvent.class); + register("jdk.internal.event.SocketConnectEvent", SocketConnectEvent.class); + register("jdk.internal.event.SocketConnectFailedEvent", SocketConnectFailedEvent.class); register("jdk.internal.event.SocketReadEvent", SocketReadEvent.class); register("jdk.internal.event.SocketWriteEvent", SocketWriteEvent.class); register("jdk.internal.event.ThreadSleepEvent", ThreadSleepEvent.class); diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index 57016a9bdd03b..64aaf846dbd6b 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -738,6 +738,18 @@ 20 ms + + true + true + 20 ms + + + + true + true + 20 ms + + true true diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index 1df3af7475f3e..4befab2994db0 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -738,6 +738,18 @@ 10 ms + + true + true + 10 ms + + + + true + true + 10 ms + + true true diff --git a/test/jdk/jdk/jfr/event/io/IOEvent.java b/test/jdk/jdk/jfr/event/io/IOEvent.java index a8392e4d35a20..174bb238800e3 100644 --- a/test/jdk/jdk/jfr/event/io/IOEvent.java +++ b/test/jdk/jdk/jfr/event/io/IOEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, 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 @@ -73,13 +73,15 @@ public int hashCode() { public static final String EVENT_FILE_FORCE = EventNames.FileForce; public static final String EVENT_FILE_READ = EventNames.FileRead; public static final String EVENT_FILE_WRITE = EventNames.FileWrite; + public static final String EVENT_SOCKET_CONNECT = EventNames.SocketConnect; + public static final String EVENT_SOCKET_CONNECT_FAILED = EventNames.SocketConnectFailed; public static final String EVENT_SOCKET_READ = EventNames.SocketRead; public static final String EVENT_SOCKET_WRITE = EventNames.SocketWrite; - public enum EventType { UnknownEvent, FileForce, FileRead, FileWrite, SocketRead, SocketWrite } + public enum EventType { UnknownEvent, FileForce, FileRead, FileWrite, SocketConnect, SocketConnectFailed, SocketRead, SocketWrite } private static final String[] eventPaths = { - EVENT_UNKNOWN, EVENT_FILE_FORCE, EVENT_FILE_READ, EVENT_FILE_WRITE, EVENT_SOCKET_READ, EVENT_SOCKET_WRITE + EVENT_UNKNOWN, EVENT_FILE_FORCE, EVENT_FILE_READ, EVENT_FILE_WRITE, EVENT_SOCKET_CONNECT, EVENT_SOCKET_CONNECT_FAILED, EVENT_SOCKET_READ, EVENT_SOCKET_WRITE }; public static boolean isWriteEvent(EventType eventType) { @@ -94,6 +96,15 @@ public static boolean isFileEvent(EventType eventType) { return (eventType == EventType.FileForce || eventType == EventType.FileWrite || eventType == EventType.FileRead); } + public static IOEvent createSocketConnectEvent(Socket s) { + return new IOEvent(Thread.currentThread().getName(), EventType.SocketConnect, 0, getAddress(s), false); + } + + + public static IOEvent createSocketConnectFailedEvent(Socket s) { + return new IOEvent(Thread.currentThread().getName(), EventType.SocketConnectFailed, 0, getAddress(s), false); + } + public static IOEvent createSocketWriteEvent(long size, Socket s) { if (size < 0) { size = 0; diff --git a/test/jdk/jdk/jfr/event/io/IOHelper.java b/test/jdk/jdk/jfr/event/io/IOHelper.java index a8dbe940b03a3..bfa666857b40d 100644 --- a/test/jdk/jdk/jfr/event/io/IOHelper.java +++ b/test/jdk/jdk/jfr/event/io/IOHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, 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 @@ -26,14 +26,20 @@ import static jdk.test.lib.Asserts.assertEquals; import static jdk.test.lib.Asserts.assertTrue; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; +import jdk.jfr.Recording; import jdk.jfr.event.io.IOEvent.EventType; import jdk.jfr.consumer.RecordedEvent; +import jdk.test.lib.Asserts; import jdk.test.lib.jfr.Events; @@ -130,4 +136,56 @@ private static void logEvents(List actualEvents, List expected } } + public interface ConnectExceptionMaker { + /** + * Implementation should attempt to connect to the given address, which + * should cause an exception to be generated. + * @return the exception generated, or null if the connection was + * unexpectedly successful. + * @throws Throwable if something else failed + */ + IOException generateConnectException(SocketAddress addr) throws Throwable; + } + + /** + * Attempt to test JFR events for an exception condition while attempting to connect + * a socket. The given function attempts to make the connection which we would like to + * fail so we can inspect the associated JFR event. To do this a range of IANA reserved + * ports are used which it is expected will be unused. + * + * @param func an implementation of a connection attempt + * @throws Throwable + */ + public static void testConnectException(ConnectExceptionMaker func) throws Throwable { + InetAddress lb = InetAddress.getLoopbackAddress(); + boolean completed = false; + for (int port = 225; (completed == false) && (port <= 241); ++port) { + completed = testConnectExceptionOnPort(new InetSocketAddress(lb, port), func); + } + if (! completed) + throw new Exception("Unable to setup connect exception"); + } + + private static boolean testConnectExceptionOnPort(SocketAddress addr, ConnectExceptionMaker func) throws Throwable { + try (Recording recording = new Recording()) { + recording.enable(IOEvent.EVENT_SOCKET_CONNECT_FAILED); + recording.start(); + + // try to connect to a port we expect to be unused + // to generate an exception + IOException connectException = func.generateConnectException(addr); + if (connectException == null) + return false; + + recording.stop(); + List events = Events.fromRecording(recording); + Asserts.assertEquals(1, events.size()); + RecordedEvent event = events.get(0); + Asserts.assertEquals(IOEvent.EVENT_SOCKET_CONNECT_FAILED, event.getEventType().getName()); + Asserts.assertNotNull(connectException); + String eventMessage = event.getString("connectExceptionMessage"); + Asserts.assertEquals(eventMessage, connectException.toString()); + return true; + } + } } diff --git a/test/jdk/jdk/jfr/event/io/TestSocketAdapterEvents.java b/test/jdk/jdk/jfr/event/io/TestSocketAdapterEvents.java index 9ac57b839fb2c..7eef7734444dd 100644 --- a/test/jdk/jdk/jfr/event/io/TestSocketAdapterEvents.java +++ b/test/jdk/jdk/jfr/event/io/TestSocketAdapterEvents.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, 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 @@ -31,10 +31,9 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; -import java.nio.ByteBuffer; +import java.net.SocketAddress; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; -import java.time.Duration; import java.util.ArrayList; import java.util.List; @@ -65,13 +64,15 @@ private synchronized void addExpectedEvent(IOEvent event) { public static void main(String[] args) throws Throwable { new TestSocketAdapterEvents().test(); + IOHelper.testConnectException(TestSocketAdapterEvents::makeConnectException); } - public void test() throws Throwable { + private void test() throws Throwable { try (Recording recording = new Recording()) { try (ServerSocketChannel ssc = ServerSocketChannel.open()) { - recording.enable(IOEvent.EVENT_SOCKET_READ).withThreshold(Duration.ofMillis(0)); - recording.enable(IOEvent.EVENT_SOCKET_WRITE).withThreshold(Duration.ofMillis(0)); + recording.enable(IOEvent.EVENT_SOCKET_CONNECT); + recording.enable(IOEvent.EVENT_SOCKET_READ); + recording.enable(IOEvent.EVENT_SOCKET_WRITE); recording.start(); InetAddress lb = InetAddress.getLoopbackAddress(); @@ -85,36 +86,38 @@ public void xrun() throws IOException { InputStream is = s.getInputStream()) { int readInt = is.read(); - assertEquals(readInt, writeInt, "Wrong readInt"); + assertEquals(writeInt, readInt, "Wrong readInt"); addExpectedEvent(IOEvent.createSocketReadEvent(1, s)); int bytesRead = is.read(bs, 0, 3); - assertEquals(bytesRead, 3, "Wrong bytesRead partial buffer"); + assertEquals(3, bytesRead, "Wrong bytesRead partial buffer"); addExpectedEvent(IOEvent.createSocketReadEvent(bytesRead, s)); bytesRead = is.read(bs); - assertEquals(bytesRead, writeBuf.length, "Wrong bytesRead full buffer"); + assertEquals(writeBuf.length, bytesRead, "Wrong bytesRead full buffer"); addExpectedEvent(IOEvent.createSocketReadEvent(bytesRead, s)); // Try to read more, but writer have closed. Should // get EOF. readInt = is.read(); - assertEquals(readInt, -1, "Wrong readInt at EOF"); + assertEquals(-1, readInt, "Wrong readInt at EOF"); addExpectedEvent(IOEvent.createSocketReadEvent(-1, s)); } } }); readerThread.start(); - try (SocketChannel sc = SocketChannel.open(ssc.getLocalAddress()); - Socket s = sc.socket(); OutputStream os = s.getOutputStream()) { - - os.write(writeInt); - addExpectedEvent(IOEvent.createSocketWriteEvent(1, s)); - os.write(writeBuf, 0, 3); - addExpectedEvent(IOEvent.createSocketWriteEvent(3, s)); - os.write(writeBuf); - addExpectedEvent(IOEvent.createSocketWriteEvent(writeBuf.length, s)); + try (SocketChannel sc = SocketChannel.open(); Socket s = sc.socket()) { + s.connect(ssc.getLocalAddress()); + addExpectedEvent(IOEvent.createSocketConnectEvent(s)); + try (OutputStream os = s.getOutputStream()) { + os.write(writeInt); + addExpectedEvent(IOEvent.createSocketWriteEvent(1, s)); + os.write(writeBuf, 0, 3); + addExpectedEvent(IOEvent.createSocketWriteEvent(3, s)); + os.write(writeBuf); + addExpectedEvent(IOEvent.createSocketWriteEvent(writeBuf.length, s)); + } } readerThread.joinAndThrow(); @@ -124,4 +127,15 @@ public void xrun() throws IOException { } } } + + private static IOException makeConnectException(SocketAddress addr) throws Throwable { + IOException connectException = null; + try (SocketChannel sc = SocketChannel.open()) { + Socket s = sc.socket(); + s.connect(addr); + } catch (IOException ioe) { + connectException = ioe; + } + return connectException; + } } diff --git a/test/jdk/jdk/jfr/event/io/TestSocketChannelEvents.java b/test/jdk/jdk/jfr/event/io/TestSocketChannelEvents.java index fc045e55caa6e..9455487d404c8 100644 --- a/test/jdk/jdk/jfr/event/io/TestSocketChannelEvents.java +++ b/test/jdk/jdk/jfr/event/io/TestSocketChannelEvents.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, 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 @@ -28,10 +28,10 @@ import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; -import java.time.Duration; import java.util.ArrayList; import java.util.List; @@ -61,13 +61,17 @@ private synchronized void addExpectedEvent(IOEvent event) { public static void main(String[] args) throws Throwable { new TestSocketChannelEvents().test(); + new TestSocketChannelEvents().testNonBlockingConnect(); + IOHelper.testConnectException(TestSocketChannelEvents::makeBlockingConnectException); + IOHelper.testConnectException(TestSocketChannelEvents::makeNonBlockingConnectException); } - public void test() throws Throwable { + private void test() throws Throwable { try (Recording recording = new Recording()) { try (ServerSocketChannel ssc = ServerSocketChannel.open()) { - recording.enable(IOEvent.EVENT_SOCKET_READ).withThreshold(Duration.ofMillis(0)); - recording.enable(IOEvent.EVENT_SOCKET_WRITE).withThreshold(Duration.ofMillis(0)); + recording.enable(IOEvent.EVENT_SOCKET_CONNECT); + recording.enable(IOEvent.EVENT_SOCKET_READ); + recording.enable(IOEvent.EVENT_SOCKET_WRITE); recording.start(); InetAddress lb = InetAddress.getLoopbackAddress(); @@ -80,13 +84,13 @@ public void xrun() throws IOException { ByteBuffer bufB = ByteBuffer.allocate(bufSizeB); try (SocketChannel sc = ssc.accept()) { int readSize = sc.read(bufA); - assertEquals(readSize, bufSizeA, "Wrong readSize bufA"); + assertEquals(bufSizeA, readSize, "Wrong readSize bufA"); addExpectedEvent(IOEvent.createSocketReadEvent(bufSizeA, sc.socket())); bufA.clear(); bufA.limit(1); readSize = (int) sc.read(new ByteBuffer[] { bufA, bufB }); - assertEquals(readSize, 1 + bufSizeB, "Wrong readSize 1+bufB"); + assertEquals(1 + bufSizeB, readSize, "Wrong readSize 1+bufB"); addExpectedEvent(IOEvent.createSocketReadEvent(readSize, sc.socket())); // We try to read, but client have closed. Should @@ -94,7 +98,7 @@ public void xrun() throws IOException { bufA.clear(); bufA.limit(1); readSize = sc.read(bufA); - assertEquals(readSize, -1, "Wrong readSize at EOF"); + assertEquals(-1, readSize, "Wrong readSize at EOF"); addExpectedEvent(IOEvent.createSocketReadEvent(-1, sc.socket())); } } @@ -102,6 +106,7 @@ public void xrun() throws IOException { readerThread.start(); try (SocketChannel sc = SocketChannel.open(ssc.getLocalAddress())) { + addExpectedEvent(IOEvent.createSocketConnectEvent(sc.socket())); ByteBuffer bufA = ByteBuffer.allocateDirect(bufSizeA); ByteBuffer bufB = ByteBuffer.allocateDirect(bufSizeB); for (int i = 0; i < bufSizeA; ++i) { @@ -119,7 +124,7 @@ public void xrun() throws IOException { bufA.clear(); bufA.limit(1); int bytesWritten = (int) sc.write(new ByteBuffer[] { bufA, bufB }); - assertEquals(bytesWritten, 1 + bufSizeB, "Wrong bytesWritten 1+bufB"); + assertEquals(1 + bufSizeB, bytesWritten, "Wrong bytesWritten 1+bufB"); addExpectedEvent(IOEvent.createSocketWriteEvent(bytesWritten, sc.socket())); } @@ -130,4 +135,58 @@ public void xrun() throws IOException { } } } + + private void testNonBlockingConnect() throws Throwable { + try (Recording recording = new Recording()) { + try (ServerSocketChannel ssc = ServerSocketChannel.open()) { + recording.enable(IOEvent.EVENT_SOCKET_CONNECT); + recording.start(); + + InetAddress lb = InetAddress.getLoopbackAddress(); + ssc.bind(new InetSocketAddress(lb, 0)); + SocketAddress addr = ssc.getLocalAddress(); + + try (SocketChannel sc = SocketChannel.open()) { + sc.configureBlocking(false); + sc.connect(addr); + try (SocketChannel serverSide = ssc.accept()) { + while (! sc.finishConnect()) { + Thread.sleep(1); + } + } + addExpectedEvent(IOEvent.createSocketConnectEvent(sc.socket())); + } + + recording.stop(); + List events = Events.fromRecording(recording); + IOHelper.verifyEquals(events, expectedEvents); + } + } + } + + private static IOException makeBlockingConnectException(SocketAddress addr) throws Throwable { + IOException connectException = null; + try (SocketChannel sc = SocketChannel.open(addr)) { + } catch (IOException ioe) { + connectException = ioe; + } + return connectException; + } + + private static IOException makeNonBlockingConnectException(SocketAddress addr) throws Throwable { + IOException connectException = null; + try (SocketChannel sc = SocketChannel.open()) { + sc.configureBlocking(false); + try { + boolean connected = sc.connect(addr); + while (!connected) { + Thread.sleep(10); + connected = sc.finishConnect(); + } + } catch (IOException ioe) { + connectException = ioe; + } + } + return connectException; + } } diff --git a/test/jdk/jdk/jfr/event/io/TestSocketEvents.java b/test/jdk/jdk/jfr/event/io/TestSocketEvents.java index d73c5010cf7d0..3bbe8b6cd8164 100644 --- a/test/jdk/jdk/jfr/event/io/TestSocketEvents.java +++ b/test/jdk/jdk/jfr/event/io/TestSocketEvents.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, 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 @@ -32,7 +32,7 @@ import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; -import java.time.Duration; +import java.net.SocketAddress; import java.util.ArrayList; import java.util.List; @@ -63,13 +63,15 @@ private synchronized void addExpectedEvent(IOEvent event) { public static void main(String[] args) throws Throwable { new TestSocketEvents().test(); + IOHelper.testConnectException(TestSocketEvents::makeConnectException); } private void test() throws Throwable { try (Recording recording = new Recording()) { try (ServerSocket ss = new ServerSocket()) { - recording.enable(IOEvent.EVENT_SOCKET_READ).withThreshold(Duration.ofMillis(0)); - recording.enable(IOEvent.EVENT_SOCKET_WRITE).withThreshold(Duration.ofMillis(0)); + recording.enable(IOEvent.EVENT_SOCKET_CONNECT); + recording.enable(IOEvent.EVENT_SOCKET_READ); + recording.enable(IOEvent.EVENT_SOCKET_WRITE); recording.start(); InetAddress lb = InetAddress.getLoopbackAddress(); @@ -81,21 +83,21 @@ public void xrun() throws IOException { byte[] bs = new byte[4]; try (Socket s = ss.accept(); InputStream is = s.getInputStream()) { int readInt = is.read(); - assertEquals(readInt, writeInt, "Wrong readInt"); + assertEquals(writeInt, readInt, "Wrong readInt"); addExpectedEvent(IOEvent.createSocketReadEvent(1, s)); int bytesRead = is.read(bs, 0, 3); - assertEquals(bytesRead, 3, "Wrong bytesRead partial buffer"); + assertEquals(3, bytesRead, "Wrong bytesRead partial buffer"); addExpectedEvent(IOEvent.createSocketReadEvent(bytesRead, s)); bytesRead = is.read(bs); - assertEquals(bytesRead, writeBuf.length, "Wrong bytesRead full buffer"); + assertEquals(writeBuf.length, bytesRead, "Wrong bytesRead full buffer"); addExpectedEvent(IOEvent.createSocketReadEvent(bytesRead, s)); // Try to read more, but writer have closed. Should // get EOF. readInt = is.read(); - assertEquals(readInt, -1, "Wrong readInt at EOF"); + assertEquals(-1, readInt, "Wrong readInt at EOF"); addExpectedEvent(IOEvent.createSocketReadEvent(-1, s)); } } @@ -104,6 +106,7 @@ public void xrun() throws IOException { try (Socket s = new Socket()) { s.connect(ss.getLocalSocketAddress()); + addExpectedEvent(IOEvent.createSocketConnectEvent(s)); try (OutputStream os = s.getOutputStream()) { os.write(writeInt); addExpectedEvent(IOEvent.createSocketWriteEvent(1, s)); @@ -121,4 +124,14 @@ public void xrun() throws IOException { } } } + + private static IOException makeConnectException(SocketAddress addr) throws Throwable { + IOException connectException = null; + try (Socket s = new Socket()) { + s.connect(addr); + } catch (IOException ioe) { + connectException = ioe; + } + return connectException; + } } diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java index 77fe554c9baaa..f7df4c5ee1ba7 100644 --- a/test/lib/jdk/test/lib/jfr/EventNames.java +++ b/test/lib/jdk/test/lib/jfr/EventNames.java @@ -194,6 +194,8 @@ public class EventNames { public static final String FileForce = PREFIX + "FileForce"; public static final String FileRead = PREFIX + "FileRead"; public static final String FileWrite = PREFIX + "FileWrite"; + public static final String SocketConnect = PREFIX + "SocketConnect"; + public static final String SocketConnectFailed = PREFIX + "SocketConnectFailed"; public static final String SocketRead = PREFIX + "SocketRead"; public static final String SocketWrite = PREFIX + "SocketWrite"; public static final String ExceptionStatistics = PREFIX + "ExceptionStatistics";