diff --git a/src/java.base/share/classes/java/io/FileInputStream.java b/src/java.base/share/classes/java/io/FileInputStream.java
index ab312fc8c5be0..b5b4813cbb59d 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 int read() throws IOException {
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 @@ private int traceRead0() throws IOException {
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 6c5a30ea43232..022aa44397a18 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 @@ private void open(String name, boolean append) throws FileNotFoundException {
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 @@ private native void writeBytes(byte[] b, int off, int len, boolean append)
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 c09f87afcdc45..339030e022c4c 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 int read() throws IOException {
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 @@ private int readBytes(byte[] b, int off, int len) throws IOException {
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 @@ private void implWrite(int b) throws IOException {
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 @@ private void implWriteBytes(byte[] b, int off, int len) throws IOException {
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 8c0ce29dbeefc..ac3325fc51417 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 d2afa166a8888..2905a51b402c6 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 int read(byte[] b, int off, int len) throws IOException {
}
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 void write(byte[] b, int off, int len) throws IOException {
}
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 45c13e3010d1c..355ee81c0fcbb 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 34ff4e9d4ed3d..77b2568802e83 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 static long timestamp() {
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 f6c279604305d..3fa7dbe9822ef 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 static long timestamp() {
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 d5f6c3241d9a0..09b3f23b07f66 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 static void commit(long start, long duration, String host, String address
* 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 static long timestamp() {
* @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 7c56ef826a5c4..12d8ffbf65b3e 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 static void commit(long start, long duration, String host, String address
* 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 static long timestamp() {
* 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 static long timestamp() {
* @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 f7076b44e909b..6502cbc80020e 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 static void traceError(Class> clazz, String message) {
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 72de217a83ce6..240405c2f7c92 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 @@ private int implRead(ByteBuffer dst) throws IOException {
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 @@ private long implRead(ByteBuffer[] dsts, int offset, int length)
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 @@ private int implWrite(ByteBuffer src) throws IOException {
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 @@ private long implWrite(ByteBuffer[] srcs, int offset, int length) throws IOExcep
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 @@ private int implRead(ByteBuffer dst, long position) throws IOException {
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 @@ private int implWrite(ByteBuffer src, long position) throws IOException {
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 39409d008ab3d..7b9771eba2be7 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.RetentionPolicy;
import java.lang.annotation.Target;
-import jdk.jfr.MetadataDefinition;
-
/**
- * Event annotation, determines the event emission rate in events per time unit.
- *
- * This setting is only supported for JVM events.
+ * 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.
*
- * @since 16
+ * @since 25
*/
@MetadataDefinition
@Target({ ElementType.TYPE })
@@ -47,30 +48,33 @@
@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:
+ *
+ *
{@code "ns"} (nanoseconds)
+ *
{@code "us"} (microseconds)
+ *
{@code "ms"} (milliseconds)
+ *
{@code "s"} (seconds)
+ *
{@code "m"} (minutes)
+ *
{@code "h"} (hours)
+ *
{@code "d"} (days)
+ *
*
* 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 22c87f3132d43..41945aaf66cd2 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.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 @@
@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 84886d2493a54..bd9926c08d38e 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.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 @@
@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 990f1845168a5..e7861eef1b64b 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.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 @@
@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 917eabd92067f..b2cdee4f8dccf 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.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 @@
@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 92c85c132d4ad..661a4d1a68e2c 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.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 @@
@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 d310c505da698..3646162e8f78f 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.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 @@ boolean isEnabled() {
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 2ea4725abc8ca..02775b7707a02 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.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.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 @@ record NamedControl(String name, Control control) {
}
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 96e6f36e5c849..7f0ed76586a8b 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 @@
* 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 @@ private Consumer instrumentable(MethodModel method) {
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 @@ private void methodEnd(CodeBuilder codeBuilder) {
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 @@ private void methodShouldCommit(CodeBuilder codeBuilder) {
}
// 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 @@ private void methodShouldCommit(CodeBuilder codeBuilder) {
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 @@ private void methodTimestamp(CodeBuilder codeBuilder) {
}
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 @@ private void methodShouldCommitStatic(CodeBuilder codeBuilder) {
}
// 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 @@ private static boolean isMethod(MethodModel m, MethodDesc desc) {
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 ded1f78b76eff..8314437ce7283 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 static boolean isNotAvailable() {
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 60db375c87686..2d1332aed06b4 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.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 @@ private Map buildTypeMap() {
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 5dcf07f719dc4..e574ab47992a9 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.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 @@ private EventConfiguration makeConfiguration(Class extends jdk.internal.event.
}
}
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 b65a26f3aad5b..ff3e0238cf03b 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.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 @@ private void setMiscellaneous(long value) {
}
}
- 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 boolean hasStackFilters() {
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 4b0f2fba3dadf..f16e153fe4392 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 @@
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 boolean shouldCommit(long duration) {
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 boolean isEnabled() {
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 800443cfaed01..ab26b5fcd3146 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.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 @@
@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 0000000000000..df8f5a587008c
--- /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 0000000000000..68908fda2a6dd
--- /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 0000000000000..31fb6cab63c94
--- /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 bd5b197d7fce2..54866ec508f59 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:
+ *