All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.elastic.otel.profiler.asyncprofiler.BufferedFile Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to Elasticsearch B.V. under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch B.V. licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package co.elastic.otel.profiler.asyncprofiler;

import co.elastic.otel.profiler.pooling.Recyclable;
import java.io.File;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.StandardOpenOption;
import javax.annotation.Nullable;

/**
 * An abstraction similar to {@link MappedByteBuffer} that allows to read the content of a file with
 * an API that is similar to {@link ByteBuffer}.
 *
 * 

Instances of this class hold a reusable buffer that contains a subset of the file, or the * whole file if the buffer's capacity is greater or equal to the file's size. * *

Whenever calling a method like {@link #getLong()} or {@link #position(long)} would exceed the * currently buffered range the same buffer is filled with a different range of the file. * *

The downside of {@link MappedByteBuffer} (and the reason for implementing this abstraction) is * that calling methods like {@link MappedByteBuffer#get()} can increase time-to-safepoint. This is * because these methods are implemented as JVM intrinsics. When the JVM executes an intrinsic, it * does not switch to the native execution context which means that it's not ready to enter a * safepoint whenever a intrinsic runs. As reading a file from disk can get stuck (for example when * the disk is busy) calling {@link MappedByteBuffer#get()} may take a while to execute. While it's * executing other threads have to wait for it to finish if the JVM wants to reach a safe point. */ class BufferedFile implements Recyclable { private static final int SIZE_OF_BYTE = 1; private static final int SIZE_OF_SHORT = 2; private static final int SIZE_OF_INT = 4; private static final int SIZE_OF_LONG = 8; // The following constant are defined by the JFR file format for identifying the string encoding private static final int STRING_ENCODING_NULL = 0; private static final int STRING_ENCODING_EMPTY = 1; private static final int STRING_ENCODING_CONSTANTPOOL = 2; private static final int STRING_ENCODING_UTF8 = 3; private static final int STRING_ENCODING_CHARARRAY = 4; private static final int STRING_ENCODING_LATIN1 = 5; private ByteBuffer buffer; private final ByteBuffer bigBuffer; private final ByteBuffer smallBuffer; /** The offset of the file from where the {@link #buffer} starts */ private long offset; private boolean wholeFileInBuffer; @Nullable private FileChannel fileChannel; /** * @param bigBuffer the buffer to be used to read the whole file if the file fits into it * @param smallBuffer the buffer to be used to read chunks of the file in case the file is larger * than bigBuffer. Constantly seeking a file with a large buffer is very bad for performance. */ public BufferedFile(ByteBuffer bigBuffer, ByteBuffer smallBuffer) { this.bigBuffer = bigBuffer; this.smallBuffer = smallBuffer; } /** * Sets the file and depending on it's size, may read the file into the {@linkplain #buffer * buffer} * * @param file the file to read from * @throws IOException If some I/O error occurs */ public void setFile(File file) throws IOException { fileChannel = FileChannel.open(file.toPath(), StandardOpenOption.READ); if (fileChannel.size() <= bigBuffer.capacity()) { buffer = bigBuffer; read(0, bigBuffer.capacity()); wholeFileInBuffer = true; } else { buffer = smallBuffer; Buffer buffer = this.buffer; buffer.flip(); } } /** * Returns the position of the file * * @return the position of the file */ public long position() { return offset + buffer.position(); } /** * Skips the provided number of bytes in the file without reading new data. * * @param bytesToSkip the number of bytes to skip */ public void skip(int bytesToSkip) { position(position() + bytesToSkip); } public void skipString() throws IOException { readOrSkipString(get(), null); } /** * @param output the buffer to place the string intro * @return false, if the string to read is null, true otherwise */ @Nullable public boolean readString(StringBuilder output) throws IOException { byte encoding = get(); if (encoding == 0) { // 0 encoding represents a null string return false; } readOrSkipString(encoding, output); return true; } @Nullable public String readString() throws IOException { byte encoding = get(); if (encoding == STRING_ENCODING_NULL) { return null; } if (encoding == STRING_ENCODING_EMPTY) { return ""; } StringBuilder output = new StringBuilder(); readOrSkipString(encoding, output); return output.toString(); } private void readOrSkipString(byte encoding, @Nullable StringBuilder output) throws IOException { switch (encoding) { case STRING_ENCODING_NULL: case STRING_ENCODING_EMPTY: return; case STRING_ENCODING_CONSTANTPOOL: if (output != null) { throw new IllegalStateException("Reading constant pool string is not supported"); } getVarLong(); return; case STRING_ENCODING_UTF8: readOrSkipUtf8(output); return; case STRING_ENCODING_CHARARRAY: throw new IllegalStateException("Char-array encoding is not supported by the parser yet"); case STRING_ENCODING_LATIN1: if (output != null) { throw new IllegalStateException("Reading LATIN1 encoded string is not supported"); } skip(getVarInt()); return; default: throw new IllegalStateException("Unknown string encoding type: " + encoding); } } private void readOrSkipUtf8(@Nullable StringBuilder output) throws IOException { int len = getVarInt(); if (output == null) { skip(len); return; } ensureRemaining(len, len); for (int i = 0; i < len; i++) { byte hopefullyAscii = getUnsafe(); if (hopefullyAscii > 0) { output.append((char) hopefullyAscii); } else { // encountered non-ascii character: fallback to allocating and UTF8-decoding position(position() - 1); // reset position before the just read byte byte[] utf8Data = new byte[len - i]; buffer.get(utf8Data); output.append(new String(utf8Data, StandardCharsets.UTF_8)); return; } } } /** * Sets the position of the file without reading new data. * * @param pos the new position */ public void position(long pos) { Buffer buffer = this.buffer; long positionDelta = pos - position(); long newBufferPos = buffer.position() + positionDelta; if (0 <= newBufferPos && newBufferPos <= buffer.limit()) { buffer.position((int) newBufferPos); } else { // makes sure that the next ensureRemaining will load from file buffer.position(0); buffer.limit(0); offset = pos; } } /** * Ensures that the provided number of bytes are available in the {@linkplain #buffer buffer} * * @param minRemaining the number of bytes which are guaranteed to be available in the {@linkplain * #buffer buffer} * @throws IOException If some I/O error occurs * @throws IllegalStateException If minRemaining is greater than the buffer's capacity */ public void ensureRemaining(int minRemaining) throws IOException { ensureRemaining(minRemaining, buffer.capacity()); } /** * Ensures that the provided number of bytes are available in the {@linkplain #buffer buffer} * * @param minRemaining the number of bytes which are guaranteed to be available in the {@linkplain * #buffer buffer} * @param maxRead the max number of bytes to read from the file in case the buffer does currently * not hold {@code minRemaining} bytes * @throws IOException If some I/O error occurs * @throws IllegalStateException If minRemaining is greater than the buffer's capacity */ public void ensureRemaining(int minRemaining, int maxRead) throws IOException { if (wholeFileInBuffer) { return; } if (minRemaining > buffer.capacity()) { throw new IllegalStateException( String.format( "Length (%d) greater than buffer capacity (%d)", minRemaining, buffer.capacity())); } if (buffer.remaining() < minRemaining) { read(position(), maxRead); } } /** * Gets a byte from the current {@linkplain #position() position} of this file. If the {@linkplain * #buffer buffer} does not fully contain this byte, loads another slice of the file into the * buffer. * * @return The byte at the file's current position * @throws IOException If some I/O error occurs */ public byte get() throws IOException { ensureRemaining(SIZE_OF_BYTE); return buffer.get(); } /** * Gets a short from the current {@linkplain #position() position} of this file. If the * {@linkplain #buffer buffer} does not fully contain this short, loads another slice of the file * into the buffer. * * @return The short at the file's current position * @throws IOException If some I/O error occurs */ public short getShort() throws IOException { ensureRemaining(SIZE_OF_SHORT); return buffer.getShort(); } /** * Gets a short from the current {@linkplain #position() position} of this file. If the * {@linkplain #buffer buffer} does not fully contain this short, loads another slice of the file * into the buffer. * * @return The short at the file's current position * @throws IOException If some I/O error occurs */ public int getUnsignedShort() throws IOException { return getShort() & 0xffff; } /** * Gets a int from the current {@linkplain #position() position} of this file and converts it to * an unsigned short. If the {@linkplain #buffer buffer} does not fully contain this int, loads * another slice of the file into the buffer. * * @return The int at the file's current position * @throws IOException If some I/O error occurs */ public int getInt() throws IOException { ensureRemaining(SIZE_OF_INT); return buffer.getInt(); } /** * Gets a long from the current {@linkplain #position() position} of this file. If the {@linkplain * #buffer buffer} does not fully contain this long, loads another slice of the file into the * buffer. * * @return The long at the file's current position * @throws IOException If some I/O error occurs */ public long getLong() throws IOException { ensureRemaining(SIZE_OF_LONG); return buffer.getLong(); } /** Reads LEB-128 variable length encoded values of a size of up to 64 bit. */ public long getVarLong() throws IOException { long value = 0; boolean hasNext = true; int shift = 0; while (hasNext) { long byteVal = ((int) get()); hasNext = (byteVal & 0x80) != 0; value |= (byteVal & 0x7F) << shift; shift += 7; } return value; } public int getVarInt() throws IOException { long val = getVarLong(); if ((int) val != val) { throw new IllegalArgumentException("The LEB128 encoded value does not fit in an int"); } return (int) val; } /** * Gets a byte from the underlying buffer without checking if this part of the file is actually in * the buffer. * *

Always mare sure to call {@link #ensureRemaining} before. * * @return The byte at the file's current position * @throws java.nio.BufferUnderflowException If the buffer's current position is not smaller than * its limit */ public byte getUnsafe() { return buffer.get(); } /** * Gets a short from the underlying buffer without checking if this part of the file is actually * in the buffer. * *

Always mare sure to call {@link #ensureRemaining} before. * * @return The byte at the file's current position * @throws java.nio.BufferUnderflowException If there are fewer than two bytes remaining in this * buffer */ public short getUnsafeShort() { return buffer.getShort(); } /** * Gets an int from the underlying buffer without checking if this part of the file is actually in * the buffer. * *

Always mare sure to call {@link #ensureRemaining} before. * * @return The byte at the file's current position * @throws java.nio.BufferUnderflowException If there are fewer than four bytes remaining in this * buffer */ public int getUnsafeInt() { return buffer.getInt(); } /** * Gets a long from the underlying buffer without checking if this part of the file is actually in * the buffer. * *

Always mare sure to call {@link #ensureRemaining} before. * * @return The byte at the file's current position * @throws java.nio.BufferUnderflowException If there are fewer than eight bytes remaining in this * buffer */ public long getUnsafeLong() { return buffer.getLong(); } public long size() throws IOException { if (fileChannel == null) { throw new IllegalStateException("setFile has not been called yet"); } return fileChannel.size(); } public boolean isSet() { return fileChannel != null; } @Override public void resetState() { if (fileChannel == null) { throw new IllegalStateException("setFile has not been called yet"); } Buffer buffer = this.buffer; buffer.clear(); offset = 0; wholeFileInBuffer = false; try { fileChannel.close(); } catch (IOException ignore) { } fileChannel = null; this.buffer = null; } private void read(long offset, int limit) throws IOException { if (limit > buffer.capacity()) { limit = buffer.capacity(); } Buffer buffer = this.buffer; buffer.clear(); fileChannel.position(offset); buffer.limit(limit); fileChannel.read(this.buffer); buffer.flip(); this.offset = offset; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy