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