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.util.ArrayList;
  29 import java.util.Collection;
  30 import java.util.Collections;
  31 import java.util.HashMap;
  32 import java.util.HashSet;
  33 import java.util.LinkedHashMap;
  34 import java.util.List;
  35 import java.util.Map;
  36 import java.util.Map.Entry;
  37 import java.util.Set;
  38 import java.util.StringJoiner;
  39 
  40 import jdk.jfr.Event;
  41 import jdk.jfr.internal.handlers.EventHandler;
  42 
  43 final class SettingsManager {
  44 
  45     private static class InternalSetting {
  46 
  47         private final String identifier;
  48         private Map<String, Set<String>> enabledMap = new LinkedHashMap<>(5);
  49         private Map<String, Set<String>> allMap = new LinkedHashMap<>(5);
  50         private boolean enabled;
  51 
  52         /**
  53          * Settings identifier, for example "com.example.HelloWorld" or "56"
  54          * (id of event)
  55          *
  56          * @param settingsId
  57          */
  58         public InternalSetting(String settingsId) {
  59             this.identifier = settingsId;
  60         }
  61 
  62         public Set<String> getValues(String key) {
  63             if (enabled) {
  64                 return enabledMap.get(key);
  65             } else {
  66                 return allMap.get(key);
  67             }
  68         }
  69 
  70         public void add(String attribute, String value) {
  71             if ("enabled".equals(attribute) && "true".equals(value)) {
  72                 enabled = true;
  73                 allMap = null; // no need to keep these around
  74             }
  75             addToMap(enabledMap, attribute, value);
  76             if (allMap != null) {
  77                 addToMap(allMap, attribute, value);
  78             }
  79         }
  80 
  81         private void addToMap(Map<String, Set<String>> map, String attribute, String value) {
  82             Set<String> values = map.get(attribute);
  83             if (values == null) {
  84                 values = new HashSet<String>(5);
  85                 map.put(attribute, values);
  86             }
  87             values.add(value);
  88 
  89         }
  90 
  91         public String getSettingsId() {
  92             return identifier;
  93         }
  94 
  95         public void add(InternalSetting enabled) {
  96             for (Map.Entry<String, Set<String>> entry : enabled.enabledMap.entrySet()) {
  97                 for (String value : entry.getValue()) {
  98                     add(entry.getKey(), value);
  99                 }
 100             }
 101         }
 102 
 103         public boolean isEnabled() {
 104             return enabled;
 105         }
 106 
 107         @Override
 108         public String toString() {
 109             StringBuilder sb = new StringBuilder();
 110             sb.append(identifier);
 111             sb.append(": ");
 112             sb.append(enabledMap.toString());
 113             return sb.toString();
 114         }
 115 
 116         public void finish() {
 117             if (!enabled) {
 118                 // settings from disabled
 119                 // events should not impact results, but
 120                 // we can't clear enabledMap since enabled=false
 121                 // needs be there, so events that are enabled
 122                 // by default are turned off
 123                 Map<String, Set<String>> disabledMap = new HashMap<>(2);
 124                 Set<String> values = new HashSet<>(2);
 125                 values.add("false");
 126                 disabledMap.put("enabled", values);
 127                 enabledMap = disabledMap;
 128             }
 129         }
 130     }
 131 
 132    private Map<String, InternalSetting> availableSettings = new LinkedHashMap<>();
 133 
 134     void setSettings(List<Map<String, String>> activeSettings) {
 135         // store settings so they are available if a new event class is loaded
 136         availableSettings = createSettingsMap(activeSettings);
 137         List<EventControl> eventControls = MetadataRepository.getInstance().getEventControls();
 138         if (!JVM.getJVM().isRecording()) {
 139             for (EventControl ec : eventControls) {
 140                 ec.disable();
 141             }
 142         } else {
 143             if (LogTag.JFR_SETTING.shouldLog(LogLevel.INFO.level)) {
 144                 Collections.sort(eventControls, (x,y) -> x.getEventType().getName().compareTo(y.getEventType().getName()));
 145             }
 146             for (EventControl ec : eventControls) {
 147                 setEventControl(ec);
 148             }
 149         }
 150         if (JVM.getJVM().getAllowedToDoEventRetransforms()) {
 151             updateRetransform(JVM.getJVM().getAllEventClasses());
 152         }
 153     }
 154 
 155     public void updateRetransform(List<Class<? extends Event>> eventClasses) {
 156         List<Class<?>> classes = new ArrayList<>();
 157         for(Class<? extends Event> eventClass: eventClasses) {
 158             EventHandler eh = Utils.getHandler(eventClass);
 159             if (eh != null ) {
 160                 PlatformEventType eventType = eh.getPlatformEventType();
 161                 if (eventType.isMarkedForInstrumentation()) {
 162                     classes.add(eventClass);
 163                     eventType.markForInstrumentation(false);
 164                     // A bit premature to set it here, but hard to check
 165                     // after call to retransformClasses.
 166                     eventType.setInstrumented();
 167                 }
 168             }
 169         }
 170         if (!classes.isEmpty()) {
 171             JVM.getJVM().retransformClasses(classes.toArray(new Class<?>[0]));
 172         }
 173     }
 174 
 175     private Map<String, InternalSetting> createSettingsMap(List<Map<String,String>> activeSettings) {
 176         Map<String, InternalSetting> map = new LinkedHashMap<>(activeSettings.size());
 177         for (Map<String, String> rec : activeSettings) {
 178             for (InternalSetting internal : makeInternalSettings(rec)) {
 179                 InternalSetting is = map.get(internal.getSettingsId());
 180                 if (is == null) {
 181                     map.put(internal.getSettingsId(), internal);
 182                 } else {
 183                     is.add(internal);
 184                 }
 185             }
 186         }
 187         return map;
 188     }
 189 
 190     private Collection<InternalSetting> makeInternalSettings(Map<String, String> rec) {
 191         Map<String, InternalSetting> internals = new LinkedHashMap<>();
 192         for (Map.Entry<String, String> entry : rec.entrySet()) {
 193             String key = entry.getKey();
 194             String value = entry.getValue();
 195             int index = key.indexOf("#");
 196             if (index > 1 && index < key.length() - 2) {
 197                 String eventName = key.substring(0, index);
 198                 eventName = Utils.upgradeLegacyJDKEvent(eventName);
 199                 InternalSetting s = internals.get(eventName);
 200                 String settingName = key.substring(index + 1).trim();
 201                 if (s == null) {
 202                     s = new InternalSetting(eventName);
 203                     internals.put(eventName, s);
 204                 }
 205                 s.add(settingName, value);
 206             }
 207         }
 208       for (InternalSetting s : internals.values()) {
 209          s.finish();
 210       }
 211 
 212       return internals.values();
 213     }
 214 
 215     void setEventControl(EventControl ec) {
 216         InternalSetting is = getInternalSetting(ec);
 217         Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "Applied settings for " + ec.getEventType().getLogName() + " {");
 218         for (Entry<String, Control> entry : ec.getEntries()) {
 219             Set<String> values = null;
 220             String settingName = entry.getKey();
 221             if (is != null) {
 222                 values = is.getValues(settingName);
 223             }
 224             Control control = entry.getValue();
 225             if (values != null) {
 226                 control.apply(values);
 227                 String after = control.getLastValue();
 228                 if (LogTag.JFR_SETTING.shouldLog(LogLevel.INFO.level)) {
 229                     if (Utils.isSettingVisible(control, ec.getEventType().hasEventHook())) {
 230                         if (values.size() > 1) {
 231                             StringJoiner sj = new StringJoiner(", ", "{", "}");
 232                             for (String s : values) {
 233                                 sj.add("\"" + s + "\"");
 234                             }
 235                             String message = "  " + settingName + "= " + sj.toString() + " => \"" + after + "\"";
 236                             Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, message);
 237                         } else {
 238                             String message = "  " + settingName + "=\"" + control.getLastValue() + "\"";
 239                             Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, message);
 240                         }
 241                     }
 242                 }
 243             } else {
 244                 control.setDefault();
 245                 if (LogTag.JFR_SETTING.shouldLog(LogLevel.INFO.level)) {
 246                     String message = "  " + settingName + "=\"" + control.getLastValue() + "\"";
 247                     Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, message);
 248                 }
 249             }
 250         }
 251         ec.writeActiveSettingEvent();
 252         Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "}");
 253     }
 254 
 255     private InternalSetting getInternalSetting(EventControl ec) {
 256         String name = ec.getEventType().getName();
 257         InternalSetting nameBased = availableSettings.get(name);
 258         InternalSetting idBased = availableSettings.get(ec.getSettingsId());
 259 
 260         if (nameBased == null && idBased == null) {
 261             return null;
 262         }
 263         if (idBased == null) {
 264             return nameBased;
 265         }
 266         if (nameBased == null) {
 267             return idBased;
 268         }
 269         InternalSetting mixed = new InternalSetting(nameBased.getSettingsId());
 270         mixed.add(nameBased);
 271         mixed.add(idBased);
 272         return mixed;
 273     }
 274 
 275     @Override
 276     public String toString() {
 277         StringBuilder sb = new StringBuilder();
 278         for (InternalSetting enabled : availableSettings.values()) {
 279             sb.append(enabled.toString());
 280             sb.append("\n");
 281         }
 282         return sb.toString();
 283     }
 284 
 285     boolean isEnabled(String eventName) {
 286         InternalSetting is = availableSettings.get(eventName);
 287         if (is == null) {
 288             return false;
 289         }
 290         return is.isEnabled();
 291     }
 292 }