package jdk.jfr.internal;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Predicate;
import jdk.jfr.Event;
import jdk.jfr.EventType;
public final class RequestEngine {
private final static JVM jvm = JVM.getJVM();
final static class RequestHook {
private final Runnable hook;
private final PlatformEventType type;
private final AccessControlContext accessControllerContext;
private long delta;
private RequestHook(AccessControlContext acc, PlatformEventType eventType, Runnable hook) {
this.hook = hook;
this.type = eventType;
this.accessControllerContext = acc;
}
RequestHook(PlatformEventType eventType) {
this(null, eventType, null);
}
private void execute() {
try {
if (accessControllerContext == null) {
if (type.isJDK()) {
hook.run();
} else {
jvm.emitEvent(type.getId(), JVM.counterTime(), 0);
}
if (Logger.shouldLog(LogTag.JFR_SYSTEM_EVENT, LogLevel.DEBUG)) {
Logger.log(LogTag.JFR_SYSTEM_EVENT, LogLevel.DEBUG, "Executed periodic hook for " + type.getLogName());
}
} else {
executeSecure();
}
} catch (Throwable e) {
Logger.log(LogTag.JFR_SYSTEM_EVENT, LogLevel.WARN, "Exception occurred during execution of period hook for " + type.getLogName());
}
}
private void executeSecure() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
try {
hook.run();
if (Logger.shouldLog(LogTag.JFR_EVENT, LogLevel.DEBUG)) {
Logger.log(LogTag.JFR_EVENT, LogLevel.DEBUG, "Executed periodic hook for " + type.getLogName());
}
} catch (Throwable t) {
Logger.log(LogTag.JFR_EVENT, LogLevel.WARN, "Exception occurred during execution of period hook for " + type.getLogName());
}
return null;
}
}, accessControllerContext);
}
}
private final static List<RequestHook> entries = new CopyOnWriteArrayList<>();
private static long lastTimeMillis;
private static long flushInterval = Long.MAX_VALUE;
private static long streamDelta;
public static void addHook(AccessControlContext acc, PlatformEventType type, Runnable hook) {
Objects.requireNonNull(acc);
addHookInternal(acc, type, hook);
}
private static void addHookInternal(AccessControlContext acc, PlatformEventType type, Runnable hook) {
RequestHook he = new RequestHook(acc, type, hook);
for (RequestHook e : entries) {
if (e.hook == hook) {
throw new IllegalArgumentException("Hook has already been added");
}
}
he.type.setEventHook(true);
entries.add(he);
logHook("Added", type);
}
public static void addTrustedJDKHook(Class<? extends Event> eventClass, Runnable runnable) {
if (eventClass.getClassLoader() != null) {
throw new SecurityException("Hook can only be registered for event classes that are loaded by the bootstrap class loader");
}
if (runnable.getClass().getClassLoader() != null) {
throw new SecurityException("Runnable hook class must be loaded by the bootstrap class loader");
}
EventType eType = MetadataRepository.getInstance().getEventType(eventClass);
PlatformEventType pType = PrivateAccess.getInstance().getPlatformEventType(eType);
addHookInternal(null, pType, runnable);
}
private static void logHook(String action, PlatformEventType type) {
if (type.isJDK() || type.isJVM()) {
Logger.log(LogTag.JFR_SYSTEM_EVENT, LogLevel.INFO, action + " periodic hook for " + type.getLogName());
} else {
Logger.log(LogTag.JFR_EVENT, LogLevel.INFO, action + " periodic hook for " + type.getLogName());
}
}
public static boolean removeHook(Runnable hook) {
for (RequestHook rh : entries) {
if (rh.hook == hook) {
entries.remove(rh);
rh.type.setEventHook(false);
logHook("Removed", rh.type);
return true;
}
}
return false;
}
static void addHooks(List<RequestHook> newEntries) {
List<RequestHook> addEntries = new ArrayList<>();
for (RequestHook rh : newEntries) {
rh.type.setEventHook(true);
addEntries.add(rh);
logHook("Added", rh.type);
}
entries.addAll(newEntries);
}
static void doChunkEnd() {
doChunk(x -> x.isEndChunk());
}
static void doChunkBegin() {
doChunk(x -> x.isBeginChunk());
}
private static void doChunk(Predicate<PlatformEventType> predicate) {
for (RequestHook requestHook : entries) {
PlatformEventType s = requestHook.type;
if (s.isEnabled() && predicate.test(s)) {
requestHook.execute();
}
}
}
static long doPeriodic() {
return run_requests(entries);
}
private static long run_requests(Collection<RequestHook> entries) {
long last = lastTimeMillis;
long now = System.currentTimeMillis();
long min = 0;
long delta = 0;
if (last == 0) {
last = now;
}
delta = now - last;
if (delta < 0) {
lastTimeMillis = now;
return 0;
}
Iterator<RequestHook> hookIterator = entries.iterator();
while(hookIterator.hasNext()) {
RequestHook he = hookIterator.next();
long left = 0;
PlatformEventType es = he.type;
if (!es.isEnabled() || es.isEveryChunk()) {
continue;
}
long r_period = es.getPeriod();
long r_delta = he.delta;
r_delta += delta;
if (r_delta >= r_period) {
r_delta = 0;
he.execute();
}
left = (r_period - r_delta);
if (left < 0) {
left = 0;
}
he.delta = r_delta;
if (min == 0 || left < min) {
min = left;
}
}
if (flushInterval != Long.MAX_VALUE) {
long r_period = flushInterval;
long r_delta = streamDelta;
r_delta += delta;
if (r_delta >= r_period) {
r_delta = 0;
MetadataRepository.getInstance().flush();
Utils.notifyFlush();
}
long left = (r_period - r_delta);
if (left < 0) {
left = 0;
}
streamDelta = r_delta;
if (min == 0 || left < min) {
min = left;
}
}
lastTimeMillis = now;
return min;
}
static void setFlushInterval(long millis) {
long interval = millis < 1000 ? 1000 : millis;
boolean needNotify = interval < flushInterval;
flushInterval = interval;
if (needNotify) {
synchronized (JVM.FILE_DELTA_CHANGE) {
JVM.FILE_DELTA_CHANGE.notifyAll();
}
}
}
}