| /* |
| * Copyright (c) 2016, 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.consumer; |
| |
| import java.io.Closeable; |
| import java.io.EOFException; |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.NoSuchFileException; |
| import java.nio.file.Path; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| |
| import jdk.jfr.EventType; |
| import jdk.jfr.internal.MetadataDescriptor; |
| import jdk.jfr.internal.Type; |
| import jdk.jfr.internal.consumer.ChunkHeader; |
| import jdk.jfr.internal.consumer.ChunkParser; |
| import jdk.jfr.internal.consumer.FileAccess; |
| import jdk.jfr.internal.consumer.RecordingInput; |
| |
| /** |
| * A recording file. |
| * <p> |
| * The following example shows how read and print all events in a recording file. |
| * |
| * <pre> |
| * <code> |
| * try (RecordingFile recordingFile = new RecordingFile(Paths.get("recording.jfr"))) { |
| * while (recordingFile.hasMoreEvents()) { |
| * RecordedEvent event = recordingFile.readEvent(); |
| * System.out.println(event); |
| * } |
| * } |
| * </code> |
| * </pre> |
| * |
| * @since 9 |
| */ |
| public final class RecordingFile implements Closeable { |
| |
| private boolean isLastEventInChunk; |
| private final File file; |
| private RecordingInput input; |
| private ChunkParser chunkParser; |
| private RecordedEvent nextEvent; |
| private boolean eof; |
| |
| /** |
| * Creates a recording file. |
| * |
| * @param file the path of the file to open, not {@code null} |
| * @throws IOException if it's not a valid recording file, or an I/O error |
| * occurred |
| * @throws NoSuchFileException if the {@code file} can't be located |
| * |
| * @throws SecurityException if a security manager exists and its |
| * {@code checkRead} method denies read access to the file. |
| */ |
| public RecordingFile(Path file) throws IOException { |
| this.file = file.toFile(); |
| this.input = new RecordingInput(this.file, FileAccess.UNPRIVILIGED); |
| findNext(); |
| } |
| |
| /** |
| * Reads the next event in the recording. |
| * |
| * @return the next event, not {@code null} |
| * |
| * @throws EOFException if no more events exist in the recording file |
| * @throws IOException if an I/O error occurs. |
| * |
| * @see #hasMoreEvents() |
| */ |
| public RecordedEvent readEvent() throws IOException { |
| if (eof) { |
| ensureOpen(); |
| throw new EOFException(); |
| } |
| isLastEventInChunk = false; |
| RecordedEvent event = nextEvent; |
| nextEvent = chunkParser.readEvent(); |
| if (nextEvent == null) { |
| isLastEventInChunk = true; |
| findNext(); |
| } |
| return event; |
| } |
| |
| /** |
| * Returns {@code true} if unread events exist in the recording file, |
| * {@code false} otherwise. |
| * |
| * @return {@code true} if unread events exist in the recording, {@code false} |
| * otherwise. |
| */ |
| public boolean hasMoreEvents() { |
| return !eof; |
| } |
| |
| /** |
| * Returns a list of all event types in this recording. |
| * |
| * @return a list of event types, not {@code null} |
| * @throws IOException if an I/O error occurred while reading from the file |
| * |
| * @see #hasMoreEvents() |
| */ |
| public List<EventType> readEventTypes() throws IOException { |
| ensureOpen(); |
| MetadataDescriptor previous = null; |
| List<EventType> types = new ArrayList<>(); |
| HashSet<Long> foundIds = new HashSet<>(); |
| try (RecordingInput ri = new RecordingInput(file, FileAccess.UNPRIVILIGED)) { |
| ChunkHeader ch = new ChunkHeader(ri); |
| aggregateEventTypeForChunk(ch, null, types, foundIds); |
| while (!ch.isLastChunk()) { |
| ch = ch.nextHeader(); |
| previous = aggregateEventTypeForChunk(ch, previous, types, foundIds); |
| } |
| } |
| return types; |
| } |
| |
| List<Type> readTypes() throws IOException { |
| ensureOpen(); |
| MetadataDescriptor previous = null; |
| List<Type> types = new ArrayList<>(); |
| HashSet<Long> foundIds = new HashSet<>(); |
| try (RecordingInput ri = new RecordingInput(file, FileAccess.UNPRIVILIGED)) { |
| ChunkHeader ch = new ChunkHeader(ri); |
| ch.awaitFinished(); |
| aggregateTypeForChunk(ch, null, types, foundIds); |
| while (!ch.isLastChunk()) { |
| ch = ch.nextHeader(); |
| previous = aggregateTypeForChunk(ch, previous, types, foundIds); |
| } |
| } |
| return types; |
| } |
| |
| private MetadataDescriptor aggregateTypeForChunk(ChunkHeader ch, MetadataDescriptor previous, List<Type> types, HashSet<Long> foundIds) throws IOException { |
| MetadataDescriptor m = ch.readMetadata(previous); |
| for (Type t : m.getTypes()) { |
| if (!foundIds.contains(t.getId())) { |
| types.add(t); |
| foundIds.add(t.getId()); |
| } |
| } |
| return m; |
| } |
| |
| private static MetadataDescriptor aggregateEventTypeForChunk(ChunkHeader ch, MetadataDescriptor previous, List<EventType> types, HashSet<Long> foundIds) throws IOException { |
| MetadataDescriptor m = ch.readMetadata(previous); |
| for (EventType t : m.getEventTypes()) { |
| if (!foundIds.contains(t.getId())) { |
| types.add(t); |
| foundIds.add(t.getId()); |
| } |
| } |
| return m; |
| } |
| |
| /** |
| * Closes this recording file and releases any system resources that are |
| * associated with it. |
| * |
| * @throws IOException if an I/O error occurred |
| */ |
| public void close() throws IOException { |
| if (input != null) { |
| eof = true; |
| input.close(); |
| chunkParser = null; |
| input = null; |
| nextEvent = null; |
| } |
| } |
| |
| /** |
| * Returns a list of all events in a file. |
| * <p> |
| * This method is intended for simple cases where it's convenient to read all |
| * events in a single operation. It isn't intended for reading large files. |
| * |
| * @param path the path to the file, not {@code null} |
| * |
| * @return the events from the file as a {@code List} object; whether the |
| * {@code List} is modifiable or not is implementation dependent and |
| * therefore not specified, not {@code null} |
| * |
| * @throws IOException if an I/O error occurred, it's not a Flight Recorder |
| * file or a version of a JFR file that can't be parsed |
| * |
| * @throws SecurityException if a security manager exists and its |
| * {@code checkRead} method denies read access to the file. |
| */ |
| public static List<RecordedEvent> readAllEvents(Path path) throws IOException { |
| try (RecordingFile r = new RecordingFile(path)) { |
| List<RecordedEvent> list = new ArrayList<>(); |
| while (r.hasMoreEvents()) { |
| list.add(r.readEvent()); |
| } |
| return list; |
| } |
| } |
| |
| // package protected |
| File getFile() { |
| return file; |
| } |
| |
| // package protected |
| boolean isLastEventInChunk() { |
| return isLastEventInChunk; |
| } |
| |
| |
| // either sets next to an event or sets eof to true |
| private void findNext() throws IOException { |
| while (nextEvent == null) { |
| if (chunkParser == null) { |
| chunkParser = new ChunkParser(input); |
| } else if (!chunkParser.isLastChunk()) { |
| chunkParser = chunkParser.nextChunkParser(); |
| } else { |
| eof = true; |
| return; |
| } |
| nextEvent = chunkParser.readEvent(); |
| } |
| } |
| |
| private void ensureOpen() throws IOException { |
| if (input == null) { |
| throw new IOException("Stream Closed"); |
| } |
| } |
| } |