1 /* 2 * Copyright (c) 2016, 2018, 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.List; 34 import java.util.Objects; 35 import java.util.concurrent.CopyOnWriteArrayList; 36 import java.util.function.Predicate; 37 38 public final class RequestEngine { 39 40 private final static JVM jvm = JVM.getJVM(); 41 42 final static class RequestHook { 43 private final Runnable hook; 44 private final PlatformEventType type; 45 private final AccessControlContext accessControllerContext; 46 private long delta; 47 48 // Java events 49 private RequestHook(AccessControlContext acc, PlatformEventType eventType, Runnable hook) { 50 this.hook = hook; 51 this.type = eventType; 52 this.accessControllerContext = acc; 53 } 54 55 // native events 56 RequestHook(PlatformEventType eventType) { 57 this(null, eventType, null); 58 } 59 60 private void execute() { 61 try { 62 if (accessControllerContext == null) { // native 63 jvm.emitEvent(type.getId(), JVM.counterTime(), 0); 64 Logger.log(LogTag.JFR_SYSTEM_EVENT, LogLevel.DEBUG, ()-> "Executed periodic hook for " + type.getLogName()); 65 } else { 66 executeSecure(); 67 } 68 } catch (Throwable e) { 69 // Prevent malicious user to propagate exception callback in the wrong context 70 Logger.log(LogTag.JFR_SYSTEM_EVENT, LogLevel.WARN, "Exception occured during execution of period hook for " + type.getLogName()); 71 } 72 } 73 74 private void executeSecure() { 75 AccessController.doPrivileged(new PrivilegedAction<Void>() { 76 @Override 77 public Void run() { 78 try { 79 hook.run(); 80 Logger.log(LogTag.JFR_EVENT, LogLevel.DEBUG, ()-> "Executed periodic hook for " + type.getLogName()); 81 } catch (Throwable t) { 82 // Prevent malicious user to propagate exception callback in the wrong context 83 Logger.log(LogTag.JFR_EVENT, LogLevel.WARN, "Exception occured during execution of period hook for " + type.getLogName()); 84 } 85 return null; 86 } 87 }, accessControllerContext); 88 } 89 } 90 91 private final static List<RequestHook> entries = new CopyOnWriteArrayList<>(); 92 private static long lastTimeMillis; 93 94 // Insertion takes O(2*n), could be O(1) with HashMap, but 95 // thinking is that CopyOnWriteArrayList is faster 96 // to iterate over, which will happen more over time. 97 public static void addHook(AccessControlContext acc, PlatformEventType type, Runnable hook) { 98 Objects.requireNonNull(acc); 99 RequestHook he = new RequestHook(acc, type, hook); 100 for (RequestHook e : entries) { 101 if (e.hook == hook) { 102 throw new IllegalArgumentException("Hook has already been added"); 103 } 104 } 105 he.type.setEventHook(true); 106 entries.add(he); 107 logHook("Added", type); 108 } 109 110 111 private static void logHook(String action, PlatformEventType type) { 112 if (type.isJDK() || type.isJVM()) { 113 Logger.log(LogTag.JFR_SYSTEM_EVENT, LogLevel.INFO, action + " periodic hook for " + type.getLogName()); 114 } else { 115 Logger.log(LogTag.JFR_EVENT, LogLevel.INFO, action + " periodic hook for " + type.getLogName()); 116 } 117 } 118 119 // Takes O(2*n), see addHook. 120 public static boolean removeHook(Runnable hook) { 121 for (RequestHook rh : entries) { 122 if (rh.hook == hook) { 123 entries.remove(rh); 124 rh.type.setEventHook(false); 125 logHook("Removed", rh.type); 126 return true; 127 } 128 } 129 return false; 130 } 131 132 // Only to be used for JVM events. No access control contest 133 // or check if hook already exists 134 static void addHooks(List<RequestHook> newEntries) { 135 List<RequestHook> addEntries = new ArrayList<>(); 136 for (RequestHook rh : newEntries) { 137 rh.type.setEventHook(true); 138 addEntries.add(rh); 139 logHook("Added", rh.type); 140 } 141 entries.addAll(newEntries); 142 } 143 144 static void doChunkEnd() { 145 doChunk(x -> x.isEndChunk()); 146 } 147 148 static void doChunkBegin() { 149 doChunk(x -> x.isBeginChunk()); 150 } 151 152 private static void doChunk(Predicate<PlatformEventType> predicate) { 153 for (RequestHook requestHook : entries) { 154 PlatformEventType s = requestHook.type; 155 if (s.isEnabled() && predicate.test(s)) { 156 requestHook.execute(); 157 } 158 } 159 } 160 161 static long doPeriodic() { 162 return run_requests(entries); 163 } 164 165 // code copied from native impl. 166 private static long run_requests(Collection<RequestHook> entries) { 167 long last = lastTimeMillis; 168 // Bug 9000556 - current time millis has rather lame resolution 169 // The use of os::elapsed_counter() is deliberate here, we don't 170 // want it exchanged for os::ft_elapsed_counter(). 171 // Keeping direct call os::elapsed_counter() here for reliable 172 // real time values in order to decide when registered requestable 173 // events are due. 174 long now = System.currentTimeMillis(); 175 long min = 0; 176 long delta = 0; 177 178 if (last == 0) { 179 last = now; 180 } 181 182 // time from then to now 183 delta = now - last; 184 185 if (delta < 0) { 186 // to handle time adjustments 187 // for example Daylight Savings 188 lastTimeMillis = now; 189 return 0; 190 } 191 for (RequestHook he : entries) { 192 long left = 0; 193 PlatformEventType es = he.type; 194 // Not enabled, skip. 195 if (!es.isEnabled() || es.isEveryChunk()) { 196 continue; 197 } 198 long r_period = es.getPeriod(); 199 long r_delta = he.delta; 200 201 // add time elapsed. 202 r_delta += delta; 203 204 // above threshold? 205 if (r_delta >= r_period) { 206 // Bug 9000556 - don't try to compensate 207 // for wait > period 208 r_delta = 0; 209 he.execute(); 210 ; 211 } 212 213 // calculate time left 214 left = (r_period - r_delta); 215 216 /** 217 * nothing outside checks that a period is >= 0, so left can end up 218 * negative here. ex. (r_period =(-1)) - (r_delta = 0) if it is, 219 * handle it. 220 */ 221 if (left < 0) { 222 left = 0; 223 } 224 225 // assign delta back 226 he.delta = r_delta; 227 228 if (min == 0 || left < min) { 229 min = left; 230 } 231 } 232 lastTimeMillis = now; 233 return min; 234 } 235 }