/* * Copyright (c) 2018, 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; /* * @test * @summary Verifies that all methods in RecordingFIle are working * @key jfr * @library /test/lib * @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 } } }