blob: 5b4a0384bbd31e5f22b42af8d9dfcfc614446651 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2017 Ericsson
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Marc-Andre Laperle - Initial API and implementation
*******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.core.trace.indexer;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.Objects;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.tmf.core.Activator;
import org.eclipse.tracecompass.internal.tmf.core.TmfCoreTracer;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.indexer.ITmfPersistentlyIndexable;
/**
* Common implementation of file-based checkpoint collection
*
* @author Marc-Andre Laperle
*/
public abstract class AbstractFileCheckpointCollection implements ICheckpointCollection {
private static final int INVALID_VERSION = -1;
private static final int VERSION = 3;
private static final int SUB_VERSION_NONE = -1;
/**
* The base file header, can be extended
*/
protected class CheckpointCollectionFileHeader {
private static final int SIZE = INT_SIZE +
INT_SIZE +
LONG_SIZE +
LONG_SIZE +
MAX_TIME_RANGE_SERIALIZE_SIZE;
/**
* Get the size of the header in bytes. This should be overridden if the
* header is augmented with more data
*
* @return the size of the header in bytes
*/
public int getSize() {
return SIZE;
}
/**
* Get the sub version of this header
*
* @return the sub version
*/
public int getSubVersion() {
return SUB_VERSION_NONE;
}
/**
* Constructs a new file header for an existing file
*
* @param randomAccessFile
* the existing file
* @throws IOException
* if an I/O error occurs reading from the file
*/
public CheckpointCollectionFileHeader(RandomAccessFile randomAccessFile) throws IOException {
fVersion = randomAccessFile.readInt();
fSize = randomAccessFile.readInt();
fNbEvents = randomAccessFile.readLong();
ByteBuffer b = ByteBuffer.allocate(MAX_TIME_RANGE_SERIALIZE_SIZE);
b.clear();
fFileChannel.read(b);
b.flip();
fTimeRange = new TmfTimeRange(TmfTimestamp.create(b), TmfTimestamp.create(b));
}
/**
* Constructs a new file header for the given version
*
* @param version
* the version
*/
public CheckpointCollectionFileHeader(int version) {
fVersion = version;
}
/**
* Serialize the header to a file
*
* @param randomAccessFile
* the existing file
* @throws IOException
* if an I/O error occurs writing to the file
*/
public void serialize(RandomAccessFile randomAccessFile) throws IOException {
randomAccessFile.seek(0);
// Version is written at the very last on dispose
randomAccessFile.writeInt(INVALID_VERSION);
randomAccessFile.writeInt(fSize);
randomAccessFile.writeLong(fNbEvents);
ByteBuffer b = ByteBuffer.allocate(MAX_TIME_RANGE_SERIALIZE_SIZE);
b.clear();
TmfTimestamp.serialize(b, fTimeRange.getStartTime());
TmfTimestamp.serialize(b, fTimeRange.getEndTime());
b.rewind();
fFileChannel.write(b);
}
/**
* The version of the collection. Should be incremented if a binary
* incompatible change occurs.
*/
protected final int fVersion;
/**
* The size of the collection expressed in a number of checkpoints.
*/
protected int fSize = 0;
/**
* The total number of events in the trace
*/
protected long fNbEvents;
/**
* The time range of the trace.
*/
protected TmfTimeRange fTimeRange = new TmfTimeRange(TmfTimestamp.ZERO, TmfTimestamp.ZERO);
}
/**
* The size of an int in bytes
*/
protected static final int INT_SIZE = 4;
/**
* The size of a long in bytes
*/
protected static final int LONG_SIZE = 8;
/**
* The maximum size of the serialize buffer when writing the time range
*/
protected static final int MAX_TIME_RANGE_SERIALIZE_SIZE = 128;
/**
* The originating trace
*/
private ITmfPersistentlyIndexable fTrace;
private long fCacheMisses = 0;
private boolean fCreatedFromScratch;
private boolean fIsDirty = false;
/**
* File handle for the file being read/written
*/
private @Nullable RandomAccessFile fRandomAccessFile;
/**
* File handle for the file being read/written
*/
private File fFile;
/**
* The base file header
*/
private final CheckpointCollectionFileHeader fHeader;
// Cached values
private FileChannel fFileChannel;
/**
* Constructs a checkpoint collection for a given trace from scratch or from
* an existing file. When the checkpoint collection is created from scratch,
* it is populated by subsequent calls to {@link #insert}.
*
* @param file
* the file to use as the persistent storage
* @param trace
* the trace
*/
public AbstractFileCheckpointCollection(File file, ITmfPersistentlyIndexable trace) {
fTrace = trace;
fFile = file;
setCreatedFromScratch(!fFile.exists());
CheckpointCollectionFileHeader header = null;
if (!isCreatedFromScratch()) {
header = tryRestore();
if (header == null) {
delete();
}
}
/* delete above may have reset the value */
if (isCreatedFromScratch()) {
fIsDirty = true;
header = initialize();
}
fHeader = header;
}
/**
* Creates a new basic file header with the version field initialized. This
* should be overridden if the file header is extended
*
* @return the created file header
*/
protected CheckpointCollectionFileHeader createHeader() {
return new CheckpointCollectionFileHeader(VERSION);
}
/**
* Creates a new basic file header for an existing file. This should be
* overridden if the file header is extended
*
* @param randomAccessFile
* the existing file
* @return the created file header
* @throws IOException
* if an I/O error occurs reading from the file
*/
protected CheckpointCollectionFileHeader createHeader(RandomAccessFile randomAccessFile) throws IOException {
return new CheckpointCollectionFileHeader(randomAccessFile);
}
/**
* Get the version of the collection.
*
* @return the version of the collection.
*/
protected int getVersion() {
return VERSION;
}
/**
* Get the sub version of the collection.
*
* @return the sub version of the collection.
*/
protected int getSubVersion() {
return SUB_VERSION_NONE;
}
private CheckpointCollectionFileHeader initialize() {
CheckpointCollectionFileHeader header = null;
try {
RandomAccessFile randomAccessFile = new RandomAccessFile(fFile, "rw"); //$NON-NLS-1$
fFileChannel = randomAccessFile.getChannel();
header = createHeader();
// Reserve space for header
randomAccessFile.setLength(header.getSize());
TmfCoreTracer.traceIndexer(CheckpointCollectionFileHeader.class.getSimpleName() + " initialize " + "nbEvents: " + header.fNbEvents + " fTimeRange: " + header.fTimeRange); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
fRandomAccessFile = randomAccessFile;
} catch (IOException e) {
Activator.logError(MessageFormat.format(Messages.ErrorOpeningIndex, fFile), e);
return null;
}
return header;
}
/**
* Try to restore the index from disk. Try to open the file and check the
* version. Returns the loaded header or null if it could not be loaded.
*
* @return the loaded header or null if it could not be loaded.
*/
private CheckpointCollectionFileHeader tryRestore() {
CheckpointCollectionFileHeader header = null;
RandomAccessFile randomAccessFile = fRandomAccessFile;
try {
randomAccessFile = new RandomAccessFile(fFile, "rw"); //$NON-NLS-1$
fFileChannel = randomAccessFile.getChannel();
} catch (FileNotFoundException e) {
Activator.logError(MessageFormat.format(Messages.ErrorOpeningIndex, fFile), e);
return null;
} finally {
fRandomAccessFile = randomAccessFile;
}
try {
header = createHeader(randomAccessFile);
if (header.fVersion != VERSION || header.getSubVersion() != getSubVersion()) {
return null;
}
TmfCoreTracer.traceIndexer(CheckpointCollectionFileHeader.class.getSimpleName() + " read " + fFile + " nbEvents: " + header.fNbEvents + " fTimeRange: " + header.fTimeRange); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
} catch (IOException e) {
Activator.logError(MessageFormat.format(Messages.IOErrorReadingHeader, fFile), e);
return null;
} finally {
fRandomAccessFile = randomAccessFile;
}
return header;
}
/**
* Marks the index as dirty. Writes an invalid file header, and sets the dirty
* flag which will cause the new file header to be serialized when the index is
* disposed.
*/
protected void markDirty() {
RandomAccessFile randomAccessFile = fRandomAccessFile;
if (fIsDirty || randomAccessFile == null) {
return;
}
try {
// Write an invalid version until the very last moment when dispose
// the index. This is how we know if it's corrupted or not.
randomAccessFile.seek(0);
randomAccessFile.writeInt(INVALID_VERSION);
} catch (IOException e) {
Activator.logError(MessageFormat.format(Messages.IOErrorWritingHeader, fFile), e);
}
fIsDirty = true;
}
/**
*
* @return true if the checkpoint collection was created from scratch, false
* otherwise
*/
@Override
public boolean isCreatedFromScratch() {
return fCreatedFromScratch;
}
/**
* Set whether or not the collection is created from scratch
*
* @param isCreatedFromScratch
* whether or not the collection is created from scratch
*/
protected void setCreatedFromScratch(boolean isCreatedFromScratch) {
fCreatedFromScratch = isCreatedFromScratch;
}
/**
* @return the number of cache misses.
*/
public long getCacheMisses() {
return fCacheMisses;
}
/**
* Increment the number of cache misses.
*/
protected void incCacheMisses() {
++fCacheMisses;
}
/**
* Returns the size of the checkpoint collection expressed as a number of
* checkpoints.
*
* @return the size of the checkpoint collection
*/
@Override
public int size() {
return fHeader.fSize;
}
/**
* Set the trace time range
*
* @param timeRange
* the trace time range
*/
@Override
public void setTimeRange(TmfTimeRange timeRange) {
if (!fHeader.fTimeRange.equals(timeRange)) {
markDirty();
}
fHeader.fTimeRange = timeRange;
}
/**
* Get the trace time range
*
* @return the trace time range
*/
@Override
public TmfTimeRange getTimeRange() {
return fHeader.fTimeRange;
}
/**
* Set the number of events in the trace
*
* @param nbEvents
* the number of events in the trace
*/
@Override
public void setNbEvents(long nbEvents) {
if (fHeader.fNbEvents != nbEvents) {
markDirty();
}
fHeader.fNbEvents = nbEvents;
}
/**
* Get the number of events in the trace
*
* @return the number of events in the trace
*/
@Override
public long getNbEvents() {
return fHeader.fNbEvents;
}
/**
* Get the trace
*
* @return the trace
*/
protected ITmfPersistentlyIndexable getTrace() {
return fTrace;
}
/**
* Get the random access file currently opened
*
* @return the file
*/
protected RandomAccessFile getRandomAccessFile() {
return fRandomAccessFile;
}
/**
* Get the file channel currently used for the index
*
* @return the file channel
* @deprecated potential resource leak, use {@link #getRandomAccessFile()} instead
*/
@Deprecated
protected FileChannel getFileChannel() {
return Objects.requireNonNull(fRandomAccessFile).getChannel();
}
/**
* Get the file handle for the index
*
* @return the file
*/
protected File getFile() {
return fFile;
}
/**
* Get the header for this collection
*
* @return the header
*/
public CheckpointCollectionFileHeader getHeader() {
return fHeader;
}
/**
* Dispose and delete the checkpoint collection
*/
@Override
public void delete() {
dispose(true);
File file = fFile;
if (file.exists()) {
try {
Files.delete(file.toPath());
} catch (IOException e) {
Activator.logError(e.getMessage(), e);
}
}
}
/**
* Dispose the collection and its resources
*/
@Override
public void dispose() {
dispose(false);
}
private void dispose(boolean deleting) {
try {
RandomAccessFile randomAccessFile = fRandomAccessFile;
if (randomAccessFile != null) {
if (!deleting && fIsDirty) {
if (fHeader != null) {
fHeader.serialize(randomAccessFile);
}
randomAccessFile.seek(0);
randomAccessFile.writeInt(getVersion());
fIsDirty = false;
}
randomAccessFile.close();
}
setCreatedFromScratch(true);
String headerTrace = fHeader == null ? "No header" : "nbEvents: " + fHeader.fNbEvents + " timerange:" + fHeader.fTimeRange; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
TmfCoreTracer.traceIndexer(this.getClass().getSimpleName() + " disposed. " + headerTrace); //$NON-NLS-1$
} catch (IOException e) {
Activator.logError(MessageFormat.format(Messages.IOErrorClosingIndex, fFile), e);
} finally {
fRandomAccessFile = null;
}
}
}