--- /dev/null 2018-06-17 23:18:20.806999507 +0800 +++ new/test/jdk/jfr/api/consumer/TestRecordingFile.java 2019-01-29 11:00:42.546719248 +0800 @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jfr.api.consumer; + +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.StringJoiner; + + +import jdk.jfr.Event; +import jdk.jfr.EventType; +import jdk.jfr.FlightRecorder; +import jdk.jfr.Name; +import jdk.jfr.Recording; +import jdk.jfr.Registered; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingFile; +import jdk.testlibrary.Asserts; + +/* + * @test + * @summary Verifies that all methods in RecordingFIle are working + * @key jfr + * @library /lib/testlibrary + * @run main/othervm jdk.jfr.api.consumer.TestRecordingFile + */ +public class TestRecordingFile { + + static class TestEvent1 extends Event { + } + + static class TestEvent2 extends Event { + } + + static class TestEvent3 extends Event { + } + + private static String TEST_CLASS_BASE = "TestRecordingFile$TestEvent"; + private final static int EVENT_COUNT = 3; + private final static int HEADER_SIZE = 68; + private final static long METADATA_OFFSET = 24; + + public static void main(String[] args) throws Throwable { + + // create some recording data + Recording r = new Recording(); + r.enable(TestEvent1.class).withoutStackTrace(); + r.enable(TestEvent2.class).withoutStackTrace(); + r.enable(TestEvent3.class).withoutStackTrace(); + r.start(); + TestEvent1 t1 = new TestEvent1(); + t1.commit(); + TestEvent2 t2 = new TestEvent2(); + t2.commit(); + TestEvent3 t3 = new TestEvent3(); + t3.commit(); + r.stop(); + Path valid = Files.createTempFile("three-event-recording", ".jfr"); + r.dump(valid); + r.close(); + + Path brokenWithZeros = createBrokenWIthZeros(valid); + Path brokenMetadata = createBrokenMetadata(valid); + // prepare event sets + testNewRecordingFile(valid, brokenWithZeros); + testIterate(valid, brokenWithZeros); + testReadAllEvents(valid, brokenWithZeros); + testReadEventTypes(valid, brokenMetadata); + testClose(valid); + testReadEventTypesMultiChunk(); + testReadEventTypeWithUnregistration(false, false); + testReadEventTypeWithUnregistration(false, true); + testReadEventTypeWithUnregistration(true, false); + testReadEventTypeWithUnregistration(true, true); + } + + private static void testReadEventTypeWithUnregistration(boolean disk, boolean background) throws Exception { + FlightRecorder.register(Event1.class); + FlightRecorder.register(Event2.class); + FlightRecorder.register(Event3.class); + Recording backgrundRecording = new Recording(); + if (disk) { + backgrundRecording.setToDisk(disk); + } + if (background) { + backgrundRecording.start(); + } + recordAndVerify(disk, background,new int[] {1,2, 3}, new int[] {}); + FlightRecorder.unregister(Event2.class); + recordAndVerify(disk, background, new int[] {1, 3}, new int[] {2}); + FlightRecorder.unregister(Event1.class); + FlightRecorder.register(Event2.class); + recordAndVerify(disk,background, new int[] {2, 3}, new int[] {1}); + FlightRecorder.unregister(Event3.class); + FlightRecorder.register(Event3.class); + FlightRecorder.unregister(Event2.class); + FlightRecorder.unregister(Event3.class); + FlightRecorder.register(Event1.class); + FlightRecorder.unregister(Event1.class); + FlightRecorder.register(Event1.class); + FlightRecorder.register(Event2.class); + recordAndVerify(disk, background,new int[] {1, 2}, new int[] {3}); + if (background) { + backgrundRecording.close(); + } + } + + private static void recordAndVerify(boolean disk, boolean background, int[] shouldExist, int[] shouldNotExist) throws Exception { + StringJoiner sb = new StringJoiner("-"); + for (int i = 0; i types = f.readEventTypes(); + for (int i = 0; i< shouldExist.length; i++) { + assertHasEventType(types, "Event" + shouldExist[i]); + } + for (int i = 0; i< shouldNotExist.length; i++) { + assertMissingEventType(types, "Event" + shouldNotExist[i]); + } + } + } + System.out.println(); + System.out.println(); + } + + @Registered(false) + @Name("Event1") + private static class Event1 extends Event { + } + @Registered(false) + @Name("Event2") + private static class Event2 extends Event { + } + @Registered(false) + @Name("Event3") + private static class Event3 extends Event { + } + + private static void testReadEventTypesMultiChunk() throws Exception { + + Path twoEventTypes = Files.createTempFile("two-event-types", ".jfr"); + Path threeEventTypes = Files.createTempFile("three-event-types", ".jfr"); + try (Recording r1 = new Recording()) { + r1.start(); + FlightRecorder.register(Event1.class); + try (Recording r2 = new Recording()) { + r2.start(); + FlightRecorder.register(Event2.class); + + // Ensure that metadata are written twice. + try (Recording rotator = new Recording()) { + rotator.start(); + rotator.stop(); + } + r2.stop(); + r2.dump(twoEventTypes);; + } + FlightRecorder.register(Event3.class); + r1.stop(); + r1.dump(threeEventTypes);; + } + try (RecordingFile f = new RecordingFile(twoEventTypes)) { + List types = f.readEventTypes(); + assertUniqueEventTypes(types); + assertHasEventType(types, "Event1"); + assertHasEventType(types, "Event2"); + assertMissingEventType(types, "Event3"); + } + try (RecordingFile f = new RecordingFile(twoEventTypes)) { + List types = f.readEventTypes(); + assertUniqueEventTypes(types); + assertHasEventType(types, "Event1"); + assertHasEventType(types, "Event2"); + assertMissingEventType(types, "Event3"); + } + + } + + private static void assertMissingEventType(List types,String name) throws Exception { + EventType type = findEventType(types, name); + if (type != null) { + throw new Exception("Found unexpected event type " + name); + } + } + + private static void assertHasEventType(List types,String name) throws Exception { + EventType type = findEventType(types, name); + if (type == null) { + throw new Exception("Missing event type " + name); + } + } + + private static EventType findEventType(List types, String name) { + for (EventType t : types) { + if (t.getName().equals(name)) { + return t; + } + } + return null; + } + + private static void assertUniqueEventTypes(List types) { + HashSet ids = new HashSet<>(); + for (EventType type : types) { + ids.add(type.getId()); + } + Asserts.assertEquals(types.size(), ids.size(), "Event types repeated. " + types); + } + + private static Path createBrokenWIthZeros(Path valid) throws Exception { + try { + Path broken = Files.createTempFile("broken-events", ".jfr"); + Files.delete(broken); + Files.copy(valid, broken); + RandomAccessFile raf = new RandomAccessFile(broken.toFile(), "rw"); + raf.seek(HEADER_SIZE); + int size = (int) Files.size(broken); + byte[] ones = new byte[size - HEADER_SIZE]; + for (int i = 0; i < ones.length; i++) { + ones[i] = (byte) 0xFF; + } + raf.write(ones, 0, ones.length); + raf.close(); + return broken; + } catch (IOException ioe) { + throw new Exception("Could not produce a broken file " + valid, ioe); + } + } + + private static Path createBrokenMetadata(Path valid) throws Exception { + try { + Path broken = Files.createTempFile("broken-metadata", ".jfr"); + Files.delete(broken); + Files.copy(valid, broken); + RandomAccessFile raf = new RandomAccessFile(broken.toFile(), "rw"); + raf.seek(METADATA_OFFSET); + long metadataOffset = raf.readLong(); + raf.seek(metadataOffset); + raf.writeLong(Long.MAX_VALUE); + raf.writeLong(Long.MAX_VALUE); + raf.close(); + return broken; + } catch (IOException ioe) { + throw new Exception("Could not produce a broken EventSet from file " + valid, ioe); + } + } + + private static void testReadEventTypes(Path valid, Path broken) throws Exception { + try (RecordingFile validFile = new RecordingFile(valid)) { + List types = validFile.readEventTypes(); + if (types.size() < EVENT_COUNT) { + throw new Exception("Expected at least " + EVENT_COUNT + " event type but got " + types.toString()); + } + int counter = 0; + for (Class testClass : Arrays.asList(TestEvent1.class, TestEvent2.class, TestEvent3.class)) { + for (EventType t : types) { + if (t.getName().equals(testClass.getName())) { + counter++; + } + } + } + if (counter != 3) { + throw new Exception("Returned incorrect event types"); + } + } + try (RecordingFile brokenFile = new RecordingFile(broken)) { + brokenFile.readEventTypes(); + throw new Exception("Expected IOException when getting Event Types from broken recording"); + } catch (IOException ise) { + // OK + } + } + + private static void testNewRecordingFile(Path valid, Path broken) throws Exception { + try (RecordingFile r = new RecordingFile(null)) { + throw new Exception("Expected NullPointerException"); + } catch (NullPointerException npe) { + // OK + } + try (RecordingFile r = new RecordingFile(Paths.get("hjhjsdfhkjshdfkj.jfr"))) { + throw new Exception("Expected FileNotFoundException"); + } catch (FileNotFoundException npe) { + // OK + } + Path testFile = Files.createTempFile("test-file", ".jfr"); + try (RecordingFile r = new RecordingFile(testFile)) { + throw new Exception("Expected IOException if file is empty"); + } catch (IOException e) { + // OK + } + FileWriter fr = new FileWriter(testFile.toFile()); + fr.write("whatever"); + fr.close(); + try (RecordingFile r = new RecordingFile(Paths.get("hjhjsdfhkjshdfkj.jfr"))) { + throw new Exception("Expected IOException if magic is incorrect"); + } catch (IOException e) { + // OK + } + + try (RecordingFile r = new RecordingFile(valid)) { + } + } + + private static void testClose(Path valid) throws Exception { + RecordingFile f = new RecordingFile(valid); + f.close(); + + try { + f.readEvent(); + throw new Exception("Should not be able to read event from closed recording"); + } catch (IOException e) { + if (!e.getMessage().equals("Stream Closed")) { + throw new Exception("Expected 'Stream Closed' in exception message for a closed stream. Got '" + e.getMessage() +"'."); + } + // OK + } + try { + f.readEventTypes(); + throw new Exception("Should not be able to read event from closed recording"); + } catch (IOException e) { + if (!e.getMessage().equals("Stream Closed")) { + throw new Exception("Expected 'Stream Closed' in exception message for a closed stream. Got '" + e.getMessage() +"'."); + } + // OK + } + // close twice + f.close(); + } + + private static void testIterate(Path valid, Path broken) throws Exception { + try (RecordingFile validFile = new RecordingFile(valid)) { + for (int i = 0; i < EVENT_COUNT; i++) { + if (!validFile.hasMoreEvents()) { + throw new Exception("Not all events available"); + } + RecordedEvent r = validFile.readEvent(); + if (r == null) { + throw new Exception("Missing event"); + } + if (!r.getEventType().getName().contains(TEST_CLASS_BASE)) { + throw new Exception("Incorrect event in recording file " + r); + } + } + if (validFile.hasMoreEvents()) { + throw new Exception("Should not be more than " + EVENT_COUNT + " in recording"); + } + } + try (RecordingFile brokenFile = new RecordingFile(broken)) { + brokenFile.readEvent(); + throw new Exception("Expected IOException for broken recording"); + } catch (IOException ise) { + // OK + } + } + + private static void testReadAllEvents(Path valid, Path broken) throws Exception { + try { + RecordingFile.readAllEvents(broken); + throw new Exception("Expected IOException when reading all events for broken recording"); + } catch (IOException ioe) { + // OK as expected + } + } +}