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 }