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