From eb770a060ad86d69b38df7d11622e9e25a528e1d Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Thu, 5 Jun 2025 11:36:08 +0000 Subject: [PATCH] 8351594: JFR: Rate-limited sampling of Java events Reviewed-by: mgronlun, alanb --- .../classes/java/io/FileInputStream.java | 25 +- .../classes/java/io/FileOutputStream.java | 18 +- .../classes/java/io/RandomAccessFile.java | 41 +-- .../share/classes/java/lang/Throwable.java | 4 +- .../share/classes/java/net/Socket.java | 10 +- .../internal/event/ExceptionThrownEvent.java | 7 +- .../jdk/internal/event/FileReadEvent.java | 26 +- .../jdk/internal/event/FileWriteEvent.java | 25 +- .../jdk/internal/event/SocketReadEvent.java | 10 +- .../jdk/internal/event/SocketWriteEvent.java | 14 +- .../jdk/internal/event/ThrowableTracer.java | 22 +- .../classes/sun/nio/ch/FileChannelImpl.java | 65 +--- .../jdk/jfr/{internal => }/Throttle.java | 52 ++-- .../jdk/jfr/events/ExceptionThrownEvent.java | 4 +- .../classes/jdk/jfr/events/FileReadEvent.java | 4 +- .../jdk/jfr/events/FileWriteEvent.java | 4 +- .../jdk/jfr/events/SocketReadEvent.java | 4 +- .../jdk/jfr/events/SocketWriteEvent.java | 4 +- .../jdk/jfr/internal/ClassInspector.java | 16 + .../jdk/jfr/internal/EventControl.java | 3 + .../jfr/internal/EventInstrumentation.java | 107 ++++++- .../classes/jdk/jfr/internal/JVMSupport.java | 2 +- .../jdk/jfr/internal/MetadataLoader.java | 5 +- .../jdk/jfr/internal/MetadataRepository.java | 6 +- .../jdk/jfr/internal/PlatformEventType.java | 16 +- .../internal/event/EventConfiguration.java | 29 ++ .../internal/settings/ThrottleSetting.java | 3 +- .../jdk/jfr/internal/settings/Throttler.java | 233 ++++++++++++++ .../settings/ThrottlerParameters.java | 94 ++++++ .../internal/settings/ThrottlerWindow.java | 99 ++++++ .../share/classes/jdk/jfr/package-info.java | 24 +- src/jdk.jfr/share/conf/jfr/default.jfc | 36 ++- src/jdk.jfr/share/conf/jfr/profile.jfc | 38 ++- .../metadata/annotations/TestThrottle.java | 290 ++++++++++++++++++ .../settings/TestSettingsAvailability.java | 14 +- .../jfr/startupargs/TestEventSettings.java | 4 +- 36 files changed, 1120 insertions(+), 238 deletions(-) rename src/jdk.jfr/share/classes/jdk/jfr/{internal => }/Throttle.java (58%) create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/settings/Throttler.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerParameters.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerWindow.java create mode 100644 test/jdk/jdk/jfr/api/metadata/annotations/TestThrottle.java diff --git a/src/java.base/share/classes/java/io/FileInputStream.java b/src/java.base/share/classes/java/io/FileInputStream.java index ab312fc8c5b..b5b4813cbb5 100644 --- a/src/java.base/share/classes/java/io/FileInputStream.java +++ b/src/java.base/share/classes/java/io/FileInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -205,22 +205,17 @@ public class FileInputStream extends InputStream private int traceRead0() throws IOException { int result = 0; - boolean endOfFile = false; long bytesRead = 0; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); result = read0(); if (result < 0) { - endOfFile = true; + bytesRead = -1; } else { bytesRead = 1; } } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - FileReadEvent.commit(start, duration, path, bytesRead, endOfFile); - } + FileReadEvent.offer(start, path, bytesRead); } return result; } @@ -236,19 +231,11 @@ public class FileInputStream extends InputStream private int traceReadBytes(byte b[], int off, int len) throws IOException { int bytesRead = 0; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); bytesRead = readBytes(b, off, len); } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - if (bytesRead < 0) { - FileReadEvent.commit(start, duration, path, 0L, true); - } else { - FileReadEvent.commit(start, duration, path, bytesRead, false); - } - } + FileReadEvent.offer(start, path, bytesRead); } return bytesRead; } diff --git a/src/java.base/share/classes/java/io/FileOutputStream.java b/src/java.base/share/classes/java/io/FileOutputStream.java index 6c5a30ea432..022aa44397a 100644 --- a/src/java.base/share/classes/java/io/FileOutputStream.java +++ b/src/java.base/share/classes/java/io/FileOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -266,16 +266,12 @@ public class FileOutputStream extends OutputStream private void traceWrite(int b, boolean append) throws IOException { long bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); write(b, append); bytesWritten = 1; } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - FileWriteEvent.commit(start, duration, path, bytesWritten); - } + FileWriteEvent.offer(start, path, bytesWritten); } } @@ -310,16 +306,12 @@ public class FileOutputStream extends OutputStream private void traceWriteBytes(byte b[], int off, int len, boolean append) throws IOException { long bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); writeBytes(b, off, len, append); bytesWritten = len; } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - FileWriteEvent.commit(start, duration, path, bytesWritten); - } + FileWriteEvent.offer(start, path, bytesWritten); } } diff --git a/src/java.base/share/classes/java/io/RandomAccessFile.java b/src/java.base/share/classes/java/io/RandomAccessFile.java index c09f87afcdc..339030e022c 100644 --- a/src/java.base/share/classes/java/io/RandomAccessFile.java +++ b/src/java.base/share/classes/java/io/RandomAccessFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -367,21 +367,16 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { private int traceRead0() throws IOException { int result = 0; long bytesRead = 0; - boolean endOfFile = false; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); result = read0(); if (result < 0) { - endOfFile = true; + bytesRead = -1; } else { bytesRead = 1; } } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - FileReadEvent.commit(start, duration, path, bytesRead, endOfFile); - } + FileReadEvent.offer(start, path, bytesRead); } return result; } @@ -404,19 +399,11 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { private int traceReadBytes0(byte b[], int off, int len) throws IOException { int bytesRead = 0; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); bytesRead = readBytes0(b, off, len); } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - if (bytesRead < 0) { - FileReadEvent.commit(start, duration, path, 0L, true); - } else { - FileReadEvent.commit(start, duration, path, bytesRead, false); - } - } + FileReadEvent.offer(start, path, bytesRead); } return bytesRead; } @@ -582,16 +569,12 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { private void traceImplWrite(int b) throws IOException { long bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); implWrite(b); bytesWritten = 1; } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - FileWriteEvent.commit(start, duration, path, bytesWritten); - } + FileWriteEvent.offer(start, path, bytesWritten); } } @@ -624,16 +607,12 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { private void traceImplWriteBytes(byte b[], int off, int len) throws IOException { long bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); implWriteBytes(b, off, len); bytesWritten = len; } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - FileWriteEvent.commit(start, duration, path, bytesWritten); - } + FileWriteEvent.offer(start, path, bytesWritten); } } diff --git a/src/java.base/share/classes/java/lang/Throwable.java b/src/java.base/share/classes/java/lang/Throwable.java index 8c0ce29dbee..ac3325fc514 100644 --- a/src/java.base/share/classes/java/lang/Throwable.java +++ b/src/java.base/share/classes/java/lang/Throwable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -121,7 +121,7 @@ public class Throwable implements Serializable { * Flag set by jdk.internal.event.JFRTracing to indicate if * exceptions should be traced by JFR. */ - static volatile boolean jfrTracing; + static boolean jfrTracing; /** * The JVM saves some indication of the stack backtrace in this slot. diff --git a/src/java.base/share/classes/java/net/Socket.java b/src/java.base/share/classes/java/net/Socket.java index d2afa166a88..2905a51b402 100644 --- a/src/java.base/share/classes/java/net/Socket.java +++ b/src/java.base/share/classes/java/net/Socket.java @@ -965,10 +965,7 @@ public class Socket implements java.io.Closeable { } long start = SocketReadEvent.timestamp(); int nbytes = implRead(b, off, len); - long duration = SocketReadEvent.timestamp() - start; - if (SocketReadEvent.shouldCommit(duration)) { - SocketReadEvent.emit(start, duration, nbytes, parent.getRemoteSocketAddress(), getSoTimeout()); - } + SocketReadEvent.offer(start, nbytes, parent.getRemoteSocketAddress(), getSoTimeout()); return nbytes; } @@ -1081,10 +1078,7 @@ public class Socket implements java.io.Closeable { } long start = SocketWriteEvent.timestamp(); implWrite(b, off, len); - long duration = SocketWriteEvent.timestamp() - start; - if (SocketWriteEvent.shouldCommit(duration)) { - SocketWriteEvent.emit(start, duration, len, parent.getRemoteSocketAddress()); - } + SocketWriteEvent.offer(start, len, parent.getRemoteSocketAddress()); } private void implWrite(byte[] b, int off, int len) throws IOException { diff --git a/src/java.base/share/classes/jdk/internal/event/ExceptionThrownEvent.java b/src/java.base/share/classes/jdk/internal/event/ExceptionThrownEvent.java index 45c13e3010d..355ee81c0fc 100644 --- a/src/java.base/share/classes/jdk/internal/event/ExceptionThrownEvent.java +++ b/src/java.base/share/classes/jdk/internal/event/ExceptionThrownEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -31,6 +31,11 @@ public final class ExceptionThrownEvent extends Event { public String message; public Class thrownClass; + public static boolean shouldThrottleCommit(long timestamp) { + // Generated by JFR + return false; + } + public static void commit(long start, String message, Class thrownClass) { // Generated by JFR } diff --git a/src/java.base/share/classes/jdk/internal/event/FileReadEvent.java b/src/java.base/share/classes/jdk/internal/event/FileReadEvent.java index 34ff4e9d4ed..77b2568802e 100644 --- a/src/java.base/share/classes/jdk/internal/event/FileReadEvent.java +++ b/src/java.base/share/classes/jdk/internal/event/FileReadEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -45,11 +45,33 @@ public final class FileReadEvent extends Event { return 0L; } - public static boolean shouldCommit(long duration) { + public static boolean shouldThrottleCommit(long duration, long end) { // Generated by JFR return false; } + /** + * 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 meets + * or exceeds the configured value and is not throttled (determined by calling the + * generated method {@link #shouldThrottleCommit(long, long)}), an event will be + * emitted by calling {@link #commit(long, long, String, long, boolean)} + * + * @param start the start time + * @param path the path + * @param bytesRead the number of bytes that were read, or -1 if the end of the file was reached + */ + public static void offer(long start, String path, long bytesRead) { + long end = timestamp(); + long duration = end - start; + if (shouldThrottleCommit(duration, end)) { + boolean endOfFile = bytesRead < 0; + long bytes = endOfFile ? 0 : bytesRead; + commit(start, duration, path, bytes, endOfFile); + } + } + public static void commit(long start, long duration, String path, long bytesRead, boolean endOfFile) { // Generated by JFR } diff --git a/src/java.base/share/classes/jdk/internal/event/FileWriteEvent.java b/src/java.base/share/classes/jdk/internal/event/FileWriteEvent.java index f6c27960430..3fa7dbe9822 100644 --- a/src/java.base/share/classes/jdk/internal/event/FileWriteEvent.java +++ b/src/java.base/share/classes/jdk/internal/event/FileWriteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -44,11 +44,32 @@ public final class FileWriteEvent extends Event { return 0L; } - public static boolean shouldCommit(long duration) { + public static boolean shouldThrottleCommit(long duration, long end) { // Generated by JFR return false; } + /** + * 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 meets + * or exceeds the configured value and is not throttled (determined by calling the + * generated method {@link #shouldThrottleCommit(long, long)}), an event will be + * emitted by calling {@link #commit(long, long, String, long)} + * + * @param start the start time + * @param path the path + * @param bytesRead the number of bytes that were written, or -1 if the end of the file was reached + */ + public static void offer(long start, String path, long bytesWritten) { + long end = timestamp(); + long duration = end - start; + if (shouldThrottleCommit(duration, end)) { + long bytes = bytesWritten > 0 ? bytesWritten : 0; + commit(start, duration, path, bytes); + } + } + public static void commit(long start, long duration, String path, long bytesWritten) { // Generated by JFR } diff --git a/src/java.base/share/classes/jdk/internal/event/SocketReadEvent.java b/src/java.base/share/classes/jdk/internal/event/SocketReadEvent.java index d5f6c3241d9..09b3f23b07f 100644 --- a/src/java.base/share/classes/jdk/internal/event/SocketReadEvent.java +++ b/src/java.base/share/classes/jdk/internal/event/SocketReadEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -74,9 +74,10 @@ public class SocketReadEvent extends Event { * of this method is generated automatically if jfr is enabled. * * @param duration time in nanoseconds to complete the operation + * @param end timestamp at the end of the operation * @return true if the event should be commited */ - public static boolean shouldCommit(long duration) { + public static boolean shouldThrottleCommit(long duration, long end) { // Generated by JFR return false; } @@ -118,8 +119,9 @@ public class SocketReadEvent extends Event { * @param timeout maximum time to wait */ public static void offer(long start, long nbytes, SocketAddress remote, long timeout) { - long duration = timestamp() - start; - if (shouldCommit(duration)) { + long end = timestamp(); + long duration = end - start; + if (shouldThrottleCommit(duration, end)) { emit(start, duration, nbytes, remote, timeout); } } diff --git a/src/java.base/share/classes/jdk/internal/event/SocketWriteEvent.java b/src/java.base/share/classes/jdk/internal/event/SocketWriteEvent.java index 7c56ef826a5..12d8ffbf65b 100644 --- a/src/java.base/share/classes/jdk/internal/event/SocketWriteEvent.java +++ b/src/java.base/share/classes/jdk/internal/event/SocketWriteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -68,10 +68,11 @@ public class SocketWriteEvent extends Event { * 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 + * @param duration time to complete the operation + * @param end timestamp at the end of the operation * @return true if the event should be commited */ - public static boolean shouldCommit(long duration) { + public static boolean shouldThrottleCommit(long duration, long end) { // Generated by JFR return false; } @@ -104,7 +105,7 @@ public class SocketWriteEvent extends 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 #shouldThrottleCommit(long)}), an event will be emitted by calling * {@link #emit(long, long, long, SocketAddress)}. * * @param start the start time @@ -112,8 +113,9 @@ public class SocketWriteEvent extends Event { * @param remote the address of the remote socket being written to */ public static void offer(long start, long bytesWritten, SocketAddress remote) { - long duration = timestamp() - start; - if (shouldCommit(duration)) { + long end = timestamp(); + long duration = end - start; + if (shouldThrottleCommit(duration, end)) { emit(start, duration, bytesWritten, remote); } } diff --git a/src/java.base/share/classes/jdk/internal/event/ThrowableTracer.java b/src/java.base/share/classes/jdk/internal/event/ThrowableTracer.java index f7076b44e90..6502cbc8002 100644 --- a/src/java.base/share/classes/jdk/internal/event/ThrowableTracer.java +++ b/src/java.base/share/classes/jdk/internal/event/ThrowableTracer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -37,22 +37,24 @@ public final class ThrowableTracer { if (OutOfMemoryError.class.isAssignableFrom(clazz)) { return; } - - if (ErrorThrownEvent.enabled()) { + if (ErrorThrownEvent.enabled() || ExceptionThrownEvent.enabled()) { long timestamp = ErrorThrownEvent.timestamp(); - ErrorThrownEvent.commit(timestamp, message, clazz); - } - if (ExceptionThrownEvent.enabled()) { - long timestamp = ExceptionThrownEvent.timestamp(); - ExceptionThrownEvent.commit(timestamp, message, clazz); + if (ErrorThrownEvent.enabled()) { + ErrorThrownEvent.commit(timestamp, message, clazz); + } + if (ExceptionThrownEvent.shouldThrottleCommit(timestamp)) { + ExceptionThrownEvent.commit(timestamp, message, clazz); + } } numThrowables.incrementAndGet(); } public static void traceThrowable(Class clazz, String message) { if (ExceptionThrownEvent.enabled()) { - long timestamp = ExceptionThrownEvent.timestamp(); - ExceptionThrownEvent.commit(timestamp, message, clazz); + long timestamp = ErrorThrownEvent.timestamp(); + if (ExceptionThrownEvent.shouldThrottleCommit(timestamp)) { + ExceptionThrownEvent.commit(timestamp, message, clazz); + } } numThrowables.incrementAndGet(); } diff --git a/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java b/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java index 72de217a83c..240405c2f7c 100644 --- a/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java +++ b/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -264,19 +264,11 @@ public class FileChannelImpl private int traceImplRead(ByteBuffer dst) throws IOException { int bytesRead = 0; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); bytesRead = implRead(dst); } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - if (bytesRead < 0) { - FileReadEvent.commit(start, duration, path, 0L, true); - } else { - FileReadEvent.commit(start, duration, path, bytesRead, false); - } - } + FileReadEvent.offer(start, path, bytesRead); } return bytesRead; } @@ -326,19 +318,11 @@ public class FileChannelImpl private long traceImplRead(ByteBuffer[] dsts, int offset, int length) throws IOException { long bytesRead = 0; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); bytesRead = implRead(dsts, offset, length); } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - if (bytesRead < 0) { - FileReadEvent.commit(start, duration, path, 0L, true); - } else { - FileReadEvent.commit(start, duration, path, bytesRead, false); - } - } + FileReadEvent.offer(start, path, bytesRead); } return bytesRead; } @@ -385,16 +369,11 @@ public class FileChannelImpl private int traceImplWrite(ByteBuffer src) throws IOException { int bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); bytesWritten = implWrite(src); } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - long bytes = bytesWritten > 0 ? bytesWritten : 0; - FileWriteEvent.commit(start, duration, path, bytes); - } + FileWriteEvent.offer(start, path, bytesWritten); } return bytesWritten; } @@ -441,16 +420,11 @@ public class FileChannelImpl private long traceImplWrite(ByteBuffer[] srcs, int offset, int length) throws IOException { long bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); bytesWritten = implWrite(srcs, offset, length); } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - long bytes = bytesWritten > 0 ? bytesWritten : 0; - FileWriteEvent.commit(start, duration, path, bytes); - } + FileWriteEvent.offer(start, path, bytesWritten); } return bytesWritten; } @@ -1199,19 +1173,11 @@ public class FileChannelImpl private int traceImplRead(ByteBuffer dst, long position) throws IOException { int bytesRead = 0; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); bytesRead = implRead(dst, position); } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - if (bytesRead < 0) { - FileReadEvent.commit(start, duration, path, 0L, true); - } else { - FileReadEvent.commit(start, duration, path, bytesRead, false); - } - } + FileReadEvent.offer(start, path, bytesRead); } return bytesRead; } @@ -1271,16 +1237,11 @@ public class FileChannelImpl private int traceImplWrite(ByteBuffer src, long position) throws IOException { int bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); bytesWritten = implWrite(src, position); } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - long bytes = bytesWritten > 0 ? bytesWritten : 0; - FileWriteEvent.commit(start, duration, path, bytes); - } + FileWriteEvent.offer(start, path, bytesWritten); } return bytesWritten; } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Throttle.java b/src/jdk.jfr/share/classes/jdk/jfr/Throttle.java similarity index 58% rename from src/jdk.jfr/share/classes/jdk/jfr/internal/Throttle.java rename to src/jdk.jfr/share/classes/jdk/jfr/Throttle.java index 39409d008ab..7b9771eba2b 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Throttle.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/Throttle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, Datadog, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -24,7 +24,7 @@ * questions. */ -package jdk.jfr.internal; +package jdk.jfr; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; @@ -32,14 +32,15 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import jdk.jfr.MetadataDefinition; - /** - * Event annotation, determines the event emission rate in events per time unit. + * Event annotation, specifies the maximum rate of events per time unit, (for + * example, {@code "100/s"}). + *

+ * If the event class annotated with {@code Throttle} is filtered by other + * settings, such as a {@link jdk.jfr.Threshold} or a user-defined setting, the + * throttling will happen after those settings have been applied. * - * This setting is only supported for JVM events. - * - * @since 16 + * @since 25 */ @MetadataDefinition @Target({ ElementType.TYPE }) @@ -47,30 +48,33 @@ import jdk.jfr.MetadataDefinition; @Retention(RetentionPolicy.RUNTIME) public @interface Throttle { /** - * Settings name {@code "throttle"} for configuring an event emission rate in events per time unit. + * Setting name {@code "throttle"} for configuring throttled events. */ public static final String NAME = "throttle"; - public static final String DEFAULT = "off"; /** - * Throttle, for example {@code "100/s"}. + * The throttle rate, for example {@code "100/s"}. *

- * String representation of a non-negative {@code Long} value followed by a slash ("/") - * and one of the following units
- * {@code "ns"} (nanoseconds)
- * {@code "us"} (microseconds)
- * {@code "ms"} (milliseconds)
- * {@code "s"} (seconds)
- * {@code "m"} (minutes)
- * {@code "h"} (hours)
- * {@code "d"} (days)
+ * String representation of a non-negative {@code long} value followed by a + * forward slash ("/") and one of the following units:
+ *

*

* Example values, {@code "6000/m"}, {@code "10/ms"} and {@code "200/s"}. - * When zero is specified, for example {@code "0/s"}, no events are emitted. - * When {@code "off"} is specified, all events are emitted. + *

+ * Specifying zero, for example {@code "0/s"}, results in no events being + * emitted. + *

+ * Specifying {@code "off"} (case-sensitive) results in all events being emitted. * * @return the throttle value, default {@code "off"} not {@code null} - * */ - String value() default DEFAULT; + String value() default "off"; } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/ExceptionThrownEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/ExceptionThrownEvent.java index 22c87f3132d..41945aaf66c 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/ExceptionThrownEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/ExceptionThrownEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -29,6 +29,7 @@ import jdk.jfr.Category; import jdk.jfr.Description; import jdk.jfr.Label; import jdk.jfr.Name; +import jdk.jfr.Throttle; import jdk.jfr.internal.MirrorEvent; import jdk.jfr.internal.RemoveFields; import jdk.jfr.internal.Type; @@ -38,6 +39,7 @@ import jdk.jfr.internal.Type; @Category("Java Application") @Description("An object derived from java.lang.Exception has been created") @RemoveFields("duration") +@Throttle public final class ExceptionThrownEvent extends MirrorEvent { @Label("Message") 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 84886d2493a..bd9926c08d3 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -30,6 +30,7 @@ import jdk.jfr.Description; import jdk.jfr.Label; import jdk.jfr.DataAmount; import jdk.jfr.Name; +import jdk.jfr.Throttle; import jdk.jfr.internal.Type; import jdk.jfr.internal.MirrorEvent; @@ -38,6 +39,7 @@ import jdk.jfr.internal.MirrorEvent; @Category("Java Application") @Description("Reading data from a file") @StackFilter({"java.io.FileInputStream", "java.io.RandomAccessFile", "sun.nio.ch.FileChannelImpl"}) +@Throttle public final class FileReadEvent extends MirrorEvent { @Label("Path") 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 990f1845168..e7861eef1b6 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -30,6 +30,7 @@ import jdk.jfr.Description; import jdk.jfr.Label; import jdk.jfr.DataAmount; import jdk.jfr.Name; +import jdk.jfr.Throttle; import jdk.jfr.internal.Type; import jdk.jfr.internal.MirrorEvent; @@ -38,6 +39,7 @@ import jdk.jfr.internal.MirrorEvent; @Category("Java Application") @Description("Writing data to a file") @StackFilter({"java.io.FileOutputStream", "java.io.RandomAccessFile", "sun.nio.ch.FileChannelImpl"}) +@Throttle public final class FileWriteEvent extends MirrorEvent { @Label("Path") 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 917eabd9206..b2cdee4f8dc 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -31,6 +31,7 @@ import jdk.jfr.Label; import jdk.jfr.DataAmount; import jdk.jfr.Name; import jdk.jfr.Timespan; +import jdk.jfr.Throttle; import jdk.jfr.internal.MirrorEvent; import jdk.jfr.internal.Type; @@ -38,6 +39,7 @@ import jdk.jfr.internal.Type; @Label("Socket Read") @Category("Java Application") @Description("Reading data from a socket") +@Throttle public final class SocketReadEvent extends MirrorEvent { @Label("Remote Host") 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 92c85c132d4..661a4d1a68e 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -30,6 +30,7 @@ import jdk.jfr.Description; import jdk.jfr.Label; import jdk.jfr.DataAmount; import jdk.jfr.Name; +import jdk.jfr.Throttle; import jdk.jfr.internal.MirrorEvent; import jdk.jfr.internal.Type; @@ -37,6 +38,7 @@ import jdk.jfr.internal.Type; @Label("Socket Write") @Category("Java Application") @Description("Writing data to a socket") +@Throttle public final class SocketWriteEvent extends MirrorEvent { @Label("Remote Host") diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java index d310c505da6..3646162e8f7 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java @@ -51,6 +51,7 @@ import jdk.jfr.Name; import jdk.jfr.Registered; import jdk.jfr.SettingControl; import jdk.jfr.SettingDefinition; +import jdk.jfr.Throttle; import jdk.jfr.internal.util.Bytecode; import jdk.jfr.internal.util.ImplicitFields; import jdk.jfr.internal.util.Bytecode.FieldDesc; @@ -64,6 +65,7 @@ final class ClassInspector { private static final ClassDesc ANNOTATION_NAME = classDesc(Name.class); private static final ClassDesc ANNOTATION_ENABLED = classDesc(Enabled.class); private static final ClassDesc ANNOTATION_REMOVE_FIELDS = classDesc(RemoveFields.class); + private static final ClassDesc ANNOTATION_THROTTLE = classDesc(Throttle.class); private static final String[] EMPTY_STRING_ARRAY = {}; private final ClassModel classModel; @@ -138,6 +140,20 @@ final class ClassInspector { return true; } + boolean isThrottled() { + String result = annotationValue(ANNOTATION_THROTTLE, String.class, "off"); + if (result != null) { + return true; + } + if (superClass != null) { + Throttle t = superClass.getAnnotation(Throttle.class); + if (t != null) { + return true; + } + } + return false; + } + boolean hasStaticMethod(MethodDesc method) { for (MethodModel m : classModel.methods()) { if (Modifier.isStatic(m.flags().flagsMask())) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java index 2ea4725abc8..02775b7707a 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java @@ -44,6 +44,7 @@ import jdk.jfr.SettingControl; import jdk.jfr.SettingDefinition; import jdk.jfr.StackTrace; import jdk.jfr.Threshold; +import jdk.jfr.Throttle; import jdk.jfr.events.ActiveSettingEvent; import jdk.jfr.events.StackFilter; import jdk.jfr.internal.settings.CutoffSetting; @@ -55,6 +56,7 @@ import jdk.jfr.internal.settings.CPUThrottleSetting; import jdk.jfr.internal.settings.StackTraceSetting; import jdk.jfr.internal.settings.ThresholdSetting; import jdk.jfr.internal.settings.ThrottleSetting; +import jdk.jfr.internal.settings.Throttler; import jdk.jfr.internal.tracing.Modification; import jdk.jfr.internal.util.Utils; @@ -95,6 +97,7 @@ public final class EventControl { } if (eventType.hasThrottle()) { addControl(Throttle.NAME, defineThrottle(eventType)); + eventType.setThrottler(new Throttler(eventType)); } if (eventType.hasLevel()) { addControl(Level.NAME, defineLevel(eventType)); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java index 96e6f36e5c8..7f0ed76586a 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java @@ -60,7 +60,14 @@ import jdk.jfr.internal.util.ImplicitFields; * Class responsible for adding instrumentation to a subclass of {@link Event}. * */ -final class EventInstrumentation { +public final class EventInstrumentation { + public static final long MASK_THROTTLE = 1 << 62; + public static final long MASK_THROTTLE_CHECK = 1 << 63; + public static final long MASK_THROTTLE_BITS = MASK_THROTTLE | MASK_THROTTLE_CHECK; + public static final long MASK_THROTTLE_CHECK_SUCCESS = MASK_THROTTLE_CHECK | MASK_THROTTLE; + public static final long MASK_THROTTLE_CHECK_FAIL = MASK_THROTTLE_CHECK | 0; + public static final long MASK_NON_THROTTLE_BITS = ~MASK_THROTTLE_BITS; + private static final FieldDesc FIELD_EVENT_CONFIGURATION = FieldDesc.of(Object.class, "eventConfiguration"); private static final ClassDesc TYPE_EVENT_CONFIGURATION = classDesc(EventConfiguration.class); @@ -71,11 +78,17 @@ final class EventInstrumentation { private static final MethodDesc METHOD_BEGIN = MethodDesc.of("begin", "()V"); private static final MethodDesc METHOD_COMMIT = MethodDesc.of("commit", "()V"); private static final MethodDesc METHOD_DURATION = MethodDesc.of("duration", "(J)J"); + private static final MethodDesc METHOD_THROTTLE = MethodDesc.of("throttle", "(JJ)J"); private static final MethodDesc METHOD_ENABLED = MethodDesc.of("enabled", "()Z"); private static final MethodDesc METHOD_END = MethodDesc.of("end", "()V"); - private static final MethodDesc METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT = MethodDesc.of("shouldCommit", "(J)Z"); + private static final MethodDesc METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT_LONG = MethodDesc.of("shouldCommit", "(J)Z"); + private static final MethodDesc METHOD_EVENT_CONFIGURATION_SHOULD_THROTTLE_COMMIT_LONG_LONG = MethodDesc.of("shouldThrottleCommit", "(JJ)Z"); + private static final MethodDesc METHOD_EVENT_CONFIGURATION_SHOULD_THROTTLE_COMMIT_LONG = MethodDesc.of("shouldThrottleCommit", "(J)Z"); + private static final MethodDesc METHOD_EVENT_CONFIGURATION_GET_SETTING = MethodDesc.of("getSetting", SettingControl.class, int.class); private static final MethodDesc METHOD_EVENT_SHOULD_COMMIT = MethodDesc.of("shouldCommit", "()Z"); + private static final MethodDesc METHOD_EVENT_SHOULD_THROTTLE_COMMIT_LONG_LONG = MethodDesc.of("shouldThrottleCommit", "(JJ)Z"); + private static final MethodDesc METHOD_EVENT_SHOULD_THROTTLE_COMMIT_LONG = MethodDesc.of("shouldThrottleCommit", "(J)Z"); private static final MethodDesc METHOD_GET_EVENT_WRITER = MethodDesc.of("getEventWriter", "()" + TYPE_EVENT_WRITER.descriptorString()); private static final MethodDesc METHOD_IS_ENABLED = MethodDesc.of("isEnabled", "()Z"); private static final MethodDesc METHOD_RESET = MethodDesc.of("reset", "()V"); @@ -88,6 +101,7 @@ final class EventInstrumentation { private final MethodDesc staticCommitMethod; private final boolean untypedEventConfiguration; private final boolean guardEventConfiguration; + private final boolean throttled; /** * Creates an EventInstrumentation object. @@ -110,6 +124,11 @@ final class EventInstrumentation { this.eventClassDesc = inspector.getClassDesc(); this.staticCommitMethod = inspector.findStaticCommitMethod(); this.untypedEventConfiguration = hasUntypedConfiguration(); + if (inspector.isJDK()) { + this.throttled = inspector.hasStaticMethod(METHOD_EVENT_SHOULD_THROTTLE_COMMIT_LONG_LONG); + } else { + this.throttled = inspector.isThrottled(); + } } byte[] buildInstrumented() { @@ -147,6 +166,12 @@ final class EventInstrumentation { if (isMethod(method, METHOD_SHOULD_COMMIT_LONG)) { return this::methodShouldCommitStatic; } + if (isMethod(method, METHOD_EVENT_SHOULD_THROTTLE_COMMIT_LONG_LONG)) { + return this::methodShouldCommitThrottleStaticLongLong; + } + if (isMethod(method, METHOD_EVENT_SHOULD_THROTTLE_COMMIT_LONG)) { + return this::methodShouldCommitThrottleStaticLong; + } if (isMethod(method, METHOD_TIME_STAMP)) { return this::methodTimestamp; } @@ -188,11 +213,11 @@ final class EventInstrumentation { if (!inspector.hasDuration()) { throwMissingDuration(codeBuilder, "end"); } else { - codeBuilder.aload(0); - codeBuilder.aload(0); - getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_START_TIME); - invokestatic(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_DURATION); - putfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); + setDuration(codeBuilder, cb -> { + codeBuilder.aload(0); + getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_START_TIME); + invokestatic(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_DURATION); + }); codeBuilder.return_(); } } @@ -205,9 +230,8 @@ final class EventInstrumentation { } // if (!eventConfiguration.shouldCommit(duration) goto fail; getEventConfiguration(codeBuilder); - codeBuilder.aload(0); - getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); - invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT); + getDuration(codeBuilder); + invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT_LONG); codeBuilder.ifeq(fail); List settingDescs = inspector.getSettings(); for (int index = 0; index < settingDescs.size(); index++) { @@ -222,6 +246,30 @@ final class EventInstrumentation { codeBuilder.invokevirtual(eventClassDesc, sd.methodName(), mdesc); codeBuilder.ifeq(fail); } + if (throttled) { + // long d = eventConfiguration.throttle(this.duration); + // this.duration = d; + // if (d & MASK_THROTTLE_BIT == 0) { + // goto fail; + // } + getEventConfiguration(codeBuilder); + codeBuilder.aload(0); + getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_START_TIME); + codeBuilder.aload(0); + getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); + Bytecode.invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_THROTTLE); + int result = codeBuilder.allocateLocal(TypeKind.LONG); + codeBuilder.lstore(result); + codeBuilder.aload(0); + codeBuilder.lload(result); + putfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); + codeBuilder.lload(result); + codeBuilder.ldc(MASK_THROTTLE); + codeBuilder.land(); + codeBuilder.lconst_0(); + codeBuilder.lcmp(); + codeBuilder.ifeq(fail); + } // return true codeBuilder.iconst_1(); codeBuilder.ireturn(); @@ -294,6 +342,17 @@ final class EventInstrumentation { } private void methodShouldCommitStatic(CodeBuilder codeBuilder) { + methodShouldCommitStatic(codeBuilder, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT_LONG); + } + + private void methodShouldCommitThrottleStaticLongLong(CodeBuilder codeBuilder) { + methodShouldCommitStatic(codeBuilder, METHOD_EVENT_CONFIGURATION_SHOULD_THROTTLE_COMMIT_LONG_LONG); + } + private void methodShouldCommitThrottleStaticLong(CodeBuilder codeBuilder) { + methodShouldCommitStatic(codeBuilder, METHOD_EVENT_CONFIGURATION_SHOULD_THROTTLE_COMMIT_LONG); + } + + private void methodShouldCommitStatic(CodeBuilder codeBuilder, MethodDesc method) { Label fail = codeBuilder.newLabel(); if (guardEventConfiguration) { // if (eventConfiguration == null) goto fail; @@ -302,8 +361,10 @@ final class EventInstrumentation { } // return eventConfiguration.shouldCommit(duration); getEventConfiguration(codeBuilder); - codeBuilder.lload(0); - codeBuilder.invokevirtual(TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT.name(), METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT.descriptor()); + for (int i = 0 ; i < method.descriptor().parameterCount(); i++) { + codeBuilder.lload(2 * i); + } + codeBuilder.invokevirtual(TYPE_EVENT_CONFIGURATION, method.name(), method.descriptor()); codeBuilder.ireturn(); // fail: codeBuilder.labelBinding(fail); @@ -515,6 +576,28 @@ final class EventInstrumentation { return desc.matches(m); } + private void getDuration(CodeBuilder codeBuilder) { + codeBuilder.aload(0); + getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); + if (throttled) { + codeBuilder.loadConstant(MASK_NON_THROTTLE_BITS); + codeBuilder.land(); + } + } + + private void setDuration(CodeBuilder codeBuilder, Consumer expression) { + codeBuilder.aload(0); + expression.accept(codeBuilder); + if (throttled) { + codeBuilder.aload(0); + getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); + codeBuilder.loadConstant(MASK_THROTTLE_BITS); + codeBuilder.land(); + codeBuilder.lor(); + } + putfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); + } + private static void getEventWriter(CodeBuilder codeBuilder) { invokestatic(codeBuilder, TYPE_EVENT_WRITER, METHOD_GET_EVENT_WRITER); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java index ded1f78b76e..8314437ce72 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java @@ -98,7 +98,7 @@ public final class JVMSupport { public static void tryToInitializeJVM() { } - static long nanosToTicks(long nanos) { + public static long nanosToTicks(long nanos) { return (long) (nanos * JVM.getTimeConversionFactor()); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java index 60db375c876..2d1332aed06 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java @@ -45,11 +45,12 @@ import jdk.jfr.Period; import jdk.jfr.Relational; import jdk.jfr.StackTrace; import jdk.jfr.Threshold; +import jdk.jfr.Throttle; import jdk.jfr.TransitionFrom; import jdk.jfr.TransitionTo; import jdk.jfr.Unsigned; import jdk.jfr.internal.util.Utils; - +import jdk.jfr.internal.settings.ThrottleSetting; public final class MetadataLoader { // Caching to reduce allocation pressure and heap usage @@ -320,7 +321,7 @@ public final class MetadataLoader { aes.add(new AnnotationElement(Cutoff.class, Cutoff.INFINITY)); } if (t.throttle) { - aes.add(new AnnotationElement(Throttle.class, Throttle.DEFAULT)); + aes.add(new AnnotationElement(Throttle.class, ThrottleSetting.DEFAULT_VALUE)); } } if (t.experimental) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java index 5dcf07f719d..e574ab47992 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java @@ -45,11 +45,13 @@ import jdk.jfr.EventType; import jdk.jfr.Name; import jdk.jfr.Period; import jdk.jfr.SettingControl; +import jdk.jfr.Throttle; import jdk.jfr.ValueDescriptor; import jdk.jfr.internal.consumer.RepositoryFiles; import jdk.jfr.internal.event.EventConfiguration; import jdk.jfr.internal.management.HiddenWait; import jdk.jfr.internal.periodic.PeriodicEvents; +import jdk.jfr.internal.settings.Throttler; import jdk.jfr.internal.util.Utils; public final class MetadataRepository { @@ -214,9 +216,11 @@ public final class MetadataRepository { } } EventType eventType = PrivateAccess.getInstance().newEventType(pEventType); + pEventType.setHasThrottle(pEventType.getAnnotation(Throttle.class) != null); EventControl ec = new EventControl(pEventType, eventClass); SettingControl[] settings = ec.getSettingControls().toArray(new SettingControl[0]); - EventConfiguration configuration = new EventConfiguration(pEventType, eventType, ec, settings, eventType.getId()); + Throttler throttler = pEventType.getThrottler(); + EventConfiguration configuration = new EventConfiguration(pEventType, eventType, ec, settings, throttler, eventType.getId()); pEventType.setRegistered(true); // If class is instrumented or should not be instrumented, mark as instrumented. if (JVM.isInstrumented(eventClass) || !JVMSupport.shouldInstrument(pEventType.isJDK(), pEventType.getName())) { 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 b65a26f3aad..ff3e0238cf0 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java @@ -35,6 +35,7 @@ import jdk.jfr.internal.periodic.PeriodicEvents; import jdk.jfr.internal.util.ImplicitFields; import jdk.jfr.internal.util.TimespanRate; import jdk.jfr.internal.util.Utils; +import jdk.jfr.internal.settings.Throttler; import jdk.jfr.internal.tracing.Modification; /** @@ -72,6 +73,7 @@ public final class PlatformEventType extends Type { private boolean registered = true; private boolean committable = enabled && registered; private boolean hasLevel = false; + private Throttler throttler; // package private PlatformEventType(String name, long id, boolean isJDK, boolean dynamicSettings) { @@ -190,9 +192,11 @@ public final class PlatformEventType extends Type { } } - public void setThrottle(long eventSampleSize, long period_ms) { + public void setThrottle(long eventSampleSize, long periodInMillis) { if (isJVM) { - JVM.setThrottle(getId(), eventSampleSize, period_ms); + JVM.setThrottle(getId(), eventSampleSize, periodInMillis); + } else { + throttler.configure(eventSampleSize, periodInMillis); } } @@ -420,4 +424,12 @@ public final class PlatformEventType extends Type { public long getStackFilterId() { return startFilterId; } + + public Throttler getThrottler() { + return throttler; + } + + public void setThrottler(Throttler throttler) { + this.throttler = throttler; + } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventConfiguration.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventConfiguration.java index 4b0f2fba3da..f16e153fe43 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventConfiguration.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventConfiguration.java @@ -28,14 +28,17 @@ package jdk.jfr.internal.event; import jdk.jfr.EventType; import jdk.jfr.SettingControl; import jdk.jfr.internal.EventControl; +import jdk.jfr.internal.EventInstrumentation; import jdk.jfr.internal.JVM; import jdk.jfr.internal.PlatformEventType; +import jdk.jfr.internal.settings.Throttler; public record EventConfiguration( PlatformEventType platformEventType, EventType eventType, EventControl eventControl, SettingControl[] settings, + Throttler throttler, long id) { // Accessed by generated code in event class @@ -43,6 +46,19 @@ public record EventConfiguration( return isEnabled() && duration >= platformEventType.getThresholdTicks(); } + // Accessed by generated code in event class. Used by: + // static boolean shouldThrottleCommit(long duration, long timestamp) + public boolean shouldThrottleCommit(long duration, long timestamp) { + return isEnabled() && duration >= platformEventType.getThresholdTicks() && throttler.sample(timestamp); + } + + // Caller must of Event::shouldThrottleCommit must check enablement. + // Accessed by generated code in event class. Used by: + // static boolean shouldThrottleCommit(long timestamp) + public boolean shouldThrottleCommit(long timestamp) { + return throttler.sample(timestamp); + } + // Accessed by generated code in event class public SettingControl getSetting(int index) { return settings[index]; @@ -53,6 +69,19 @@ public record EventConfiguration( return platformEventType.isCommittable(); } + public long throttle(long startTime, long rawDuration) { + // We have already tried to throttle, return as is + if ((rawDuration & EventInstrumentation.MASK_THROTTLE_BITS) != 0) { + return rawDuration; + } + long endTime = startTime + rawDuration; + if (throttler.sample(endTime)) { + return rawDuration | EventInstrumentation.MASK_THROTTLE_CHECK_SUCCESS; + } else { + return rawDuration | EventInstrumentation.MASK_THROTTLE_CHECK_FAIL; + } + } + // Not really part of the configuration, but the method // needs to be somewhere the event class can access, // but not part of the public API. diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottleSetting.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottleSetting.java index 800443cfaed..ab26b5fcd31 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottleSetting.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottleSetting.java @@ -38,7 +38,6 @@ import jdk.jfr.MetadataDefinition; import jdk.jfr.Name; import jdk.jfr.SettingControl; import jdk.jfr.internal.PlatformEventType; -import jdk.jfr.internal.Throttle; import jdk.jfr.internal.Type; import jdk.jfr.internal.util.Rate; import jdk.jfr.internal.util.TimespanUnit; @@ -49,7 +48,7 @@ import jdk.jfr.internal.util.Utils; @Description("Throttles the emission rate for an event") @Name(Type.SETTINGS_PREFIX + "Throttle") public final class ThrottleSetting extends SettingControl { - public static final String DEFAULT_VALUE = Throttle.DEFAULT; + public static final String DEFAULT_VALUE = "off"; private final PlatformEventType eventType; private final String defaultValue; private String value; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/Throttler.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/Throttler.java new file mode 100644 index 00000000000..df8f5a58700 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/Throttler.java @@ -0,0 +1,233 @@ +/* + * 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. 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.internal.settings; + +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; +import jdk.jfr.internal.PlatformEventType; +public final class Throttler { + private static final ThrottlerParameters DISABLED_PARAMETERS = new ThrottlerParameters(0, 0, 0); + private static final long MILLIUNITS = 1000; + private static final long MINUTE = 60 * MILLIUNITS; + private static final long TEN_PER_1000_MS_IN_MINUTES = 600; + private static final long HOUR = 60 * MINUTE; + private static final long TEN_PER_1000_MS_IN_HOURS = 36000; + private static final long TEN_PER_1000_MS_IN_DAYS = 864000; + private static final long EVENT_THROTTLER_OFF = -2; + + private final ReentrantLock lock = new ReentrantLock(); + private final Random randomGenerator = new Random(); + private final ThrottlerWindow window0 = new ThrottlerWindow(); + private final ThrottlerWindow window1 = new ThrottlerWindow(); + + private volatile ThrottlerWindow activeWindow = window0; + + // Guarded by lock + private double averagePopulationSize; + private double ewmaPopulationSize; + private long accumulatedDebtCarryLimit; + private long accumulatedDebtCarryCount; + private ThrottlerParameters lastParameters = new ThrottlerParameters(0, 0, 0); + private long sampleSize; + private long periodMillis; + private boolean disabled; + private boolean update = true; + + public Throttler(PlatformEventType t) { + } + // Not synchronized in fast path, but uses volatile reads. + public boolean sample(long ticks) { + if (disabled) { + return true; + } + ThrottlerWindow current = activeWindow; + if (current.isExpired(ticks)) { + if (lock.tryLock()) { + try { + rotateWindow(ticks); + } finally { + lock.unlock(); + } + } + return activeWindow.sample(); + } + return current.sample(); + } + + public void configure(long sampleSize, long periodMillis) { + lock.lock(); + try { + this.sampleSize = sampleSize; + this.periodMillis = periodMillis; + this.update = true; + this.activeWindow = configure(nextWindowParameters(), activeWindow); + } finally { + lock.unlock(); + } + } + + private ThrottlerWindow configure(ThrottlerParameters parameters, ThrottlerWindow expired) { + if (parameters.reconfigure) { + // Store updated params once to both windows + expired.parameters = parameters; + nextWindow(expired).parameters = parameters; + configure(parameters); + } + ThrottlerWindow next = setRate(parameters, expired); + next.initialize(parameters); + return next; + } + + private void configure(ThrottlerParameters parameters) { + averagePopulationSize = 0; + ewmaPopulationSize = computeEwmaAlphaCoefficient(parameters.windowLookBackCount); + accumulatedDebtCarryLimit = computeAccumulatedDebtCarryLimit(parameters); + accumulatedDebtCarryCount = accumulatedDebtCarryLimit; + parameters.reconfigure = false; + } + + private void rotateWindow(long ticks) { + ThrottlerWindow current = activeWindow; + if (current.isExpired(ticks)) { + activeWindow = configure(current.parameters.copy(), current); + } + } + + private ThrottlerWindow setRate(ThrottlerParameters parameters, ThrottlerWindow expired) { + ThrottlerWindow next = nextWindow(expired); + long projectedSampleSize = parameters.samplePointsPerWindow + amortizeDebt(expired); + if (projectedSampleSize == 0) { + next.projectedPopulationSize = 0; + return next; + } + next.samplingInterval = deriveSamplingInterval(projectedSampleSize, expired); + next.projectedPopulationSize = projectedSampleSize * next.samplingInterval; + return next; + } + + private long amortizeDebt(ThrottlerWindow expired) { + long accumulatedDebt = expired.accumulatedDebt(); + if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { + accumulatedDebtCarryCount = 1; + return 0; + } + accumulatedDebtCarryCount++; + return -accumulatedDebt; + } + + private long deriveSamplingInterval(double sampleSize, ThrottlerWindow expired) { + double populationSize = projectPopulationSize(expired); + if (populationSize <= sampleSize) { + return 1; + } + double projectProbability = sampleSize / populationSize; + return nextGeometric(projectProbability, randomGenerator.nextDouble()); + } + + private double projectPopulationSize(ThrottlerWindow expired) { + averagePopulationSize = exponentiallyWeightedMovingAverage(expired.populationSize(), ewmaPopulationSize, averagePopulationSize); + return averagePopulationSize; + } + + private static long nextGeometric(double p, double u) { + return (long) Math.ceil(Math.log(1.0 - adjustBoundary(u)) / Math.log(1.0 - p)); + } + + private static double adjustBoundary(double u) { + if (u == 0.0) { + return 0.01; + } + if (u == 1.0) { + return 0.99; + } + return u; + } + + private void normalize() { + if (periodMillis == MILLIUNITS) { + return; + } + if (periodMillis == MINUTE) { + if (sampleSize >= TEN_PER_1000_MS_IN_MINUTES) { + sampleSize /= 60; + periodMillis /= 60; + } + return; + } + if (periodMillis == HOUR) { + if (sampleSize >= TEN_PER_1000_MS_IN_HOURS) { + sampleSize /= 3600; + periodMillis /= 3600; + } + return; + } + if (sampleSize >= TEN_PER_1000_MS_IN_DAYS) { + sampleSize /= 86400; + periodMillis /= 86400; + } + } + + private ThrottlerParameters nextWindowParameters() { + if (update) { + return updateParameters(); + } + return disabled ? DISABLED_PARAMETERS : lastParameters; + } + + private ThrottlerParameters updateParameters() { + disabled = is_disabled(sampleSize); + if (disabled) { + return DISABLED_PARAMETERS; + } + normalize(); + lastParameters.setSamplePointsAndWindowDuration(sampleSize, periodMillis); + lastParameters.reconfigure = true; + update = false; + return lastParameters; + } + + private boolean is_disabled(long eventSampleSize) { + return eventSampleSize == EVENT_THROTTLER_OFF; + } + + private double exponentiallyWeightedMovingAverage(double y, double alpha, double s) { + return alpha * y + (1 - alpha) * s; + } + + private double computeEwmaAlphaCoefficient(long lookBackCount) { + return lookBackCount <= 1 ? 1.0 : 1.0 / lookBackCount; + } + + private long computeAccumulatedDebtCarryLimit(ThrottlerParameters parameters) { + if (parameters.windowDurationMillis == 0 || parameters.windowDurationMillis >= 1000) { + return 1; + } + return 1000 / parameters.windowDurationMillis; + } + + private ThrottlerWindow nextWindow(ThrottlerWindow expired) { + return expired == window0 ? window1 : window0; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerParameters.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerParameters.java new file mode 100644 index 00000000000..68908fda2a6 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerParameters.java @@ -0,0 +1,94 @@ +/* + * 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. 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.internal.settings; + +final class ThrottlerParameters { + private static final long LOW_RATE_UPPER_BOUND = 9; + private static final long WINDOW_DIVISOR = 5; + private static final long MILLIUNITS = 1000; + private static final long MINUTE = 60 * MILLIUNITS; + private static final long TEN_PER_1000_MS_IN_MINUTES = 600; + private static final long HOUR = 60 * MINUTE; + private static final long TEN_PER_1000_MS_IN_HOURS = 36000; + private static final long DAY = 24 * HOUR; + private static final long TEN_PER_1000_MS_IN_DAYS = 864000; + private static final long DEFAULT_WINDOWS_LOOKBACK_COUNT = 25; // 25 windows == 5 seconds (for default window duration of 200 ms) + + long samplePointsPerWindow; + long windowDurationMillis; + long windowLookBackCount; + boolean reconfigure; + + ThrottlerParameters(long samplePointsPerWindow, long windowDuration, long windowLockBackCount) { + this.samplePointsPerWindow = samplePointsPerWindow; + this.windowDurationMillis = windowDuration; + this.windowLookBackCount = windowLockBackCount; + } + + public ThrottlerParameters copy() { + return new ThrottlerParameters(samplePointsPerWindow, windowDurationMillis, windowLookBackCount); + } + + void setSamplePointsAndWindowDuration(long sampleSize, long periodMillis) { + try { + if (sampleSize <= LOW_RATE_UPPER_BOUND) { + samplePointsPerWindow = sampleSize; + windowDurationMillis = periodMillis; + return; + } + if (periodMillis == MINUTE && sampleSize < TEN_PER_1000_MS_IN_MINUTES) { + samplePointsPerWindow = sampleSize; + windowDurationMillis = periodMillis; + return; + } + if (periodMillis == HOUR && sampleSize < TEN_PER_1000_MS_IN_HOURS) { + samplePointsPerWindow = sampleSize; + windowDurationMillis = periodMillis; + return; + } + if (periodMillis == DAY && sampleSize < TEN_PER_1000_MS_IN_DAYS) { + samplePointsPerWindow = sampleSize; + windowDurationMillis = periodMillis; + return; + } + samplePointsPerWindow = sampleSize / WINDOW_DIVISOR; + windowDurationMillis = periodMillis / WINDOW_DIVISOR; + } finally { + updateWindowLookback(); + } + } + + private void updateWindowLookback() { + if (windowDurationMillis <= MILLIUNITS) { + windowLookBackCount = DEFAULT_WINDOWS_LOOKBACK_COUNT; // 5 seconds + return; + } + if (windowDurationMillis == MINUTE) { + windowLookBackCount = 5; // 5 windows == 5 minutes + return; + } + windowLookBackCount = 1; // 1 window == 1 hour or 1 day + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerWindow.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerWindow.java new file mode 100644 index 00000000000..31fb6cab63c --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerWindow.java @@ -0,0 +1,99 @@ +/* + * 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. 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.internal.settings; + +import jdk.jfr.internal.JVMSupport; + +import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicLong; +import jdk.jfr.internal.JVM; + +final class ThrottlerWindow { + private final AtomicLong measuredPopulationSize = new AtomicLong(); + // Guarded by Throttler.lock. + ThrottlerParameters parameters = new ThrottlerParameters(0, 0, 0); + long samplingInterval = 1; + long projectedPopulationSize; + + private volatile long endTicks; + + void initialize(ThrottlerParameters parameters) { + if (parameters.windowDurationMillis == 0) { + endTicks = 0; + return; + } + measuredPopulationSize.set(0); + endTicks = JVM.counterTime() + JVMSupport.nanosToTicks(1_000_000L * parameters.windowDurationMillis); + } + + boolean isExpired(long timestamp) { + long endTicks = this.endTicks; + if (timestamp == 0) { + return JVM.counterTime() >= endTicks; + } else { + return timestamp >= endTicks; + } + } + + boolean sample() { + long ordinal = measuredPopulationSize.incrementAndGet(); + return ordinal <= projectedPopulationSize && ordinal % samplingInterval == 0; + } + + long maxSampleSize() { + return samplingInterval == 0 ? 0 : projectedPopulationSize / samplingInterval; + } + + long sampleSize() { + long size = populationSize(); + return size > projectedPopulationSize ? maxSampleSize() : size / samplingInterval; + } + + long populationSize() { + return measuredPopulationSize.get(); + } + + long accumulatedDebt() { + if (projectedPopulationSize == 0) { + return 0; + } + return parameters.samplePointsPerWindow - maxSampleSize() + debt(); + } + + long debt() { + if (projectedPopulationSize == 0) { + return 0; + } + return sampleSize() - parameters.samplePointsPerWindow; + } + + public String toString() { + StringJoiner sb = new StringJoiner(", "); + sb.add("measuredPopulationSize=" + measuredPopulationSize); + sb.add("samplingInterval=" + samplingInterval); + sb.add("projectedPopulationSize=" + projectedPopulationSize); + return sb.toString(); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/package-info.java b/src/jdk.jfr/share/classes/jdk/jfr/package-info.java index bd5b197d7fc..54866ec508f 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/package-info.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -177,6 +177,28 @@ * {@code "false"} * * + * {@code throttle} + * Specifies the maximum rate of events per time unit. + * {@code "off"} (no throttling) + * + * "off", if events should not be throttled, otherwise a string representation of a positive {@code Long} value followed by forward slash ("/") and one of the following units: + *

+ * + * + * {@code "off"}
+ * {@code "100/s"}
+ * {@code "1000/m"} + * + * + * * {@code filter} * Specifies the filter for the event * {@code ""} (empty string) diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index 541d1d3aa2f..565dfbdee05 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -769,31 +769,35 @@ true true - 20 ms + 20 ms true true - 20 ms + 1 ms + 100/s true true - 20 ms + 1 ms + 100/s true true - 20 ms + 1 ms + 100/s true true - 20 ms + 1 ms + 100/s @@ -836,8 +840,9 @@ - false + true true + 100/s @@ -1136,20 +1141,27 @@ - + - - + + - + + + + + + + + @@ -1177,10 +1189,6 @@ 20 ms - 20 ms - - 20 ms - diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index 9cec2d9a70f..2c0812e75fe 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -769,31 +769,35 @@ true true - 10 ms + 10 ms true true - 10 ms + 1 ms + 300/s true true - 10 ms + 1 ms + 300/s true true - 10 ms + 1 ms + 300/s true true - 10 ms + 1 ms + 300/s @@ -836,8 +840,9 @@ - false + true true + 300/s @@ -1135,21 +1140,28 @@ - + - - + + - + - + + + + + + + + @@ -1176,10 +1188,6 @@ 10 ms - 10 ms - - 10 ms - diff --git a/test/jdk/jdk/jfr/api/metadata/annotations/TestThrottle.java b/test/jdk/jdk/jfr/api/metadata/annotations/TestThrottle.java new file mode 100644 index 00000000000..da3bd311abb --- /dev/null +++ b/test/jdk/jdk/jfr/api/metadata/annotations/TestThrottle.java @@ -0,0 +1,290 @@ +/* + * 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.api.metadata.annotations; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Constructor; +import java.time.Duration; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import jdk.jfr.AnnotationElement; +import jdk.jfr.Event; +import jdk.jfr.EventType; +import jdk.jfr.MetadataDefinition; +import jdk.jfr.Name; +import jdk.jfr.Threshold; +import jdk.jfr.Enabled; +import jdk.jfr.Recording; +import jdk.jfr.SettingDefinition; +import jdk.jfr.SettingDescriptor; +import jdk.jfr.Throttle; +import jdk.jfr.ValueDescriptor; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.Events; +import jdk.jfr.SettingControl; + +/** + * @test + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.api.metadata.annotations.TestThrottle + */ +public class TestThrottle { + + @Throttle("off") + @Enabled(false) + public static class ThrottledDisabledEvent extends Event { + } + + @Throttle("off") + public static class ThrottledOffEvent extends Event { + } + + @Throttle("0/s") + public static class ThrottledZeroRateEvent extends Event { + } + + @Throttle("10000000/s") + public static class ThrottledHighRateEvent extends Event { + } + + @Throttle("off") + @Threshold("5 h") + public static class ThrottledThresholdedEvent extends Event { + } + + @Throttle("50/s") + public static class ThrottledNormalRateEvent extends Event { + public int index; + } + + static class TestSetting extends SettingControl { + private boolean value; + + @Override + public String combine(Set values) { + if (values.contains("true")) { + return "true"; + } + if (values.contains("false")) { + return "false"; + } + return "true"; + } + + @Override + public void setValue(String text) { + value = Boolean.parseBoolean(text); + } + + @Override + public String getValue() { + return "" + value; + } + } + + @Throttle("10000000/s") + public static class ThrottledUserdefinedEvent extends Event { + @SettingDefinition + public boolean test(TestSetting control) { + return control.value; + } + } + + @Throttle("50/s") + public static class ThrottledReuseEvent extends Event { + public int index; + } + + public static void main(String[] args) throws Exception { + testThrottleDisabled(); + testThrottledOff(); + testThottleZeroRate(); + testThrottleHighRate(); + testThrottleThresholded(); + testThrottleNormalRate(); + testThrottleUserdefined(); + } + + private static void testThrottleDisabled() throws Exception { + testEvent(ThrottledDisabledEvent.class, false); + } + + private static void testThrottledOff() throws Exception { + testEvent(ThrottledOffEvent.class, true); + } + + private static void testThottleZeroRate() throws Exception { + testEvent(ThrottledZeroRateEvent.class, false); + } + + private static void testThrottleHighRate() throws Exception { + testEvent(ThrottledHighRateEvent.class, true); + } + + private static void testThrottleThresholded() throws Exception { + testEvent(ThrottledThresholdedEvent.class, false); + } + + private static void testThrottleNormalRate() throws Exception { + try (RecordingStream rs = new RecordingStream()) { + AtomicInteger lastIndex = new AtomicInteger(); + AtomicInteger throttled = new AtomicInteger(); + rs.onEvent(ThrottledNormalRateEvent.class.getName(), e -> { + int index = e.getInt("index"); + if (lastIndex.get() + 1 != index) { + throttled.incrementAndGet(); + } + lastIndex.set(index); + }); + rs.startAsync(); + int index = 1; + while (throttled.get() < 30) { + ThrottledNormalRateEvent e = new ThrottledNormalRateEvent(); + e.index = index; + e.commit(); + index++; + Thread.sleep(3); + } + } + } + + private static void testThrottleUserdefined() throws Exception { + testThrottleUserdefined("false", "1000000/s", false); + testThrottleUserdefined("true", "10000000/s", true); + testThrottleUserdefined("true", "0/s", false); + testThrottleUserdefined("true", "off", true); + testThrottleUserdefined("false", "off", false); + } + + private static void testThrottleUserdefined(String test, String throttle, boolean emit) throws Exception { + String eventName = ThrottledUserdefinedEvent.class.getName(); + try (Recording r = new Recording()) { + r.enable(eventName).with("test", test).with("throttle", throttle); + r.start(); + + ThrottledUserdefinedEvent e1 = new ThrottledUserdefinedEvent(); + e1.commit(); + + ThrottledUserdefinedEvent e2 = new ThrottledUserdefinedEvent(); + e2.begin(); + e2.commit(); + + ThrottledUserdefinedEvent e3 = new ThrottledUserdefinedEvent(); + e3.begin(); + e3.end(); + e3.commit(); + + ThrottledUserdefinedEvent e4 = new ThrottledUserdefinedEvent(); + if (e4.shouldCommit()) { + e4.commit(); + } + assertShouldCommit(e4, emit); + + ThrottledUserdefinedEvent e5 = new ThrottledUserdefinedEvent(); + assertShouldCommit(e5, emit); + if (e5.shouldCommit()) { + e5.commit(); + } + + r.stop(); + assertEvents(r, eventName, emit ? 5 : 0); + } + } + + @SuppressWarnings("unchecked") + private static void testEvent(Class eventClass, boolean shouldCommit) throws Exception { + try (Recording r = new Recording()) { + r.start(); + Constructor c = (Constructor) eventClass.getConstructor(); + for (int i = 0; i < 17; i++) { + Event e = c.newInstance(); + if (i % 5 == 0) { + assertShouldCommit(e, shouldCommit); + } + e.commit(); + if (i % 3 == 0) { + assertShouldCommit(e, shouldCommit); + } + } + for (int i = 0; i < 50; i++) { + Event e = c.newInstance(); + e.begin(); + if (i % 5 == 0) { + assertShouldCommit(e, shouldCommit); + } + e.end(); + if (i % 3 == 0) { + assertShouldCommit(e, shouldCommit); + } + e.commit(); + if (i % 7 == 0) { + assertShouldCommit(e, shouldCommit); + } + } + for (int i = 0; i < 11; i++) { + Event e = c.newInstance(); + e.begin(); + e.commit(); + if (i % 7 == 0) { + assertShouldCommit(e, shouldCommit); + } + } + if (shouldCommit) { + assertEvents(r, eventClass.getName(), 17 + 50 + 11); + } + } + } + + private static void assertEvents(Recording r, String name, int expected) throws Exception { + int count = 0; + for (RecordedEvent event : Events.fromRecording(r)) { + if (event.getEventType().getName().equals(name)) { + count++; + } + } + if (count != expected) { + throw new Exception("Expected " + expected + " " + name + " events, but found " + count); + } + } + + private static void assertShouldCommit(Event e, boolean expected) throws Exception { + if (e.shouldCommit() != expected) { + throw new Exception("Expected " + e.getClass() + "::shouldCommit() to return " + expected); + } + } +} diff --git a/test/jdk/jdk/jfr/api/recording/settings/TestSettingsAvailability.java b/test/jdk/jdk/jfr/api/recording/settings/TestSettingsAvailability.java index dc22644b4d0..7c6e6fa276b 100644 --- a/test/jdk/jdk/jfr/api/recording/settings/TestSettingsAvailability.java +++ b/test/jdk/jdk/jfr/api/recording/settings/TestSettingsAvailability.java @@ -66,7 +66,7 @@ public class TestSettingsAvailability { for (EventType parsedType : rf.readEventTypes()) { EventType inMem = inMemoryTypes.get(parsedType.getName()); if (inMem == null) { - throw new Exception("Superflous event type " + parsedType.getName() + " in recording"); + throw new Exception("Superfluous event type " + parsedType.getName() + " in recording"); } Set inMemsettings = new HashSet<>(); for (SettingDescriptor sd : inMem.getSettingDescriptors()) { @@ -75,7 +75,7 @@ public class TestSettingsAvailability { for (SettingDescriptor parsedSetting : parsedType.getSettingDescriptors()) { if (!inMemsettings.contains(parsedSetting.getName())) { - throw new Exception("Superflous setting " + parsedSetting.getName() + " in " + parsedType.getName()); + throw new Exception("Superfluous setting " + parsedSetting.getName() + " in " + parsedType.getName()); } inMemsettings.remove(parsedSetting.getName()); } @@ -89,14 +89,14 @@ public class TestSettingsAvailability { private static void testKnownSettings() throws Exception { testSetting(EventNames.JVMInformation, "enabled", "period"); - testSetting(EventNames.FileRead, "enabled", "threshold", "stackTrace"); - testSetting(EventNames.FileWrite, "enabled", "threshold","stackTrace"); + testSetting(EventNames.FileRead, "enabled", "threshold", "stackTrace", "throttle"); + testSetting(EventNames.FileWrite, "enabled", "threshold", "stackTrace", "throttle"); testSetting(EventNames.ExceptionStatistics, "enabled", "period"); - testSetting(EventNames.SocketRead, "enabled", "threshold", "stackTrace"); - testSetting(EventNames.SocketWrite, "enabled", "threshold", "stackTrace"); + testSetting(EventNames.SocketRead, "enabled", "threshold", "stackTrace", "throttle"); + testSetting(EventNames.SocketWrite, "enabled", "threshold", "stackTrace", "throttle"); testSetting(EventNames.ActiveRecording, "enabled"); testSetting(EventNames.ActiveSetting, "enabled"); - testSetting(EventNames.JavaExceptionThrow, "enabled", "stackTrace"); + testSetting(EventNames.JavaExceptionThrow, "enabled", "stackTrace", "throttle"); } private static void testSetting(String eventName, String... settingNames) throws Exception { diff --git a/test/jdk/jdk/jfr/startupargs/TestEventSettings.java b/test/jdk/jdk/jfr/startupargs/TestEventSettings.java index 37af394affd..c86a6331c52 100644 --- a/test/jdk/jdk/jfr/startupargs/TestEventSettings.java +++ b/test/jdk/jdk/jfr/startupargs/TestEventSettings.java @@ -51,7 +51,7 @@ import jdk.jfr.Recording; * jdk.jfr.startupargs.TestEventSettings multipleSettings * * @run main/othervm - * -XX:StartFlightRecording:class-loading=true,socket-threshold=100ms + * -XX:StartFlightRecording:class-loading=true,locking-threshold=100ms * jdk.jfr.startupargs.TestEventSettings jfcOptions */ public class TestEventSettings { @@ -70,7 +70,7 @@ public class TestEventSettings { } case "jfcOptions" -> { assertSetting("jdk.ClassDefine#enabled","true"); - assertSetting("jdk.SocketRead#threshold", "100 ms"); + assertSetting("jdk.JavaMonitorEnter#threshold", "100 ms"); } default -> throw new Exception("Uknown tes " + subTest); }