1 /*
2  * Copyright (c) 2016, 2019, 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.internal.consumer;
27 
28 import java.io.IOException;
29 
30 import jdk.jfr.internal.LogLevel;
31 import jdk.jfr.internal.LogTag;
32 import jdk.jfr.internal.Logger;
33 import jdk.jfr.internal.MetadataDescriptor;
34 import jdk.jfr.internal.Utils;
35 
36 public final class ChunkHeader {
37     private static final long HEADER_SIZE = 68;
38     private static final byte UPDATING_CHUNK_HEADER = (byte) 255;
39     private static final long CHUNK_SIZE_POSITION = 8;
40     private static final long DURATION_NANOS_POSITION = 40;
41     private static final long FILE_STATE_POSITION = 64;
42     private static final long FLAG_BYTE_POSITION = 67;
43     private static final long METADATA_TYPE_ID = 0;
44     private static final byte[] FILE_MAGIC = { 'F', 'L', 'R', '\0' };
45     private static final int MASK_FINAL_CHUNK = 1 << 1;
46 
47     private final short major;
48     private final short minor;
49     private final long chunkStartTicks;
50     private final long ticksPerSecond;
51     private final long chunkStartNanos;
52     private final long absoluteChunkStart;
53     private final RecordingInput input;
54     private final long id;
55     private long absoluteEventStart;
56     private long chunkSize = 0;
57     private long constantPoolPosition = 0;
58     private long metadataPosition = 0;
59     private long durationNanos;
60     private long absoluteChunkEnd;
61     private boolean isFinished;
62     private boolean finished;
63     private boolean finalChunk;
64 
ChunkHeader(RecordingInput input)65     public ChunkHeader(RecordingInput input) throws IOException {
66         this(input, 0, 0);
67     }
68 
ChunkHeader(RecordingInput input, long absoluteChunkStart, long id)69     private ChunkHeader(RecordingInput input, long absoluteChunkStart, long id) throws IOException {
70         this.absoluteChunkStart = absoluteChunkStart;
71         this.absoluteEventStart = absoluteChunkStart + HEADER_SIZE;
72         if (input.getFileSize() < HEADER_SIZE) {
73             throw new IOException("Not a complete Chunk header");
74         }
75         input.setValidSize(absoluteChunkStart + HEADER_SIZE);
76         input.position(absoluteChunkStart);
77         if (input.position() >= input.size()) {
78            throw new IOException("Chunk contains no data");
79         }
80         verifyMagic(input);
81         this.input = input;
82         this.id = id;
83         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: " + id);
84         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: file=" + input.getFilename());
85         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startPosition=" + absoluteChunkStart);
86         major = input.readRawShort();
87         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: major=" + major);
88         minor = input.readRawShort();
89         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: minor=" + minor);
90         if (major != 1 && major != 2) {
91             throw new IOException("File version " + major + "." + minor + ". Only Flight Recorder files of version 1.x and 2.x can be read by this JDK.");
92         }
93         input.readRawLong(); // chunk size
94         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: chunkSize=" + chunkSize);
95         input.readRawLong(); // constant pool position
96         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: constantPoolPosition=" + constantPoolPosition);
97         input.readRawLong(); // metadata position
98         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: metadataPosition=" + metadataPosition);
99         chunkStartNanos = input.readRawLong(); // nanos since epoch
100         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startNanos=" + chunkStartNanos);
101         durationNanos = input.readRawLong(); // duration nanos, not used
102         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: durationNanos=" + durationNanos);
103         chunkStartTicks = input.readRawLong();
104         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startTicks=" + chunkStartTicks);
105         ticksPerSecond = input.readRawLong();
106         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: ticksPerSecond=" + ticksPerSecond);
107         input.readRawInt(); // ignore file state and flag bits
108         refresh();
109         input.position(absoluteEventStart);
110     }
111 
refresh()112     void refresh() throws IOException {
113         while (true) {
114             byte fileState1;
115             input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION);
116             while ((fileState1 = input.readPhysicalByte()) == UPDATING_CHUNK_HEADER) {
117                 Utils.takeNap(1);
118                 input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION);
119             }
120             input.positionPhysical(absoluteChunkStart + CHUNK_SIZE_POSITION);
121             long chunkSize = input.readPhysicalLong();
122             long constantPoolPosition = input.readPhysicalLong();
123             long metadataPosition = input.readPhysicalLong();
124             input.positionPhysical(absoluteChunkStart + DURATION_NANOS_POSITION);
125             long durationNanos = input.readPhysicalLong();
126             input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION);
127             byte fileState2 =  input.readPhysicalByte();
128             input.positionPhysical(absoluteChunkStart + FLAG_BYTE_POSITION);
129             int flagByte = input.readPhysicalByte();
130             if (fileState1 == fileState2) { // valid header
131                 finished = fileState1 == 0;
132                 if (metadataPosition != 0) {
133                     Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Setting input size to " + (absoluteChunkStart + chunkSize));
134                     if (finished) {
135                         // This assumes that the whole recording
136                         // is finished if the first chunk is.
137                         // This is a limitation we may want to
138                         // remove, but greatly improves performance as
139                         // data can be read across chunk boundaries
140                         // of multi-chunk files and only once.
141                         input.setValidSize(input.getFileSize());
142                     } else {
143                         input.setValidSize(absoluteChunkStart + chunkSize);
144                     }
145                     this.chunkSize = chunkSize;
146                     Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: chunkSize=" + chunkSize);
147                     this.constantPoolPosition = constantPoolPosition;
148                     Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: constantPoolPosition=" + constantPoolPosition);
149                     this.metadataPosition = metadataPosition;
150                     Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: metadataPosition=" + metadataPosition);
151                     this.durationNanos = durationNanos;
152                     Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: durationNanos =" + durationNanos);
153                     isFinished = fileState2 == 0;
154                     Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: generation=" + fileState2);
155                     Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: finished=" + isFinished);
156                     Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: fileSize=" + input.size());
157                     this.finalChunk = (flagByte & MASK_FINAL_CHUNK) != 0;
158                     Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: finalChunk=" + finalChunk);
159                     absoluteChunkEnd = absoluteChunkStart + chunkSize;
160                     return;
161                 }
162             }
163         }
164     }
165 
awaitFinished()166     public void awaitFinished() throws IOException {
167         if (finished) {
168             return;
169         }
170         long pos = input.position();
171         try {
172             input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION);
173             while (true) {
174                 byte filestate = input.readPhysicalByte();
175                 if (filestate == 0) {
176                     finished = true;
177                     return;
178                 }
179                 Utils.takeNap(1);
180             }
181         } finally {
182             input.position(pos);
183         }
184     }
185 
isLastChunk()186     public boolean isLastChunk() throws IOException {
187         awaitFinished();
188         // streaming files only have one chunk
189         return input.getFileSize() == absoluteChunkEnd;
190    }
191 
isFinalChunk()192     public boolean isFinalChunk() {
193         return finalChunk;
194     }
195 
isFinished()196     public boolean isFinished() throws IOException {
197         return isFinished;
198     }
199 
nextHeader()200     public ChunkHeader nextHeader() throws IOException {
201         return new ChunkHeader(input, absoluteChunkEnd, id + 1);
202     }
readMetadata()203     public MetadataDescriptor readMetadata() throws IOException {
204         return readMetadata(null);
205     }
206 
readMetadata(MetadataDescriptor previous)207     public MetadataDescriptor readMetadata(MetadataDescriptor previous) throws IOException {
208         input.position(absoluteChunkStart + metadataPosition);
209         input.readInt(); // size
210         long id = input.readLong(); // event type id
211         if (id != METADATA_TYPE_ID) {
212             throw new IOException("Expected metadata event. Type id=" + id + ", should have been " + METADATA_TYPE_ID);
213         }
214         input.readLong(); // start time
215         input.readLong(); // duration
216         long metadataId = input.readLong();
217         if (previous != null && metadataId == previous.metadataId) {
218             return previous;
219         }
220         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, "New metadata id = " + metadataId);
221         MetadataDescriptor m =  MetadataDescriptor.read(input);
222         m.metadataId = metadataId;
223         return m;
224     }
225 
226 
getMajor()227     public short getMajor() {
228         return major;
229     }
230 
getMinor()231     public short getMinor() {
232         return minor;
233     }
234 
getAbsoluteChunkStart()235     public long getAbsoluteChunkStart() {
236         return absoluteChunkStart;
237     }
238 
getAbsoluteEventStart()239     public long getAbsoluteEventStart() {
240         return absoluteEventStart;
241     }
getConstantPoolPosition()242     public long getConstantPoolPosition() {
243         return constantPoolPosition;
244     }
245 
getMetataPosition()246     public long getMetataPosition() {
247         return metadataPosition;
248     }
getStartTicks()249     public long getStartTicks() {
250         return chunkStartTicks;
251     }
getChunkSize()252     public long getChunkSize() {
253         return chunkSize;
254     }
255 
getTicksPerSecond()256     public double getTicksPerSecond() {
257         return ticksPerSecond;
258     }
259 
getStartNanos()260     public long getStartNanos() {
261         return chunkStartNanos;
262     }
263 
getEnd()264     public long getEnd() {
265         return absoluteChunkEnd;
266     }
267 
getSize()268     public long getSize() {
269         return chunkSize;
270     }
271 
getDurationNanos()272     public long getDurationNanos() {
273         return durationNanos;
274     }
275 
getInput()276     public RecordingInput getInput() {
277         return input;
278     }
279 
verifyMagic(RecordingInput input)280     private static void verifyMagic(RecordingInput input) throws IOException {
281         for (byte c : FILE_MAGIC) {
282             if (input.readByte() != c) {
283                 throw new IOException("Not a Flight Recorder file");
284             }
285         }
286     }
287 
getEventStart()288     public long getEventStart() {
289         return absoluteEventStart;
290     }
291 
headerSize()292     static long headerSize() {
293         return HEADER_SIZE;
294     }
295 
getLastNanos()296     public long getLastNanos() {
297         return getStartNanos() + getDurationNanos();
298     }
299 }
300