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 }