blob: 608458ee8c20ef9d2a1bf92e57523bd75bf45696 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2019 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:
* Matthew Khouzam - Initial API and implementation
* Patrick Tasse - Fix parsing of instance numbers
*******************************************************************************/
package org.eclipse.tracecompass.btf.core.trace;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.btf.core.Activator;
import org.eclipse.tracecompass.btf.core.event.BtfEvent;
import org.eclipse.tracecompass.btf.core.event.BtfEventType;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.ITmfEventField;
import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException;
import org.eclipse.tracecompass.tmf.core.io.BufferedRandomAccessFile;
import org.eclipse.tracecompass.tmf.core.parsers.custom.CustomTxtTraceContext;
import org.eclipse.tracecompass.tmf.core.project.model.ITmfPropertiesProvider;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
import org.eclipse.tracecompass.tmf.core.trace.TmfContext;
import org.eclipse.tracecompass.tmf.core.trace.TmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.eclipse.tracecompass.tmf.core.trace.TraceValidationStatus;
import org.eclipse.tracecompass.tmf.core.trace.indexer.ITmfPersistentlyIndexable;
import org.eclipse.tracecompass.tmf.core.trace.indexer.ITmfTraceIndexer;
import org.eclipse.tracecompass.tmf.core.trace.indexer.TmfBTreeTraceIndexer;
import org.eclipse.tracecompass.tmf.core.trace.indexer.checkpoint.ITmfCheckpoint;
import org.eclipse.tracecompass.tmf.core.trace.indexer.checkpoint.TmfCheckpoint;
import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation;
import org.eclipse.tracecompass.tmf.core.trace.location.TmfLongLocation;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
import com.google.common.primitives.Longs;
/**
* BTF reader. Reads Best Trace Format traces.
*
* @author Matthew Khouzam
*/
public class BtfTrace extends TmfTrace implements ITmfPersistentlyIndexable, ITmfPropertiesProvider {
private static final int MAX_FIELDS = 8;
private static final int MIN_FIELDS = 7;
private static final long MICROSECONDS_IN_A_SECOND = 1000000L;
private static final String VERSION = "#version"; //$NON-NLS-1$
private static final String CREATOR = "#creator"; //$NON-NLS-1$
private static final String CREATIONDATE = "#creationDate"; //$NON-NLS-1$
private static final String INPUTFILE = "#inputFile"; //$NON-NLS-1$
private static final String TIMESCALE = "#timeScale"; //$NON-NLS-1$
private static final String ENTITYTYPE = "#entityType"; //$NON-NLS-1$
private static final String ENTITYTABLE = "#entityTable"; //$NON-NLS-1$
private static final String ENTITYTYPETABLE = "#entityTypeTable"; //$NON-NLS-1$
// lower-case helpers
private static final String lCREATIONDATE = "#creationdate"; //$NON-NLS-1$
private static final String lINPUTFILE = "#inputfile"; //$NON-NLS-1$
private static final String lTIMESCALE = "#timescale"; //$NON-NLS-1$
private static final String lENTITYTYPE = "#entitytype"; //$NON-NLS-1$
private static final String lENTITYTABLE = "#entitytable"; //$NON-NLS-1$
private static final String lENTITYTYPETABLE = "#entitytypetable"; //$NON-NLS-1$
private static final TmfLongLocation NULL_LOCATION = new TmfLongLocation(-1L);
private static final int CACHE_SIZE = 256;
private static final int MAX_CONFIDENCE = 100;
private static final int MAX_LINES = 100;
private static int fCheckpointSize = -1;
private final @NonNull Map<String, String> fProperties = new HashMap<>();
private final @NonNull Map<Integer, String> fEntityTable = new TreeMap<>();
private final @NonNull Map<BtfEventType, String> fEntityTypeTable = new HashMap<>();
private final @NonNull Map<Integer, BtfEventType> fEntityTypes = new TreeMap<>();
private String fVersion;
private String fCreator;
private String fCreationDate;
private String fInputFile;
// default unit is ns
private BtfTimestampFormat fTsFormat = BtfTimestampFormat.NS;
private File fFile;
private RandomAccessFile fFileInput;
private long fDataOffset;
private long fTsOffset = 0;
/**
* Default constructor
*/
public BtfTrace() {
super();
setCacheSize(CACHE_SIZE);
fProperties.put(TIMESCALE, fTsFormat.toString());
}
private void parseHeader(RandomAccessFile input) throws IOException {
String line = input.readLine();
long pos = 0;
while (line != null && line.startsWith("#")) { //$NON-NLS-1$
String[] tokens = line.split(" ", 2); //$NON-NLS-1$
/*
* please note that the examples we were given and the spec are NOT
* consistent, so we are ignoring the case to avoid issues
*/
switch (tokens[0].toLowerCase()) {
case VERSION:
fVersion = tokens[1];
fProperties.put(VERSION, fVersion);
break;
case CREATOR:
fCreator = tokens[1];
fProperties.put(CREATOR, fCreator);
break;
case lCREATIONDATE:
fCreationDate = tokens[1];
fProperties.put(CREATIONDATE, fCreationDate);
try {
// DateFormats are inherently unsafe for multithreaded use
// so we can't make this a field. Just in case.
final SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); //$NON-NLS-1$
Date dateTime = ISO8601DATEFORMAT.parse(fCreationDate);
fTsOffset = dateTime.getTime() * MICROSECONDS_IN_A_SECOND;
} catch (ParseException e) {
Activator.logWarning("Creation date error: " + e.getMessage()); //$NON-NLS-1$
}
break;
case lINPUTFILE:
fInputFile = tokens[1];
fProperties.put(INPUTFILE, fInputFile);
break;
case lTIMESCALE:
fTsFormat = BtfTimestampFormat.parse(tokens[1]);
fProperties.put(TIMESCALE, fTsFormat.toString());
break;
case lENTITYTYPE:
pos = fFileInput.getFilePointer();
line = fFileInput.readLine();
while (line.startsWith("#-")) { //$NON-NLS-1$
String tempLine = line.substring(1);
String[] elements = tempLine.split(" ", 2); //$NON-NLS-1$
fEntityTypes.put(Integer.parseInt(elements[0]), BtfEventTypeFactory.parse(elements[1]));
pos = fFileInput.getFilePointer();
line = fFileInput.readLine();
}
fFileInput.seek(pos);
fProperties.put(ENTITYTYPE, fEntityTypes.toString());
break;
case lENTITYTABLE:
pos = fFileInput.getFilePointer();
line = fFileInput.readLine();
while (line.startsWith("#-")) { //$NON-NLS-1$
String tempLine = line.substring(1);
String[] elements = tempLine.split(" ", 2); //$NON-NLS-1$
fEntityTable.put(Integer.parseInt(elements[0]), elements[1]);
pos = fFileInput.getFilePointer();
line = fFileInput.readLine();
}
fProperties.put(ENTITYTABLE, fEntityTable.toString());
fFileInput.seek(pos);
break;
case lENTITYTYPETABLE:
pos = fFileInput.getFilePointer();
line = fFileInput.readLine();
while (line.startsWith("#-")) { //$NON-NLS-1$
String tempLine = line.substring(1);
String[] elements = tempLine.split(" ", 2); //$NON-NLS-1$
fEntityTypeTable.put(BtfEventTypeFactory.parse(elements[0]), elements[1]);
pos = fFileInput.getFilePointer();
line = fFileInput.readLine();
}
fFileInput.seek(pos);
fProperties.put(ENTITYTYPETABLE, fEntityTypeTable.toString());
break;
default:
break;
}
fDataOffset = input.getFilePointer();
line = input.readLine();
}
fTsOffset = (long) (fTsOffset * fTsFormat.getScaleFactor());
}
@Override
public void initTrace(IResource resource, String path, Class<? extends ITmfEvent> type) throws TmfTraceException {
super.initTrace(resource, path, type);
fFile = new File(path);
try {
fFileInput = new RandomAccessFile(fFile, "r"); //$NON-NLS-1$
parseHeader(fFileInput);
} catch (IOException e) {
throw new TmfTraceException(e.getMessage(), e);
}
}
private void initFile() throws TmfTraceException {
closeFile();
try {
fFileInput = new BufferedRandomAccessFile(getPath(), "r"); //$NON-NLS-1$
} catch (IOException e) {
throw new TmfTraceException(e.getMessage(), e);
}
}
private void closeFile() {
if (fFileInput != null) {
try {
fFileInput.close();
} catch (IOException e) {
} finally {
fFileInput = null;
}
}
}
@Override
public IStatus validate(IProject project, String path) {
File file = new File(path);
if (!file.exists()) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "File not found: " + path); //$NON-NLS-1$
}
if (!file.isFile()) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Not a file. It's a directory: " + path); //$NON-NLS-1$
}
int confidence = 0;
try {
if (!TmfTraceUtils.isText(file)) {
return new TraceValidationStatus(confidence, Activator.PLUGIN_ID);
}
} catch (IOException e) {
Activator.logError("Error validating file: " + path, e); //$NON-NLS-1$
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "IOException validating file: " + path, e); //$NON-NLS-1$
}
try (BufferedRandomAccessFile rafile = new BufferedRandomAccessFile(path, "r")) { //$NON-NLS-1$
int lineCount = 0;
int matches = 0;
String line = rafile.getNextLine();
while ((line != null) && line.startsWith("#")) { //$NON-NLS-1$
line = rafile.getNextLine();
}
while ((line != null) && (lineCount++ < MAX_LINES)) {
// Skip comment lines
while (line != null && line.startsWith("#")) { //$NON-NLS-1$
line = rafile.readLine();
}
ITmfEvent event = parseLine(0, line);
if (event != null) {
matches++;
}
confidence = MAX_CONFIDENCE * matches / lineCount;
line = rafile.getNextLine();
}
} catch (IOException e) {
Activator.logError("Error validating file: " + path, e); //$NON-NLS-1$
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "IOException validating file: " + path, e); //$NON-NLS-1$
}
return new TraceValidationStatus(confidence, Activator.PLUGIN_ID);
}
@Override
public ITmfLocation getCurrentLocation() {
long temp = -1;
try {
temp = fFileInput.getFilePointer();
} catch (IOException e) {
}
return new TmfLongLocation(temp);
}
@Override
public double getLocationRatio(ITmfLocation location) {
long size = fFile.length() - fDataOffset;
long pos;
try {
pos = fFileInput.getFilePointer() - fDataOffset;
} catch (IOException e) {
pos = 0;
}
return 1.0 / size * pos;
}
@Override
public ITmfContext seekEvent(ITmfLocation location) {
final TmfContext context = new TmfContext(NULL_LOCATION, ITmfContext.UNKNOWN_RANK);
if (NULL_LOCATION.equals(location) || fFile == null) {
return context;
}
try {
if (location == null) {
fFileInput.seek(fDataOffset);
} else if (location.getLocationInfo() instanceof Long) {
fFileInput.seek((Long) location.getLocationInfo());
}
context.setLocation(new TmfLongLocation(fFileInput.getFilePointer()));
return context;
} catch (final FileNotFoundException e) {
Activator.logError("Error seeking event. File not found: " + getPath(), e); //$NON-NLS-1$
return context;
} catch (final IOException e) {
Activator.logError("Error seeking event. File: " + getPath(), e); //$NON-NLS-1$
return context;
}
}
@Override
public ITmfContext seekEvent(double ratio) {
if (fFile == null) {
return new TmfContext(NULL_LOCATION, ITmfContext.UNKNOWN_RANK);
}
try {
long pos = Math.round(ratio * fFile.length()) - fDataOffset;
while (pos > 0) {
fFileInput.seek(pos - 1);
if (fFileInput.read() == '\n') {
break;
}
pos--;
}
final ITmfLocation location = new TmfLongLocation(pos);
final ITmfContext context = seekEvent(location);
context.setRank(ITmfContext.UNKNOWN_RANK);
return context;
} catch (final IOException e) {
Activator.logError("Error seeking event. File: " + getPath(), e); //$NON-NLS-1$
return new CustomTxtTraceContext(NULL_LOCATION, ITmfContext.UNKNOWN_RANK);
}
}
@Override
public ITmfEvent parseEvent(ITmfContext tmfContext) {
if (fFile == null || (!(tmfContext instanceof TmfContext))) {
return null;
}
final TmfContext context = (TmfContext) tmfContext;
ITmfLocation location = context.getLocation();
if (location == null
|| !(location.getLocationInfo() instanceof Long)
|| NULL_LOCATION.equals(location)) {
return null;
}
return parseLine(context);
}
/**
* Parse a line with a context
*
* @param context
* the context, has a location
* @return the event from a given line
*/
private ITmfEvent parseLine(TmfContext context) {
ITmfLocation location = context.getLocation();
if (location != null) {
try {
if (!location.getLocationInfo().equals(fFileInput.getFilePointer())) {
seekEvent(location);
}
} catch (IOException e1) {
seekEvent(location);
}
String line;
try {
line = fFileInput.readLine();
while (line != null && line.startsWith("#")) { //$NON-NLS-1$
line = fFileInput.readLine();
}
return parseLine(context.getRank(), line);
} catch (IOException e) {
Activator.logError(e.getMessage(), e);
}
}
return null;
}
/**
* Parse a line of text and make an event using it.
*
* @param rank
* the rank of the event
* @param line
* the raw string of the event
* @return the event, or null if the line is not a valid format
*/
private ITmfEvent parseLine(long rank, String line) {
if (line == null) {
return null;
}
String[] tokens = line.split(",", MAX_FIELDS); //$NON-NLS-1$
if (tokens.length < MIN_FIELDS) {
return null;
}
Iterator<String> token = Iterators.forArray(tokens);
Long timestamp = Longs.tryParse(token.next());
if (timestamp == null) {
return null;
}
String source = token.next();
Long sourceInstance = Longs.tryParse(token.next());
if (sourceInstance == null) {
sourceInstance = -1L;
}
BtfEventType type = BtfEventTypeFactory.parse(token.next());
if (type == null) {
return null;
}
String target = token.next();
Long targetInstance = Longs.tryParse(token.next());
if (targetInstance == null) {
targetInstance = -1L;
}
String event = token.next();
String notes = null;
if (token.hasNext()) {
notes = token.next();
}
ITmfEventField content = type.generateContent(event, sourceInstance, targetInstance, notes);
return new BtfEvent(this, rank,
getTimestampTransform().transform(fTsFormat.createTimestamp(timestamp + fTsOffset)),
source,
type,
type.getDescription(),
content,
target);
}
@Override
public int getCheckpointSize() {
synchronized (BtfTrace.class) {
if (fCheckpointSize == -1) {
TmfCheckpoint c = new TmfCheckpoint(TmfTimestamp.ZERO, new TmfLongLocation(0L), 0);
ByteBuffer b = ByteBuffer.allocate(ITmfCheckpoint.MAX_SERIALIZE_SIZE);
b.clear();
c.serialize(b);
fCheckpointSize = b.position();
}
}
return fCheckpointSize;
}
@Override
public ITmfLocation restoreLocation(ByteBuffer bufferIn) {
return new TmfLongLocation(bufferIn);
}
@Override
protected ITmfTraceIndexer createIndexer(int interval) {
return new TmfBTreeTraceIndexer(this, interval);
}
/**
* @since 2.0
*/
@Override
public Map<String, String> getProperties() {
return ImmutableMap.copyOf(fProperties);
}
@Override
public Iterable<ITmfEventAspect<?>> getEventAspects() {
return BtfEventAspects.getAspects();
}
@Override
public synchronized void dispose() {
RandomAccessFile raf = fFileInput;
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
}
}
super.dispose();
}
@TmfSignalHandler
@Override
public void traceRangeUpdated(TmfTraceRangeUpdatedSignal signal) {
if (signal.getTrace() == this) {
try {
synchronized (this) {
/*
* Reset the file handle in case it has reached the end of
* the file already. Otherwise, it will not be able to read
* new data pass the previous end.
*/
initFile();
}
} catch (TmfTraceException e) {
Activator.logError(e.getLocalizedMessage(), e);
}
}
super.traceRangeUpdated(signal);
}
}