--- old/core/org.openjdk.jmc.flightrecorder/META-INF/MANIFEST.MF 2018-12-18 16:00:33.000000000 +0100 +++ new/core/org.openjdk.jmc.flightrecorder/META-INF/MANIFEST.MF 2018-12-18 16:00:33.000000000 +0100 @@ -12,7 +12,8 @@ org.openjdk.jmc.flightrecorder.memleak, org.openjdk.jmc.flightrecorder.parser, org.openjdk.jmc.flightrecorder.parser.filter, - org.openjdk.jmc.flightrecorder.stacktrace + org.openjdk.jmc.flightrecorder.stacktrace, + org.openjdk.jmc.flightrecorder.util Require-Bundle: org.openjdk.jmc.common;visibility:=reexport Eclipse-BuddyPolicy: app Eclipse-ExtensibleAPI: true --- old/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/internal/parser/v1/ChunkLoaderV1.java 2018-12-18 16:00:34.000000000 +0100 +++ new/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/internal/parser/v1/ChunkLoaderV1.java 2018-12-18 16:00:34.000000000 +0100 @@ -80,17 +80,16 @@ // Read events long index = header.getBodyStartOffset(); - while (true) { + while (index < header.getChunkSize()) { input.seek(index); int size = input.readInt(); long type = input.readLong(); - if (type == ChunkMetadata.METADATA_EVENT_TYPE) { - return data; - } else if (type != CONSTANT_POOL_EVENT_TYPE) { + if (type != CONSTANT_POOL_EVENT_TYPE && type != ChunkMetadata.METADATA_EVENT_TYPE) { manager.readEvent(type, input); } index += size; } + return data; } private static long readConstantPoolEvent(IDataInput input, TypeManager manager) --- old/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/util/ChunkReader.java 2018-12-18 16:00:35.000000000 +0100 +++ new/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/util/ChunkReader.java 2018-12-18 16:00:34.000000000 +0100 @@ -66,6 +66,10 @@ private static final int HEADER_SIZE = DataInputToolkit.INTEGER_SIZE + 2 * DataInputToolkit.SHORT_SIZE + DataInputToolkit.LONG_SIZE; + private ChunkReader() { + throw new UnsupportedOperationException("Not to be instantiated"); //$NON-NLS-1$ + } + /** * Chunk iterator for an uncompressed JFR file. Efficiently reads a JFR file, chunk by chunk, * into memory as byte arrays by memory mapping the JFR file, finding the chunk boundaries with --- old/core/tests/org.openjdk.jmc.common.test/src/test/java/org/openjdk/jmc/common/test/TestToolkit.java 2018-12-18 16:00:35.000000000 +0100 +++ new/core/tests/org.openjdk.jmc.common.test/src/test/java/org/openjdk/jmc/common/test/TestToolkit.java 2018-12-18 16:00:35.000000000 +0100 @@ -93,6 +93,14 @@ } return new IOResourceSet(resources); } + + public static IOResource getNamedResource(Class clazz, String directory, String fileName) throws IOException { + String resourceName = directory + '/' + fileName; + if (clazz.getClassLoader().getResource(resourceName) == null) { + throw new IOException("Resource not found: " + resourceName); + } + return new ResourceResource(clazz, directory, fileName); + } /** * Asserts that two resource have the same textual content. The resource are compared line by --- old/core/tests/org.openjdk.jmc.flightrecorder.test/src/test/java/org/openjdk/jmc/flightrecorder/test/util/RecordingToolkit.java 2018-12-18 16:00:36.000000000 +0100 +++ new/core/tests/org.openjdk.jmc.flightrecorder.test/src/test/java/org/openjdk/jmc/flightrecorder/test/util/RecordingToolkit.java 2018-12-18 16:00:36.000000000 +0100 @@ -41,6 +41,7 @@ import org.openjdk.jmc.common.io.IOToolkit; import org.openjdk.jmc.common.item.IItemCollection; import org.openjdk.jmc.common.test.TestToolkit; +import org.openjdk.jmc.common.test.io.IOResource; import org.openjdk.jmc.common.test.io.IOResourceSet; import org.openjdk.jmc.flightrecorder.CouldNotLoadRecordingException; import org.openjdk.jmc.flightrecorder.JfrLoaderToolkit; @@ -66,10 +67,23 @@ return TestToolkit.getResourcesInDirectory(RecordingToolkit.class, RECORDINGS_DIRECTORY, RECORDINGS_INDEXFILE); } + public static IItemCollection getNamedRecording(String recordingName) throws IOException, CouldNotLoadRecordingException { + return getFlightRecording(TestToolkit.getNamedResource(RecordingToolkit.class, RECORDINGS_DIRECTORY, recordingName)); + } + + public static InputStream getNamedRecordingResource(String recordingName) throws IOException { + return TestToolkit.getNamedResource(RecordingToolkit.class, RECORDINGS_DIRECTORY, recordingName).open(); + } + public static IItemCollection getFlightRecording(IOResourceSet resourceSet) throws IOException, CouldNotLoadRecordingException { + return getFlightRecording(resourceSet.getResource(0)); + } + + public static IItemCollection getFlightRecording(IOResource resource) + throws IOException, CouldNotLoadRecordingException { File tmpRecording = createResultFile("recordingTest", "tmp_recording", true); - InputStream is = resourceSet.getResource(0).open(); + InputStream is = resource.open(); OutputStream os = new FileOutputStream(tmpRecording); int read = 0; byte[] tmp = new byte[4096]; @@ -80,7 +94,7 @@ IOToolkit.closeSilently(is); return JfrLoaderToolkit.loadEvents(tmpRecording); } - + public static File createResultFile(String prefix, String suffix, boolean deleteTempOnExit) throws IOException { String resultDir = System.getProperty("results.dir"); File resultFile; --- /dev/null 2018-12-18 16:00:37.000000000 +0100 +++ new/core/tests/org.openjdk.jmc.flightrecorder.test/src/test/java/org/openjdk/jmc/flightrecorder/test/MetadataEventLocationUpdateTest.java 2018-12-18 16:00:36.000000000 +0100 @@ -0,0 +1,137 @@ +package org.openjdk.jmc.flightrecorder.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.junit.Test; +import org.openjdk.jmc.common.item.Aggregators; +import org.openjdk.jmc.common.item.IAggregator; +import org.openjdk.jmc.common.item.IItemCollection; +import org.openjdk.jmc.common.item.IItemIterable; +import org.openjdk.jmc.common.item.IType; +import org.openjdk.jmc.common.item.ItemFilters; +import org.openjdk.jmc.flightrecorder.CouldNotLoadRecordingException; +import org.openjdk.jmc.flightrecorder.JfrAttributes; +import org.openjdk.jmc.flightrecorder.test.util.RecordingToolkit; +import org.openjdk.jmc.flightrecorder.util.ChunkReader; + +/** + * In an upcoming release (12 or 13) the metadata event cannot be counted on being the last event in + * the chunk anymore. These tests make sure the parser still work. + */ +public final class MetadataEventLocationUpdateTest { + private static final int CHUNK_COUNT_FLUSH_RECORDINGS = 2; + private static final int CHUNK_COUNT_METADATA_RECORDINGS = 1; + private static final String[] TYPES_TO_CHECK = {"jdk.GCPhaseParallel", "jdk.CompilerInlining"}; //$NON-NLS-1$ //$NON-NLS-2$ + private static final String[] TYPES_TO_CHECK_FLUSH = {"jdk.ModuleExport", "jdk.BooleanFlag", "jdk.JavaMonitorWait"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + private static final long[] EXPECTED_COUNTS_CONTROL = {12584, 7283}; + private static final long[] EXPECTED_COUNTS_NEW = {27738, 6883}; + + private static final long[] EXPECTED_COUNTS_FLUSH_CONTROL = {1512, 1500, 991}; + private static final long[] EXPECTED_COUNTS_FLUSH_INCREMENTAL = {1512, 1500, 860}; + + private static final int EXPECTED_NUMBER_OF_TYPES_FLUSH_RECORDINGS = 133; + + private static final String RECORDING_METADATA_CONTROL = "metadata_control.jfr"; //$NON-NLS-1$ + private static final String RECORDING_METADATA_NEW = "metadata_new.jfr"; //$NON-NLS-1$ + + private static final String RECORDING_FLUSH_METADATA = "flush_metadata.jfr"; //$NON-NLS-1$ + private static final String RECORDING_FLUSH_INCREMENTAL_METADATA = "flush_incremental_metadata.jfr"; //$NON-NLS-1$ + + @Test + public void testChunkSplitter() throws IOException, CouldNotLoadRecordingException { + // Should not be affected, but just for good measure + int controlFlushChunkCount = countChunks(ChunkReader + .readChunks(RecordingToolkit.getNamedRecordingResource(RECORDING_FLUSH_INCREMENTAL_METADATA))); + int incrementalFlushChunkCount = countChunks(ChunkReader + .readChunks(RecordingToolkit.getNamedRecordingResource(RECORDING_FLUSH_INCREMENTAL_METADATA))); + int controlMetadataChunkCount = countChunks( + ChunkReader.readChunks(RecordingToolkit.getNamedRecordingResource(RECORDING_METADATA_CONTROL))); + int newMetadataChunkCount = countChunks( + ChunkReader.readChunks(RecordingToolkit.getNamedRecordingResource(RECORDING_METADATA_NEW))); + + assertEquals(CHUNK_COUNT_FLUSH_RECORDINGS, controlFlushChunkCount); + assertEquals(CHUNK_COUNT_FLUSH_RECORDINGS, incrementalFlushChunkCount); + assertEquals(CHUNK_COUNT_METADATA_RECORDINGS, controlMetadataChunkCount); + assertEquals(CHUNK_COUNT_METADATA_RECORDINGS, newMetadataChunkCount); + } + + @Test + public void testGetEventTypes() throws IOException, CouldNotLoadRecordingException { + IItemCollection controlEvents = RecordingToolkit.getNamedRecording(RECORDING_METADATA_CONTROL); + IItemCollection newEvents = RecordingToolkit.getNamedRecording(RECORDING_METADATA_NEW); + + IAggregator, ?> distinctTypesAggregator = Aggregators.distinct(JfrAttributes.EVENT_TYPE_ID); + Set controlTypes = controlEvents.getAggregate(distinctTypesAggregator); + Set newTypes = newEvents.getAggregate(distinctTypesAggregator); + newTypes.removeAll(controlTypes); + // The new flush event should be the one remaining + assertTrue(newTypes.contains("jdk.Flush")); //$NON-NLS-1$ + assertEquals(1, newTypes.size()); + } + + @Test + public void testCountsInRecordings() throws IOException, CouldNotLoadRecordingException { + IItemCollection controlEvents = RecordingToolkit.getNamedRecording(RECORDING_METADATA_CONTROL); + IItemCollection newEvents = RecordingToolkit.getNamedRecording(RECORDING_METADATA_NEW); + for (int i = 0; i < TYPES_TO_CHECK.length; i++) { + String typeId = TYPES_TO_CHECK[i]; + long countControl = controlEvents.apply(ItemFilters.type(typeId)).getAggregate(Aggregators.count()) + .longValue(); + long countNew = newEvents.apply(ItemFilters.type(typeId)).getAggregate(Aggregators.count()).longValue(); + assertEquals(EXPECTED_COUNTS_CONTROL[i], countControl); + assertEquals(EXPECTED_COUNTS_NEW[i], countNew); + } + } + + @Test + public void testCountsInFlushRecordings() throws IOException, CouldNotLoadRecordingException { + IItemCollection controlFlushEvents = RecordingToolkit.getNamedRecording(RECORDING_FLUSH_METADATA); + IItemCollection incrementalFlushEvents = RecordingToolkit + .getNamedRecording(RECORDING_FLUSH_INCREMENTAL_METADATA); + for (int i = 0; i < TYPES_TO_CHECK_FLUSH.length; i++) { + String typeId = TYPES_TO_CHECK_FLUSH[i]; + long countControl = controlFlushEvents.apply(ItemFilters.type(typeId)).getAggregate(Aggregators.count()) + .longValue(); + long countNew = incrementalFlushEvents.apply(ItemFilters.type(typeId)).getAggregate(Aggregators.count()) + .longValue(); + assertEquals(EXPECTED_COUNTS_FLUSH_CONTROL[i], countControl); + assertEquals(EXPECTED_COUNTS_FLUSH_INCREMENTAL[i], countNew); + } + } + + @Test + public void testIncrementalMetadataTypeCounts() throws IOException, CouldNotLoadRecordingException { + IItemCollection controlEvents = RecordingToolkit.getNamedRecording(RECORDING_FLUSH_METADATA); + IItemCollection incremetalMetadataEvents = RecordingToolkit + .getNamedRecording(RECORDING_FLUSH_INCREMENTAL_METADATA); + Set controlTypes = getKnownTypes(controlEvents); + Set incrementalTypes = getKnownTypes(incremetalMetadataEvents); + assertEquals(EXPECTED_NUMBER_OF_TYPES_FLUSH_RECORDINGS, controlTypes.size()); + assertEquals(EXPECTED_NUMBER_OF_TYPES_FLUSH_RECORDINGS, incrementalTypes.size()); + } + + private static int countChunks(Iterator readChunks) { + int count = 0; + while (readChunks.hasNext()) { + readChunks.next(); + count++; + } + return count; + } + + private static Set getKnownTypes(IItemCollection items) { + Set types = new HashSet<>(); + Iterator iterable = items.iterator(); + while (iterable.hasNext()) { + types.add(iterable.next().getType().getIdentifier()); + } + return types; + } +} Binary files /dev/null and new/core/tests/org.openjdk.jmc.flightrecorder.test/src/test/resources/recordings/flush_incremental_metadata.jfr differ Binary files /dev/null and new/core/tests/org.openjdk.jmc.flightrecorder.test/src/test/resources/recordings/flush_metadata.jfr differ Binary files /dev/null and new/core/tests/org.openjdk.jmc.flightrecorder.test/src/test/resources/recordings/metadata_control.jfr differ Binary files /dev/null and new/core/tests/org.openjdk.jmc.flightrecorder.test/src/test/resources/recordings/metadata_new.jfr differ