1 /* 2 * Copyright (c) 2016, 2020, 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 static final long HEADER_SIZE = 68; 38 static final byte UPDATING_CHUNK_HEADER = (byte) 255; 39 static final long CHUNK_SIZE_POSITION = 8; 40 static final long DURATION_NANOS_POSITION = 40; 41 static final long FILE_STATE_POSITION = 64; 42 static final long FLAG_BYTE_POSITION = 67; 43 static final long METADATA_TYPE_ID = 0; 44 static final byte[] FILE_MAGIC = { 'F', 'L', 'R', '\0' }; 45 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 long c = input.readRawLong(); // chunk size 94 Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: chunkSize=" + c); 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 public 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 readHeader(byte[] bytes, int count)166 public boolean readHeader(byte[] bytes, int count) throws IOException { 167 input.position(absoluteChunkStart); 168 for (int i = 0; i< count; i++) { 169 bytes[i] = input.readPhysicalByte(); 170 } 171 return bytes[(int)FILE_STATE_POSITION] != UPDATING_CHUNK_HEADER; 172 } 173 awaitFinished()174 public void awaitFinished() throws IOException { 175 if (finished) { 176 return; 177 } 178 long pos = input.position(); 179 try { 180 input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION); 181 while (true) { 182 byte filestate = input.readPhysicalByte(); 183 if (filestate == 0) { 184 finished = true; 185 return; 186 } 187 Utils.takeNap(1); 188 } 189 } finally { 190 input.position(pos); 191 } 192 } 193 isLastChunk()194 public boolean isLastChunk() throws IOException { 195 awaitFinished(); 196 // streaming files only have one chunk 197 return input.getFileSize() == absoluteChunkEnd; 198 } 199 isFinalChunk()200 public boolean isFinalChunk() { 201 return finalChunk; 202 } 203 isFinished()204 public boolean isFinished() throws IOException { 205 return isFinished; 206 } 207 nextHeader()208 public ChunkHeader nextHeader() throws IOException { 209 return new ChunkHeader(input, absoluteChunkEnd, id + 1); 210 } readMetadata()211 public MetadataDescriptor readMetadata() throws IOException { 212 return readMetadata(null); 213 } 214 readMetadata(MetadataDescriptor previous)215 public MetadataDescriptor readMetadata(MetadataDescriptor previous) throws IOException { 216 input.position(absoluteChunkStart + metadataPosition); 217 input.readInt(); // size 218 long id = input.readLong(); // event type id 219 if (id != METADATA_TYPE_ID) { 220 throw new IOException("Expected metadata event. Type id=" + id + ", should have been " + METADATA_TYPE_ID); 221 } 222 input.readLong(); // start time 223 input.readLong(); // duration 224 long metadataId = input.readLong(); 225 if (previous != null && metadataId == previous.metadataId) { 226 return previous; 227 } 228 Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, "New metadata id = " + metadataId); 229 MetadataDescriptor m = MetadataDescriptor.read(input); 230 m.metadataId = metadataId; 231 return m; 232 } 233 234 getMajor()235 public short getMajor() { 236 return major; 237 } 238 getMinor()239 public short getMinor() { 240 return minor; 241 } 242 getAbsoluteChunkStart()243 public long getAbsoluteChunkStart() { 244 return absoluteChunkStart; 245 } 246 getAbsoluteEventStart()247 public long getAbsoluteEventStart() { 248 return absoluteEventStart; 249 } getConstantPoolPosition()250 public long getConstantPoolPosition() { 251 return constantPoolPosition; 252 } 253 getMetataPosition()254 public long getMetataPosition() { 255 return metadataPosition; 256 } getStartTicks()257 public long getStartTicks() { 258 return chunkStartTicks; 259 } getChunkSize()260 public long getChunkSize() { 261 return chunkSize; 262 } 263 getTicksPerSecond()264 public double getTicksPerSecond() { 265 return ticksPerSecond; 266 } 267 getStartNanos()268 public long getStartNanos() { 269 return chunkStartNanos; 270 } 271 getEnd()272 public long getEnd() { 273 return absoluteChunkEnd; 274 } 275 getSize()276 public long getSize() { 277 return chunkSize; 278 } 279 getDurationNanos()280 public long getDurationNanos() { 281 return durationNanos; 282 } 283 getInput()284 public RecordingInput getInput() { 285 return input; 286 } 287 verifyMagic(RecordingInput input)288 private static void verifyMagic(RecordingInput input) throws IOException { 289 for (byte c : FILE_MAGIC) { 290 if (input.readByte() != c) { 291 throw new IOException("Not a Flight Recorder file"); 292 } 293 } 294 } 295 getEventStart()296 public long getEventStart() { 297 return absoluteEventStart; 298 } 299 headerSize()300 static long headerSize() { 301 return HEADER_SIZE; 302 } 303 getLastNanos()304 public long getLastNanos() { 305 return getStartNanos() + getDurationNanos(); 306 } 307 } 308