1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.jfr.api.consumer; 27 28 import java.io.FileNotFoundException; 29 import java.io.FileWriter; 30 import java.io.IOException; 31 import java.io.RandomAccessFile; 32 import java.nio.file.Files; 33 import java.nio.file.Path; 34 import java.nio.file.Paths; 35 import java.util.Arrays; 36 import java.util.HashSet; 37 import java.util.List; 38 import java.util.StringJoiner; 39 40 41 import jdk.jfr.Event; 42 import jdk.jfr.EventType; 43 import jdk.jfr.FlightRecorder; 44 import jdk.jfr.Name; 45 import jdk.jfr.Recording; 46 import jdk.jfr.Registered; 47 import jdk.jfr.consumer.RecordedEvent; 48 import jdk.jfr.consumer.RecordingFile; 49 import jdk.test.lib.Asserts; 50 import jdk.test.lib.Utils; 51 52 /** 53 * @test 54 * @summary Verifies that all methods in RecordingFIle are working 55 * @key jfr 56 * @requires vm.hasJFR 57 * @library /test/lib 58 * @run main/othervm jdk.jfr.api.consumer.TestRecordingFile 59 */ 60 public class TestRecordingFile { 61 62 static class TestEvent1 extends Event { 63 } 64 65 static class TestEvent2 extends Event { 66 } 67 68 static class TestEvent3 extends Event { 69 } 70 71 private static String TEST_CLASS_BASE = "TestRecordingFile$TestEvent"; 72 private final static int EVENT_COUNT = 3; 73 private final static int HEADER_SIZE = 68; 74 private final static long METADATA_OFFSET = 24; 75 76 public static void main(String[] args) throws Throwable { 77 78 // create some recording data 79 Recording r = new Recording(); 80 r.enable(TestEvent1.class).withoutStackTrace(); 81 r.enable(TestEvent2.class).withoutStackTrace(); 82 r.enable(TestEvent3.class).withoutStackTrace(); 83 r.start(); 84 TestEvent1 t1 = new TestEvent1(); 85 t1.commit(); 86 TestEvent2 t2 = new TestEvent2(); 87 t2.commit(); 88 TestEvent3 t3 = new TestEvent3(); 89 t3.commit(); 90 r.stop(); 91 Path valid = Utils.createTempFile("three-event-recording", ".jfr"); 92 r.dump(valid); 93 r.close(); 94 95 Path brokenWithZeros = createBrokenWIthZeros(valid); 96 Path brokenMetadata = createBrokenMetadata(valid); 97 // prepare event sets 98 testNewRecordingFile(valid, brokenWithZeros); 99 testIterate(valid, brokenWithZeros); 100 testReadAllEvents(valid, brokenWithZeros); 101 testReadEventTypes(valid, brokenMetadata); 102 testClose(valid); 103 testReadEventTypesMultiChunk(); 104 testReadEventTypeWithUnregistration(false, false); 105 testReadEventTypeWithUnregistration(false, true); 106 testReadEventTypeWithUnregistration(true, false); 107 testReadEventTypeWithUnregistration(true, true); 108 } 109 110 private static void testReadEventTypeWithUnregistration(boolean disk, boolean background) throws Exception { 111 FlightRecorder.register(Event1.class); 112 FlightRecorder.register(Event2.class); 113 FlightRecorder.register(Event3.class); 114 Recording backgrundRecording = new Recording(); 115 if (disk) { 116 backgrundRecording.setToDisk(disk); 117 } 118 if (background) { 119 backgrundRecording.start(); 120 } 121 recordAndVerify(disk, background,new int[] {1,2, 3}, new int[] {}); 122 FlightRecorder.unregister(Event2.class); 123 recordAndVerify(disk, background, new int[] {1, 3}, new int[] {2}); 124 FlightRecorder.unregister(Event1.class); 125 FlightRecorder.register(Event2.class); 126 recordAndVerify(disk,background, new int[] {2, 3}, new int[] {1}); 127 FlightRecorder.unregister(Event3.class); 128 FlightRecorder.register(Event3.class); 129 FlightRecorder.unregister(Event2.class); 130 FlightRecorder.unregister(Event3.class); 131 FlightRecorder.register(Event1.class); 132 FlightRecorder.unregister(Event1.class); 133 FlightRecorder.register(Event1.class); 134 FlightRecorder.register(Event2.class); 135 recordAndVerify(disk, background,new int[] {1, 2}, new int[] {3}); 136 if (background) { 137 backgrundRecording.close(); 138 } 139 } 140 141 private static void recordAndVerify(boolean disk, boolean background, int[] shouldExist, int[] shouldNotExist) throws Exception { 142 StringJoiner sb = new StringJoiner("-"); 143 for (int i = 0; i <shouldExist.length; i++) { 144 sb.add(Integer.toString(shouldExist[i])); 145 } 146 System.out.println("Verifying recordings: disk=" + disk + " background=" + background); 147 System.out.println("Should exist: " + Arrays.toString(shouldExist)); 148 System.out.println("Should not exist: " + Arrays.toString(shouldNotExist)); 149 150 Path p = Utils.createTempFile(sb.toString(), ".jfr"); 151 System.out.println("Filename: " + p); 152 try (Recording r = new Recording()) { 153 r.start(); 154 r.stop(); 155 r.dump(p); 156 try (RecordingFile f = new RecordingFile(p)) { 157 List<EventType> types = f.readEventTypes(); 158 for (int i = 0; i< shouldExist.length; i++) { 159 assertHasEventType(types, "Event" + shouldExist[i]); 160 } 161 for (int i = 0; i< shouldNotExist.length; i++) { 162 assertMissingEventType(types, "Event" + shouldNotExist[i]); 163 } 164 } 165 } 166 System.out.println(); 167 System.out.println(); 168 } 169 170 @Registered(false) 171 @Name("Event1") 172 private static class Event1 extends Event { 173 } 174 @Registered(false) 175 @Name("Event2") 176 private static class Event2 extends Event { 177 } 178 @Registered(false) 179 @Name("Event3") 180 private static class Event3 extends Event { 181 } 182 183 private static void testReadEventTypesMultiChunk() throws Exception { 184 185 Path twoEventTypes = Utils.createTempFile("two-event-types", ".jfr"); 186 Path threeEventTypes = Utils.createTempFile("three-event-types", ".jfr"); 187 try (Recording r1 = new Recording()) { 188 r1.start(); 189 FlightRecorder.register(Event1.class); 190 try (Recording r2 = new Recording()) { 191 r2.start(); 192 FlightRecorder.register(Event2.class); 193 194 // Ensure that metadata are written twice. 195 try (Recording rotator = new Recording()) { 196 rotator.start(); 197 rotator.stop(); 198 } 199 r2.stop(); 200 r2.dump(twoEventTypes);; 201 } 202 FlightRecorder.register(Event3.class); 203 r1.stop(); 204 r1.dump(threeEventTypes);; 205 } 206 try (RecordingFile f = new RecordingFile(twoEventTypes)) { 207 List<EventType> types = f.readEventTypes(); 208 assertUniqueEventTypes(types); 209 assertHasEventType(types, "Event1"); 210 assertHasEventType(types, "Event2"); 211 assertMissingEventType(types, "Event3"); 212 } 213 try (RecordingFile f = new RecordingFile(twoEventTypes)) { 214 List<EventType> types = f.readEventTypes(); 215 assertUniqueEventTypes(types); 216 assertHasEventType(types, "Event1"); 217 assertHasEventType(types, "Event2"); 218 assertMissingEventType(types, "Event3"); 219 } 220 221 } 222 223 private static void assertMissingEventType(List<EventType> types,String name) throws Exception { 224 EventType type = findEventType(types, name); 225 if (type != null) { 226 throw new Exception("Found unexpected event type " + name); 227 } 228 } 229 230 private static void assertHasEventType(List<EventType> types,String name) throws Exception { 231 EventType type = findEventType(types, name); 232 if (type == null) { 233 throw new Exception("Missing event type " + name); 234 } 235 } 236 237 private static EventType findEventType(List<EventType> types, String name) { 238 for (EventType t : types) { 239 if (t.getName().equals(name)) { 240 return t; 241 } 242 } 243 return null; 244 } 245 246 private static void assertUniqueEventTypes(List<EventType> types) { 247 HashSet<Long> ids = new HashSet<>(); 248 for (EventType type : types) { 249 ids.add(type.getId()); 250 } 251 Asserts.assertEquals(types.size(), ids.size(), "Event types repeated. " + types); 252 } 253 254 private static Path createBrokenWIthZeros(Path valid) throws Exception { 255 try { 256 Path broken = Utils.createTempFile("broken-events", ".jfr"); 257 Files.delete(broken); 258 Files.copy(valid, broken); 259 RandomAccessFile raf = new RandomAccessFile(broken.toFile(), "rw"); 260 raf.seek(HEADER_SIZE); 261 int size = (int) Files.size(broken); 262 byte[] ones = new byte[size - HEADER_SIZE]; 263 for (int i = 0; i < ones.length; i++) { 264 ones[i] = (byte) 0xFF; 265 } 266 raf.write(ones, 0, ones.length); 267 raf.close(); 268 return broken; 269 } catch (IOException ioe) { 270 throw new Exception("Could not produce a broken file " + valid, ioe); 271 } 272 } 273 274 private static Path createBrokenMetadata(Path valid) throws Exception { 275 try { 276 Path broken = Utils.createTempFile("broken-metadata", ".jfr"); 277 Files.delete(broken); 278 Files.copy(valid, broken); 279 RandomAccessFile raf = new RandomAccessFile(broken.toFile(), "rw"); 280 raf.seek(METADATA_OFFSET); 281 long metadataOffset = raf.readLong(); 282 raf.seek(metadataOffset); 283 raf.writeLong(Long.MAX_VALUE); 284 raf.writeLong(Long.MAX_VALUE); 285 raf.close(); 286 return broken; 287 } catch (IOException ioe) { 288 throw new Exception("Could not produce a broken EventSet from file " + valid, ioe); 289 } 290 } 291 292 private static void testReadEventTypes(Path valid, Path broken) throws Exception { 293 try (RecordingFile validFile = new RecordingFile(valid)) { 294 List<EventType> types = validFile.readEventTypes(); 295 if (types.size() < EVENT_COUNT) { 296 throw new Exception("Expected at least " + EVENT_COUNT + " event type but got " + types.toString()); 297 } 298 int counter = 0; 299 for (Class<?> testClass : Arrays.asList(TestEvent1.class, TestEvent2.class, TestEvent3.class)) { 300 for (EventType t : types) { 301 if (t.getName().equals(testClass.getName())) { 302 counter++; 303 } 304 } 305 } 306 if (counter != 3) { 307 throw new Exception("Returned incorrect event types"); 308 } 309 } 310 try (RecordingFile brokenFile = new RecordingFile(broken)) { 311 brokenFile.readEventTypes(); 312 throw new Exception("Expected IOException when getting Event Types from broken recording"); 313 } catch (IOException ise) { 314 // OK 315 } 316 } 317 318 private static void testNewRecordingFile(Path valid, Path broken) throws Exception { 319 try (RecordingFile r = new RecordingFile(null)) { 320 throw new Exception("Expected NullPointerException"); 321 } catch (NullPointerException npe) { 322 // OK 323 } 324 try (RecordingFile r = new RecordingFile(Paths.get("hjhjsdfhkjshdfkj.jfr"))) { 325 throw new Exception("Expected FileNotFoundException"); 326 } catch (FileNotFoundException npe) { 327 // OK 328 } 329 Path testFile = Utils.createTempFile("test-empty-file", ".jfr"); 330 try (RecordingFile r = new RecordingFile(testFile)) { 331 throw new Exception("Expected IOException if file is empty"); 332 } catch (IOException e) { 333 // OK 334 } 335 FileWriter fr = new FileWriter(testFile.toFile()); 336 fr.write("whatever"); 337 fr.close(); 338 try (RecordingFile r = new RecordingFile(Paths.get("hjhjsdfhkjshdfkj.jfr"))) { 339 throw new Exception("Expected IOException if magic is incorrect"); 340 } catch (IOException e) { 341 // OK 342 } 343 344 try (RecordingFile r = new RecordingFile(valid)) { 345 } 346 } 347 348 private static void testClose(Path valid) throws Exception { 349 RecordingFile f = new RecordingFile(valid); 350 f.close(); 351 352 try { 353 f.readEvent(); 354 throw new Exception("Should not be able to read event from closed recording"); 355 } catch (IOException e) { 356 if (!e.getMessage().equals("Stream Closed")) { 357 throw new Exception("Expected 'Stream Closed' in exception message for a closed stream. Got '" + e.getMessage() +"'."); 358 } 359 // OK 360 } 361 try { 362 f.readEventTypes(); 363 throw new Exception("Should not be able to read event from closed recording"); 364 } catch (IOException e) { 365 if (!e.getMessage().equals("Stream Closed")) { 366 throw new Exception("Expected 'Stream Closed' in exception message for a closed stream. Got '" + e.getMessage() +"'."); 367 } 368 // OK 369 } 370 // close twice 371 f.close(); 372 } 373 374 private static void testIterate(Path valid, Path broken) throws Exception { 375 try (RecordingFile validFile = new RecordingFile(valid)) { 376 for (int i = 0; i < EVENT_COUNT; i++) { 377 if (!validFile.hasMoreEvents()) { 378 throw new Exception("Not all events available"); 379 } 380 RecordedEvent r = validFile.readEvent(); 381 if (r == null) { 382 throw new Exception("Missing event"); 383 } 384 if (!r.getEventType().getName().contains(TEST_CLASS_BASE)) { 385 throw new Exception("Incorrect event in recording file " + r); 386 } 387 } 388 if (validFile.hasMoreEvents()) { 389 throw new Exception("Should not be more than " + EVENT_COUNT + " in recording"); 390 } 391 } 392 try (RecordingFile brokenFile = new RecordingFile(broken)) { 393 brokenFile.readEvent(); 394 throw new Exception("Expected IOException for broken recording"); 395 } catch (IOException ise) { 396 // OK 397 } 398 } 399 400 private static void testReadAllEvents(Path valid, Path broken) throws Exception { 401 try { 402 RecordingFile.readAllEvents(broken); 403 throw new Exception("Expected IOException when reading all events for broken recording"); 404 } catch (IOException ioe) { 405 // OK as expected 406 } 407 } 408 }