1 /* 2 * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.jfr.internal; 27 28 import java.security.AccessControlContext; 29 import java.security.AccessController; 30 import java.security.PrivilegedAction; 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.Iterator; 34 import java.util.List; 35 import java.util.Objects; 36 import java.util.concurrent.CopyOnWriteArrayList; 37 import java.util.function.Predicate; 38 import jdk.jfr.Event; 39 import jdk.jfr.EventType; 40 41 public final class RequestEngine { 42 43 private final static JVM jvm = JVM.getJVM(); 44 45 final static class RequestHook { 46 private final Runnable hook; 47 private final PlatformEventType type; 48 private final AccessControlContext accessControllerContext; 49 private long delta; 50 51 // Java events RequestHook(AccessControlContext acc, PlatformEventType eventType, Runnable hook)52 private RequestHook(AccessControlContext acc, PlatformEventType eventType, Runnable hook) { 53 this.hook = hook; 54 this.type = eventType; 55 this.accessControllerContext = acc; 56 } 57 58 // native events RequestHook(PlatformEventType eventType)59 RequestHook(PlatformEventType eventType) { 60 this(null, eventType, null); 61 } 62 execute()63 private void execute() { 64 try { 65 if (accessControllerContext == null) { // native 66 if (type.isJDK()) { 67 hook.run(); 68 } else { 69 jvm.emitEvent(type.getId(), JVM.counterTime(), 0); 70 } 71 if (Logger.shouldLog(LogTag.JFR_SYSTEM_EVENT, LogLevel.DEBUG)) { 72 Logger.log(LogTag.JFR_SYSTEM_EVENT, LogLevel.DEBUG, "Executed periodic hook for " + type.getLogName()); 73 } 74 } else { 75 executeSecure(); 76 } 77 } catch (Throwable e) { 78 // Prevent malicious user to propagate exception callback in the wrong context 79 Logger.log(LogTag.JFR_SYSTEM_EVENT, LogLevel.WARN, "Exception occurred during execution of period hook for " + type.getLogName()); 80 } 81 } 82 executeSecure()83 private void executeSecure() { 84 AccessController.doPrivileged(new PrivilegedAction<Void>() { 85 @Override 86 public Void run() { 87 try { 88 hook.run(); 89 if (Logger.shouldLog(LogTag.JFR_EVENT, LogLevel.DEBUG)) { 90 Logger.log(LogTag.JFR_EVENT, LogLevel.DEBUG, "Executed periodic hook for " + type.getLogName()); 91 } 92 } catch (Throwable t) { 93 // Prevent malicious user to propagate exception callback in the wrong context 94 Logger.log(LogTag.JFR_EVENT, LogLevel.WARN, "Exception occurred during execution of period hook for " + type.getLogName()); 95 } 96 return null; 97 } 98 }, accessControllerContext); 99 } 100 } 101 102 private final static List<RequestHook> entries = new CopyOnWriteArrayList<>(); 103 private static long lastTimeMillis; 104 private static long flushInterval = Long.MAX_VALUE; 105 private static long streamDelta; 106 addHook(AccessControlContext acc, PlatformEventType type, Runnable hook)107 public static void addHook(AccessControlContext acc, PlatformEventType type, Runnable hook) { 108 Objects.requireNonNull(acc); 109 addHookInternal(acc, type, hook); 110 } 111 addHookInternal(AccessControlContext acc, PlatformEventType type, Runnable hook)112 private static void addHookInternal(AccessControlContext acc, PlatformEventType type, Runnable hook) { 113 RequestHook he = new RequestHook(acc, type, hook); 114 for (RequestHook e : entries) { 115 if (e.hook == hook) { 116 throw new IllegalArgumentException("Hook has already been added"); 117 } 118 } 119 he.type.setEventHook(true); 120 // Insertion takes O(2*n), could be O(1) with HashMap, but 121 // thinking is that CopyOnWriteArrayList is faster 122 // to iterate over, which will happen more over time. 123 entries.add(he); 124 logHook("Added", type); 125 } 126 addTrustedJDKHook(Class<? extends Event> eventClass, Runnable runnable)127 public static void addTrustedJDKHook(Class<? extends Event> eventClass, Runnable runnable) { 128 if (eventClass.getClassLoader() != null) { 129 throw new SecurityException("Hook can only be registered for event classes that are loaded by the bootstrap class loader"); 130 } 131 if (runnable.getClass().getClassLoader() != null) { 132 throw new SecurityException("Runnable hook class must be loaded by the bootstrap class loader"); 133 } 134 EventType eType = MetadataRepository.getInstance().getEventType(eventClass); 135 PlatformEventType pType = PrivateAccess.getInstance().getPlatformEventType(eType); 136 addHookInternal(null, pType, runnable); 137 } 138 logHook(String action, PlatformEventType type)139 private static void logHook(String action, PlatformEventType type) { 140 if (type.isJDK() || type.isJVM()) { 141 Logger.log(LogTag.JFR_SYSTEM_EVENT, LogLevel.INFO, action + " periodic hook for " + type.getLogName()); 142 } else { 143 Logger.log(LogTag.JFR_EVENT, LogLevel.INFO, action + " periodic hook for " + type.getLogName()); 144 } 145 } 146 147 // Takes O(2*n), see addHook. removeHook(Runnable hook)148 public static boolean removeHook(Runnable hook) { 149 for (RequestHook rh : entries) { 150 if (rh.hook == hook) { 151 entries.remove(rh); 152 rh.type.setEventHook(false); 153 logHook("Removed", rh.type); 154 return true; 155 } 156 } 157 return false; 158 } 159 160 // Only to be used for JVM events. No access control contest 161 // or check if hook already exists addHooks(List<RequestHook> newEntries)162 static void addHooks(List<RequestHook> newEntries) { 163 List<RequestHook> addEntries = new ArrayList<>(); 164 for (RequestHook rh : newEntries) { 165 rh.type.setEventHook(true); 166 addEntries.add(rh); 167 logHook("Added", rh.type); 168 } 169 entries.addAll(newEntries); 170 } 171 doChunkEnd()172 static void doChunkEnd() { 173 doChunk(x -> x.isEndChunk()); 174 } 175 doChunkBegin()176 static void doChunkBegin() { 177 doChunk(x -> x.isBeginChunk()); 178 } 179 doChunk(Predicate<PlatformEventType> predicate)180 private static void doChunk(Predicate<PlatformEventType> predicate) { 181 for (RequestHook requestHook : entries) { 182 PlatformEventType s = requestHook.type; 183 if (s.isEnabled() && predicate.test(s)) { 184 requestHook.execute(); 185 } 186 } 187 } 188 doPeriodic()189 static long doPeriodic() { 190 return run_requests(entries); 191 } 192 193 // code copied from native impl. run_requests(Collection<RequestHook> entries)194 private static long run_requests(Collection<RequestHook> entries) { 195 long last = lastTimeMillis; 196 // Bug 9000556 - current time millis has rather lame resolution 197 // The use of os::elapsed_counter() is deliberate here, we don't 198 // want it exchanged for os::ft_elapsed_counter(). 199 // Keeping direct call os::elapsed_counter() here for reliable 200 // real time values in order to decide when registered requestable 201 // events are due. 202 long now = System.currentTimeMillis(); 203 long min = 0; 204 long delta = 0; 205 206 if (last == 0) { 207 last = now; 208 } 209 210 // time from then to now 211 delta = now - last; 212 213 if (delta < 0) { 214 // to handle time adjustments 215 // for example Daylight Savings 216 lastTimeMillis = now; 217 return 0; 218 } 219 Iterator<RequestHook> hookIterator = entries.iterator(); 220 while(hookIterator.hasNext()) { 221 RequestHook he = hookIterator.next(); 222 long left = 0; 223 PlatformEventType es = he.type; 224 // Not enabled, skip. 225 if (!es.isEnabled() || es.isEveryChunk()) { 226 continue; 227 } 228 long r_period = es.getPeriod(); 229 long r_delta = he.delta; 230 231 // add time elapsed. 232 r_delta += delta; 233 234 // above threshold? 235 if (r_delta >= r_period) { 236 // Bug 9000556 - don't try to compensate 237 // for wait > period 238 r_delta = 0; 239 he.execute(); 240 } 241 242 // calculate time left 243 left = (r_period - r_delta); 244 245 /* 246 * nothing outside checks that a period is >= 0, so left can end up 247 * negative here. ex. (r_period =(-1)) - (r_delta = 0) if it is, 248 * handle it. 249 */ 250 if (left < 0) { 251 left = 0; 252 } 253 254 // assign delta back 255 he.delta = r_delta; 256 257 if (min == 0 || left < min) { 258 min = left; 259 } 260 } 261 // Flush should happen after all periodic events has been emitted 262 // Repeat of the above algorithm, but using the stream interval. 263 if (flushInterval != Long.MAX_VALUE) { 264 long r_period = flushInterval; 265 long r_delta = streamDelta; 266 r_delta += delta; 267 if (r_delta >= r_period) { 268 r_delta = 0; 269 MetadataRepository.getInstance().flush(); 270 Utils.notifyFlush(); 271 } 272 long left = (r_period - r_delta); 273 if (left < 0) { 274 left = 0; 275 } 276 streamDelta = r_delta; 277 if (min == 0 || left < min) { 278 min = left; 279 } 280 } 281 282 lastTimeMillis = now; 283 return min; 284 } 285 setFlushInterval(long millis)286 static void setFlushInterval(long millis) { 287 // Don't accept shorter interval than 1 s. 288 long interval = millis < 1000 ? 1000 : millis; 289 boolean needNotify = interval < flushInterval; 290 flushInterval = interval; 291 if (needNotify) { 292 synchronized (JVM.FILE_DELTA_CHANGE) { 293 JVM.FILE_DELTA_CHANGE.notifyAll(); 294 } 295 } 296 } 297 } 298