| /* |
| * 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.test.lib.Asserts; |
| import jdk.test.lib.Utils; |
| |
| /** |
| * @test |
| * @summary Verifies that all methods in RecordingFIle are working |
| * @key jfr |
| * @requires vm.hasJFR |
| * @library /test/lib |
| * @run main/othervm -Xlog:jfr*=info 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 = Utils.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 <shouldExist.length; i++) { |
| sb.add(Integer.toString(shouldExist[i])); |
| } |
| System.out.println("Verifying recordings: disk=" + disk + " background=" + background); |
| System.out.println("Should exist: " + Arrays.toString(shouldExist)); |
| System.out.println("Should not exist: " + Arrays.toString(shouldNotExist)); |
| |
| Path p = Utils.createTempFile(sb.toString(), ".jfr"); |
| System.out.println("Filename: " + p); |
| try (Recording r = new Recording()) { |
| r.start(); |
| r.stop(); |
| r.dump(p); |
| try (RecordingFile f = new RecordingFile(p)) { |
| List<EventType> 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 = Utils.createTempFile("two-event-types", ".jfr"); |
| Path threeEventTypes = Utils.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<EventType> types = f.readEventTypes(); |
| assertUniqueEventTypes(types); |
| assertHasEventType(types, "Event1"); |
| assertHasEventType(types, "Event2"); |
| assertMissingEventType(types, "Event3"); |
| } |
| try (RecordingFile f = new RecordingFile(threeEventTypes)) { |
| List<EventType> types = f.readEventTypes(); |
| assertUniqueEventTypes(types); |
| assertHasEventType(types, "Event1"); |
| assertHasEventType(types, "Event2"); |
| assertHasEventType(types, "Event3"); |
| } |
| |
| } |
| |
| private static void assertMissingEventType(List<EventType> 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<EventType> 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<EventType> types, String name) { |
| for (EventType t : types) { |
| if (t.getName().equals(name)) { |
| return t; |
| } |
| } |
| return null; |
| } |
| |
| private static void assertUniqueEventTypes(List<EventType> types) { |
| HashSet<Long> 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 = Utils.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 = Utils.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<EventType> 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 = Utils.createTempFile("test-empty-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 |
| } |
| } |
| } |