1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  *
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * The contents of this file are subject to the terms of either the Universal Permissive License
   7  * v 1.0 as shown at http://oss.oracle.com/licenses/upl
   8  *
   9  * or the following license:
  10  *
  11  * Redistribution and use in source and binary forms, with or without modification, are permitted
  12  * provided that the following conditions are met:
  13  *
  14  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
  15  * and the following disclaimer.
  16  *
  17  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
  18  * conditions and the following disclaimer in the documentation and/or other materials provided with
  19  * the distribution.
  20  *
  21  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
  22  * endorse or promote products derived from this software without specific prior written permission.
  23  *
  24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  26  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  27  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  30  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  31  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32  */
  33 package org.openjdk.jmc.flightrecorder.parser.synthetic;
  34 
  35 import static org.openjdk.jmc.common.item.Attribute.attr;
  36 import static org.openjdk.jmc.common.unit.UnitLookup.FLAG;
  37 import static org.openjdk.jmc.common.unit.UnitLookup.TIMESPAN;
  38 
  39 import java.util.ArrayList;
  40 import java.util.Arrays;
  41 import java.util.HashMap;
  42 import java.util.List;
  43 import java.util.Map;
  44 
  45 import org.openjdk.jmc.common.item.IAttribute;
  46 import org.openjdk.jmc.common.unit.IQuantity;
  47 import org.openjdk.jmc.common.util.LabeledIdentifier;
  48 import org.openjdk.jmc.flightrecorder.JfrAttributes;
  49 import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes;
  50 import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs;
  51 import org.openjdk.jmc.flightrecorder.parser.IEventSink;
  52 import org.openjdk.jmc.flightrecorder.parser.IEventSinkFactory;
  53 import org.openjdk.jmc.flightrecorder.parser.ValueField;
  54 
  55 /**
  56  * Event sink that transforms pre JDK 11 event types to their equivalent JDK 11 types. JDK 11 input
  57  * data will be passed through mostly untouched.
  58  */
  59 class SettingsTransformer implements IEventSink {
  60 
  61         /**
  62          * Fix for JDK-8157024, the code cache stats unallocatedCapacity event is written as KiB but
  63          * reported as B. This is fixed in JDK9, but we need to perform this transformation for
  64          * recordings from JDK8 and earlier.
  65          */
  66         private static class FixCodeCacheSink implements IEventSink {
  67 
  68                 private int unallocatedFieldIndex;
  69                 private IEventSink subSink;
  70 
  71                 public FixCodeCacheSink(int unallocatedFieldIndex, IEventSink subSink) {
  72                         this.unallocatedFieldIndex = unallocatedFieldIndex;
  73                         this.subSink = subSink;
  74                 }
  75 
  76                 @Override
  77                 public void addEvent(Object[] values) {
  78                         if (values[unallocatedFieldIndex] instanceof IQuantity) {
  79                                 values[unallocatedFieldIndex] = ((IQuantity) values[unallocatedFieldIndex]).multiply(1024);
  80                         }
  81                         subSink.addEvent(values);
  82                 }
  83 
  84         }
  85 
  86         // TODO: Break out constants?
  87         static final String REC_SETTING_NAME_ENABLED = "enabled"; //$NON-NLS-1$
  88         static final String REC_SETTING_NAME_STACKTRACE = "stacktrace"; //$NON-NLS-1$
  89         static final String REC_SETTING_NAME_THRESHOLD = "threshold"; //$NON-NLS-1$
  90         static final String REC_SETTING_NAME_PERIOD = "period"; //$NON-NLS-1$
  91 
  92         static final String REC_SETTING_PERIOD_EVERY_CHUNK = "everyChunk"; //$NON-NLS-1$
  93 
  94         private static final IAttribute<Boolean> REC_SETTINGS_ATTR_ENABLED = attr("enabled", //$NON-NLS-1$
  95                         Messages.getString(Messages.SettingsTransformer_REC_SETTINGS_ATTR_ENABLED), FLAG);
  96         private static final IAttribute<Boolean> REC_SETTINGS_ATTR_STACKTRACE = attr("stacktrace", //$NON-NLS-1$
  97                         Messages.getString(Messages.SettingsTransformer_REC_SETTINGS_ATTR_STACKTRACE), FLAG);
  98         static final IAttribute<IQuantity> REC_SETTINGS_ATTR_THRESHOLD = attr("threshold", //$NON-NLS-1$
  99                         Messages.getString(Messages.SettingsTransformer_REC_SETTINGS_ATTR_THRESHOLD), TIMESPAN);
 100         static final IAttribute<IQuantity> REC_SETTINGS_ATTR_PERIOD = attr("period", //$NON-NLS-1$
 101                         Messages.getString(Messages.SettingsTransformer_REC_SETTINGS_ATTR_PERIOD), TIMESPAN);
 102         private static final List<ValueField> FIELDS = Arrays.asList(new ValueField(JfrAttributes.END_TIME),
 103                         new ValueField(SyntheticAttributeExtension.REC_SETTING_EVENT_ID_ATTRIBUTE),
 104                         new ValueField(JdkAttributes.REC_SETTING_NAME), new ValueField(JdkAttributes.REC_SETTING_VALUE));
 105 
 106         // Renamed attributes from pre JDK 11: <event id, <pre 11 attribute id, 11 attribute id>>
 107         private static final Map<String, Map<String, String>> attributeRenameMap;
 108 
 109         // JDK-8157024 constant for the field id
 110         private static final String UNALLOCATED_CAPACITY_FIELD_ID = "unallocatedCapacity"; //$NON-NLS-1$
 111 
 112         private final IEventSink sink;
 113         private final Object[] reusableArray = new Object[FIELDS.size()];
 114         private int endTimeIndex = -1;
 115         private int typeIndex = -1;
 116         private int enabledIndex = -1;
 117         private int stacktraceIndex = -1;
 118         private int thresholdIndex = -1;
 119         private int periodIndex = -1;
 120 
 121         static {
 122                 attributeRenameMap = buildRenameMap();
 123         }
 124 
 125         @SuppressWarnings("nls")
 126         private static HashMap<String, Map<String, String>> buildRenameMap() {
 127                 // NOTE: Replace the last string argument with an identifier reference if a matching one is added to JfrAttributes.
 128                 HashMap<String, Map<String, String>> map = new HashMap<>();
 129                 addRenameEntry(map, JdkTypeIDsPreJdk11.THREAD_PARK, "klass", "parkedClass");
 130                 addRenameEntry(map, JdkTypeIDsPreJdk11.MONITOR_ENTER, "klass", JdkAttributes.MONITOR_CLASS.getIdentifier());
 131                 addRenameEntry(map, JdkTypeIDsPreJdk11.MONITOR_WAIT, "klass", JdkAttributes.MONITOR_CLASS.getIdentifier());
 132                 addRenameEntry(map, JdkTypeIDsPreJdk11.INT_FLAG_CHANGED, "old_value", "oldValue");
 133                 addRenameEntry(map, JdkTypeIDsPreJdk11.INT_FLAG_CHANGED, "new_value", "newValue");
 134                 addRenameEntry(map, JdkTypeIDsPreJdk11.UINT_FLAG_CHANGED, "old_value", "oldValue");
 135                 addRenameEntry(map, JdkTypeIDsPreJdk11.UINT_FLAG_CHANGED, "new_value", "newValue");
 136                 addRenameEntry(map, JdkTypeIDsPreJdk11.LONG_FLAG_CHANGED, "old_value", "oldValue");
 137                 addRenameEntry(map, JdkTypeIDsPreJdk11.LONG_FLAG_CHANGED, "new_value", "newValue");
 138                 addRenameEntry(map, JdkTypeIDsPreJdk11.ULONG_FLAG_CHANGED, "old_value", "oldValue");
 139                 addRenameEntry(map, JdkTypeIDsPreJdk11.ULONG_FLAG_CHANGED, "new_value", "newValue");
 140                 addRenameEntry(map, JdkTypeIDsPreJdk11.DOUBLE_FLAG_CHANGED, "old_value", "oldValue");
 141                 addRenameEntry(map, JdkTypeIDsPreJdk11.DOUBLE_FLAG_CHANGED, "new_value", "newValue");
 142                 addRenameEntry(map, JdkTypeIDsPreJdk11.BOOLEAN_FLAG_CHANGED, "old_value", "oldValue");
 143                 addRenameEntry(map, JdkTypeIDsPreJdk11.BOOLEAN_FLAG_CHANGED, "new_value", "newValue");
 144                 addRenameEntry(map, JdkTypeIDsPreJdk11.STRING_FLAG_CHANGED, "old_value", "oldValue");
 145                 addRenameEntry(map, JdkTypeIDsPreJdk11.STRING_FLAG_CHANGED, "new_value", "newValue");
 146                 addRenameEntry(map, JdkTypeIDsPreJdk11.GC_DETAILED_EVACUATION_INFO, "allocRegionsUsedBefore",
 147                                 "allocationRegionsUsedBefore");
 148                 addRenameEntry(map, JdkTypeIDsPreJdk11.GC_DETAILED_EVACUATION_INFO, "allocRegionsUsedAfter",
 149                                 "allocationRegionsUsedAfter");
 150                 addRenameEntry(map, JdkTypeIDsPreJdk11.SWEEP_CODE_CACHE, "sweepIndex", "sweepId");
 151                 addRenameEntry(map, JdkTypeIDsPreJdk11.ALLOC_INSIDE_TLAB, "class",
 152                                 JdkAttributes.ALLOCATION_CLASS.getIdentifier());
 153                 addRenameEntry(map, JdkTypeIDsPreJdk11.ALLOC_OUTSIDE_TLAB, "class",
 154                                 JdkAttributes.ALLOCATION_CLASS.getIdentifier());
 155                 addRenameEntry(map, JdkTypeIDsPreJdk11.OBJECT_COUNT, "class", JdkAttributes.OBJECT_CLASS.getIdentifier());
 156                 addRenameEntry(map, JdkTypeIDsPreJdk11.COMPILER_PHASE, "compileID",
 157                                 JdkAttributes.COMPILER_COMPILATION_ID.getIdentifier());
 158                 addRenameEntry(map, JdkTypeIDsPreJdk11.COMPILATION, "compileID",
 159                                 JdkAttributes.COMPILER_COMPILATION_ID.getIdentifier());
 160                 addRenameEntry(map, JdkTypeIDsPreJdk11.COMPILER_FAILURE, "compileID",
 161                                 JdkAttributes.COMPILER_COMPILATION_ID.getIdentifier());
 162                 addRenameEntry(map, JdkTypeIDsPreJdk11.COMPILER_FAILURE, "failure",
 163                                 JdkAttributes.COMPILER_FAILED_MESSAGE.getIdentifier());
 164                 addRenameEntry(map, JdkTypeIDsPreJdk11.GC_DETAILED_OBJECT_COUNT_AFTER_GC, "class",
 165                                 JdkAttributes.OBJECT_CLASS.getIdentifier());
 166                 return map;
 167         }
 168 
 169         private static void addRenameEntry(
 170                 Map<String, Map<String, String>> renameMap, String eventId, String pre9AttrId, String attrId) {
 171                 Map<String, String> attrMap = renameMap.get(eventId);
 172                 if (attrMap == null) {
 173                         attrMap = new HashMap<>();
 174                         renameMap.put(eventId, attrMap);
 175                 }
 176                 attrMap.put(pre9AttrId, attrId);
 177         }
 178 
 179         SettingsTransformer(IEventSinkFactory sinkFactory, String label, String[] category, String description,
 180                         List<ValueField> dataStructure) {
 181                 for (int i = 0; i < dataStructure.size(); i++) {
 182                         ValueField vf = dataStructure.get(i);
 183                         if (vf.matches(JfrAttributes.END_TIME)) {
 184                                 endTimeIndex = i;
 185                         } else if (vf.matches(SyntheticAttributeExtension.REC_SETTING_EVENT_ID_ATTRIBUTE)) {
 186                                 typeIndex = i;
 187                         } else if (vf.matches(REC_SETTINGS_ATTR_ENABLED)) {
 188                                 enabledIndex = i;
 189                         } else if (vf.matches(REC_SETTINGS_ATTR_STACKTRACE)) {
 190                                 stacktraceIndex = i;
 191                         } else if (vf.matches(REC_SETTINGS_ATTR_THRESHOLD)) {
 192                                 thresholdIndex = i;
 193                         } else if (vf.matches(REC_SETTINGS_ATTR_PERIOD)) {
 194                                 periodIndex = i;
 195                         }
 196                 }
 197                 if (endTimeIndex >= 0) {
 198                         sink = sinkFactory.create(JdkTypeIDs.RECORDING_SETTING, label, category, description, FIELDS);
 199                 } else {
 200                         sink = sinkFactory.create(JdkTypeIDs.RECORDING_SETTING, label, category, description, dataStructure);
 201                 }
 202         }
 203 
 204         boolean isValid() {
 205                 return endTimeIndex >= 0 && typeIndex >= 0 && enabledIndex >= 0 && stacktraceIndex >= 0 && thresholdIndex >= 0
 206                                 && periodIndex >= 0;
 207         }
 208 
 209         boolean isValidV1() {
 210                 return typeIndex >= 0;
 211         }
 212 
 213         @Override
 214         public void addEvent(Object[] values) {
 215                 LabeledIdentifier type = (LabeledIdentifier) values[typeIndex];
 216                 if (type != null) {
 217                         type = new LabeledIdentifier(JdkTypeIDsPreJdk11.translate(type.getInterfaceId()),
 218                                 type.getImplementationId(), type.getName(), type.getDeclaredDescription());
 219                         if (endTimeIndex < 0) {
 220                                 values[typeIndex] = type;
 221                                 sink.addEvent(values);
 222                                 return;
 223                         }
 224                 }
 225                 Object startTime = values[endTimeIndex];
 226 
 227                 addSettingEvent(startTime, type, REC_SETTING_NAME_ENABLED, values[enabledIndex]);
 228                 addSettingEvent(startTime, type, REC_SETTING_NAME_STACKTRACE, values[stacktraceIndex]);
 229                 addThresholdSettingEvent(startTime, type, (IQuantity) values[thresholdIndex]);
 230                 addPeriodSettingEvent(startTime, type, (IQuantity) values[periodIndex]);
 231         }
 232 
 233         private boolean addThresholdSettingEvent(Object startTime, LabeledIdentifier type, IQuantity quantity) {
 234                 // Remove thresholds with Long.MIN_VALUE ns duration as these are just padding for
 235                 // event types that cannot have thresholds. (At least JDK 7u75 used Long.MAX_VALUE.)
 236                 if (quantity != null) {
 237                         long numQuantity = quantity.longValue();
 238                         if ((numQuantity != Long.MIN_VALUE) && (numQuantity != Long.MAX_VALUE)) {
 239                                 addSettingEvent(startTime, type, REC_SETTING_NAME_THRESHOLD, quantity.persistableString());
 240                         }
 241                 }
 242                 return false;
 243         }
 244 
 245         private boolean addPeriodSettingEvent(Object startTime, LabeledIdentifier type, IQuantity quantity) {
 246                 // Similar to threshold. Seems to be similar but almost the opposite for period, which at least in JDK 8u40
 247                 // uses Long.MIN_VALUE ms for event types that can have a period, but none have been defined.
 248                 if (quantity != null) {
 249                         long numQuantity = quantity.longValue();
 250                         if (numQuantity == 0L) {
 251                                 addSettingEvent(startTime, type, REC_SETTING_NAME_PERIOD, REC_SETTING_PERIOD_EVERY_CHUNK);
 252                         } else if ((numQuantity != Long.MIN_VALUE) && (numQuantity != Long.MAX_VALUE)) {
 253                                 addSettingEvent(startTime, type, REC_SETTING_NAME_PERIOD, quantity.persistableString());
 254                         }
 255                 }
 256                 return false;
 257         }
 258 
 259         private void addSettingEvent(Object startTime, LabeledIdentifier type, String settingName, Object settingValue) {
 260                 reusableArray[0] = startTime;
 261                 reusableArray[1] = type;
 262                 reusableArray[2] = settingName;
 263                 reusableArray[3] = settingValue == null ? null : settingValue.toString();
 264                 sink.addEvent(reusableArray);
 265         }
 266 
 267         /*
 268          * FIXME: Weird to explicitly wrap when the parser does exactly that.
 269          *
 270          * This class should be refactored into a parser extension although this may require a change to
 271          * the API by adding priorities so that type transformation occurs before synthetic attributes
 272          * are added.
 273          */
 274         static IEventSinkFactory wrapSinkFactory(final IEventSinkFactory subFactory) {
 275                 return new IEventSinkFactory() {
 276 
 277                         @Override
 278                         public IEventSink create(
 279                                 String identifier, String label, String[] category, String description,
 280                                 List<ValueField> dataStructure) {
 281                                 if (JdkTypeIDsPreJdk11.RECORDING_SETTING.equals(identifier) ||
 282                                         JdkTypeIDsPreJdk11.JDK9_RECORDING_SETTING.equals(identifier)) {
 283                                         SettingsTransformer st = new SettingsTransformer(subFactory, label, category, description,
 284                                                         dataStructure);
 285                                         if (st.isValid() || st.isValidV1()) {
 286                                                 return st;
 287                                         } else {
 288                                                 // FIXME: Avoid System.err.println
 289                                                 System.err
 290                                                                 .println("Cannot create SettingsTransformer from fields: " + dataStructure.toString()); //$NON-NLS-1$
 291                                         }
 292                                 } else if (JdkTypeIDsPreJdk11.RECORDINGS.equals(identifier)) {
 293                                         /*
 294                                          * NOTE: Renaming 'duration' and 'startTime' attributes for JDK 8 'Recording'
 295                                          * events so that they won't conflict with general attributes with the same
 296                                          * names in JDK 9+ recordings.
 297                                          */
 298                                         ValueField[] struct = new ValueField[dataStructure.size()];
 299                                         for (int i = 0; i < struct.length; i++) {
 300                                                 ValueField vf = dataStructure.get(i);
 301                                                 if (vf.matches(JfrAttributes.START_TIME)) {
 302                                                         vf = new ValueField(JdkAttributes.RECORDING_START);
 303                                                 } else if (vf.matches(JfrAttributes.DURATION)) {
 304                                                         vf = new ValueField(JdkAttributes.RECORDING_DURATION);
 305                                                 }
 306                                                 struct[i] = vf;
 307                                         }
 308                                         return subFactory.create(JdkTypeIDs.RECORDINGS, label, category, description,
 309                                                         Arrays.asList(struct));
 310                                 } else if (JdkTypeIDsPreJdk11.CODE_CACHE_STATISTICS.equals(identifier)) {
 311                                         for (int i = 0; i < dataStructure.size(); i++) {
 312                                                 if (UNALLOCATED_CAPACITY_FIELD_ID.equals(dataStructure.get(i).getIdentifier())) {
 313                                                         return new FixCodeCacheSink(i, subFactory.create(JdkTypeIDsPreJdk11.translate(identifier),
 314                                                                         label, category, description, dataStructure));
 315                                                 }
 316                                         }
 317                                 }
 318                                 return subFactory.create(JdkTypeIDsPreJdk11.translate(identifier), label, category, description,
 319                                                 translate(identifier, dataStructure));
 320                         }
 321 
 322                         private List<ValueField> translate(String identifier, List<ValueField> dataStructure) {
 323                                 Map<String, String> attrMap = attributeRenameMap.get(identifier);
 324                                 if (attrMap == null) {
 325                                         return dataStructure;
 326                                 }
 327                                 List<ValueField> renamedDataStructure = new ArrayList<>();
 328                                 for (ValueField vf : dataStructure) {
 329                                         String renamedId = attrMap.get(vf.getIdentifier());
 330                                         if (renamedId == null) {
 331                                                 renamedDataStructure.add(vf);
 332                                         } else {
 333                                                 renamedDataStructure
 334                                                                 .add(new ValueField(renamedId, vf.getName(), vf.getDescription(), vf.getContentType()));
 335                                         }
 336                                 }
 337                                 return renamedDataStructure;
 338                         }
 339 
 340                         @Override
 341                         public void flush() {
 342                                 subFactory.flush();
 343                         }
 344                 };
 345         }
 346 
 347 }