/*******************************************************************************
* Copyright (c) 2014 Ericsson
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alexandre Montplaisir - Initial API and implementation
*******************************************************************************/
package org.eclipse.tracecompass.tmf.core.trace;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.common.core.StreamUtils;
import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
import org.eclipse.tracecompass.tmf.core.component.ITmfEventProvider;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;
import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* Utility methods for ITmfTrace's.
*
* @author Alexandre Montplaisir
*/
@NonNullByDefault
public final class TmfTraceUtils {
private static final int MAX_NB_BINARY_BYTES = 2048;
private TmfTraceUtils() {
}
/**
* Return the first result of the first analysis module belonging to this trace or its children,
* with the specified ID and class.
*
* @param trace
* The trace for which you want the modules
* @param moduleClass
* Returned modules must extend this class
* @param id
* The ID of the analysis module
* @return The analysis module with specified class and ID, or null if no
* such module exists.
*/
public static @Nullable <T extends IAnalysisModule> T getAnalysisModuleOfClass(ITmfTrace trace,
Class<T> moduleClass, String id) {
Iterable<T> modules = getAnalysisModulesOfClass(trace, moduleClass);
for (T module : modules) {
if (id.equals(module.getId())) {
return module;
}
}
return null;
}
/**
* Return the analysis modules that are of a given class. The modules will be
* cast to the requested class. If the trace has children, the childrens modules
* are also returned.
*
* @param trace
* The trace for which you want the modules, the children trace modules
* are added as well.
* @param moduleClass
* Returned modules must extend this class
* @return List of modules of class moduleClass
*/
public static <T> Iterable<@NonNull T> getAnalysisModulesOfClass(ITmfTrace trace, Class<T> moduleClass) {
Iterable<IAnalysisModule> analysisModules = trace.getAnalysisModules();
List<@NonNull T> modules = new ArrayList<>();
for (IAnalysisModule module : analysisModules) {
if (moduleClass.isAssignableFrom(module.getClass())) {
modules.add(checkNotNull(moduleClass.cast(module)));
}
}
for (ITmfEventProvider child : trace.getChildren()) {
if (child instanceof ITmfTrace) {
ITmfTrace childTrace = (ITmfTrace) child;
Iterables.addAll(modules, getAnalysisModulesOfClass(childTrace, moduleClass));
}
}
return modules;
}
/**
* Return the first result of the first aspect that resolves as non null for
* the event received in parameter. If the returned value is not null, it
* can be safely cast to the aspect's class proper return type.
*
* @param trace
* The trace for which you want the event aspects
* @param aspectClass
* The class of the aspect(s) to resolve
* @param event
* The event for which to get the aspect
* @return The first result of the
* {@link ITmfEventAspect#resolve(ITmfEvent)} that returns non null
* for the event or {@code null} otherwise
*/
public static <T extends ITmfEventAspect<?>> @Nullable Object resolveEventAspectOfClassForEvent(
ITmfTrace trace, Class<T> aspectClass, ITmfEvent event) {
return StreamUtils.getStream(trace.getEventAspects())
.filter(aspect -> aspectClass.isAssignableFrom(aspect.getClass()))
.map(aspect -> aspect.resolve(event))
.filter(obj -> obj != null)
.findFirst().orElse(null);
}
/**
* Return the first result of the first aspect that resolves as a non-null
* Integer for the event received in parameter. If no matching aspects are
* found then null is returned.
*
* @param trace
* The trace for which you want the event aspects
* @param aspectClass
* The class of the aspect(s) to resolve
* @param event
* The event for which to get the aspect
* @return Integer of the first result of the
* {@link ITmfEventAspect#resolve(ITmfEvent)} that returns non null
* for the event or {@code null} otherwise
* @since 2.0
*/
public static <T extends ITmfEventAspect<Integer>> @Nullable Integer resolveIntEventAspectOfClassForEvent(
ITmfTrace trace, Class<T> aspectClass, ITmfEvent event) {
return StreamUtils.getStream(trace.getEventAspects())
.filter(aspect -> aspectClass.isAssignableFrom(aspect.getClass()))
/* Enforced by the T parameter bounding */
.map(aspect -> (Integer) aspect.resolve(event))
.filter(obj -> obj != null)
.findFirst().orElse(null);
}
/**
* Checks for text file.
*
* Note that it checks for binary value 0 in the first MAX_NB_BINARY_BYTES
* bytes to determine if the file is text.
*
* @param file
* the file to check. Caller has to make sure that file exists.
* @return true if it is binary else false
* @throws IOException
* if IOException occurs
* @since 1.2
*/
public static boolean isText(File file) throws IOException {
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file))) {
int count = 0;
int val = bufferedInputStream.read();
while ((count < MAX_NB_BINARY_BYTES) && (val >= 0)) {
if (val == 0) {
return false;
}
count++;
val = bufferedInputStream.read();
}
}
return true;
}
// ------------------------------------------------------------------------
// Event matching methods
// ------------------------------------------------------------------------
/**
* Retrieve from a trace the next event, from a starting rank, matching the
* given predicate.
*
* @param trace
* The trace
* @param startRank
* The rank of the event at which to start searching. Use
* <code>0</code> to search from the start of the trace.
* @param predicate
* The predicate to test events against
* @param monitor
* Optional progress monitor that can be used to cancel the
* operation
* @return The first event matching the predicate, or null if the end of the
* trace was reached and no event was found
* @since 2.1
*/
public static @Nullable ITmfEvent getNextEventMatching(ITmfTrace trace, long startRank,
Predicate<ITmfEvent> predicate, @Nullable IProgressMonitor monitor) {
if (monitor != null && monitor.isCanceled()) {
return null;
}
/* rank + 1 because we do not want to include the start event itself in the search */
EventMatchingRequest req = new EventMatchingRequest(startRank + 1, predicate, false);
trace.sendRequest(req);
try {
/* Check periodically if the job was cancelled */
req.waitForStart();
while (req.isRunning()) {
Thread.sleep(200);
if (monitor != null && monitor.isCanceled()) {
req.cancel();
return null;
}
}
req.waitForCompletion();
} catch (InterruptedException e) {
return null;
}
return req.getFoundEvent();
}
/**
* Retrieve from a trace the previous event, from a given rank, matching the
* given predicate.
*
* @param trace
* The trace
* @param startRank
* The rank of the event at which to start searching backwards.
* @param predicate
* The predicate to test events against
* @param monitor Optional progress monitor that can be used to cancel the
* operation
* @return The first event found matching the predicate, or null if the
* beginning of the trace was reached and no event was found
* @since 2.1
*/
public static @Nullable ITmfEvent getPreviousEventMatching(ITmfTrace trace, long startRank,
Predicate<ITmfEvent> predicate, @Nullable IProgressMonitor monitor) {
if (monitor != null && monitor.isCanceled()) {
return null;
}
/*
* We are going to do a series of queries matching the trace's cache
* size in length (which should minimize on-disk seeks), then iterate on
* the found events in reverse order until we find a match.
*/
int step = trace.getCacheSize();
/*
* If we are close to the beginning of the trace, make sure we only look
* for the events before the startRank.
*/
if (startRank < step) {
step = (int) startRank;
}
long currentRank = startRank;
try {
while (currentRank > 0) {
currentRank = Math.max(currentRank - step, 0);
List<ITmfEvent> list = new ArrayList<>(step);
ArrayFillingRequest req = new ArrayFillingRequest(currentRank, step, list);
trace.sendRequest(req);
/* Check periodically if the job was cancelled */
req.waitForStart();
while (req.isRunning()) {
Thread.sleep(200);
if (monitor != null && monitor.isCanceled()) {
req.cancel();
return null;
}
}
req.waitForCompletion();
Optional<ITmfEvent> matchingEvent = Lists.reverse(list).stream()
.filter(predicate)
.findFirst();
if (matchingEvent.isPresent()) {
/* We found an event matching, return it! */
return matchingEvent.get();
}
/* Keep searching, next loop */
}
} catch (InterruptedException e) {
return null;
}
/*
* We searched up to the beginning of the trace and didn't find
* anything.
*/
return null;
}
/**
* Event request looking for an event matching a Predicate.
*/
private static class EventMatchingRequest extends TmfEventRequest {
private final Predicate<ITmfEvent> fPredicate;
private final boolean fReturnLast;
private @Nullable ITmfEvent fFoundEvent = null;
/**
* Basic constructor, will query the trace until the end.
*
* @param startRank
* The rank at which to start, use 0 for the beginning
* @param predicate
* The predicate to test against each event
* @param returnLast
* Should we return the last or first event found. If false,
* the request ends as soon as a matching event is found. If
* false, we will go through all events to find a possible
* last-match.
*/
public EventMatchingRequest(long startRank, Predicate<ITmfEvent> predicate, boolean returnLast) {
super(ITmfEvent.class, startRank, ALL_DATA, ExecutionType.FOREGROUND);
fPredicate = predicate;
fReturnLast = returnLast;
}
/**
* Basic constructor, will query the trace the limit is reached.
*
* @param startRank
* The rank at which to start, use 0 for the beginning
* @param limit
* The limit on the number of events
* @param predicate
* The predicate to test against each event
* @param returnLast
* Should we return the last or first event found. If false,
* the request ends as soon as a matching event is found. If
* false, we will go through all events to find a possible
* last-match.
*/
public EventMatchingRequest(long startRank, int limit, Predicate<ITmfEvent> predicate, boolean returnLast) {
super(ITmfEvent.class, startRank, limit, ExecutionType.FOREGROUND);
fPredicate = predicate;
fReturnLast = returnLast;
}
public @Nullable ITmfEvent getFoundEvent() {
return fFoundEvent;
}
@Override
public void handleData(ITmfEvent event) {
super.handleData(event);
if (fPredicate.test(event)) {
fFoundEvent = event;
if (!fReturnLast) {
this.done();
}
}
}
}
/**
* Event request that simply puts all returned events into a list passed in
* parameter.
*/
private static class ArrayFillingRequest extends TmfEventRequest {
private final List<ITmfEvent> fList;
public ArrayFillingRequest(long startRank, int limit, List<ITmfEvent> listToFill) {
super(ITmfEvent.class, startRank, limit, ExecutionType.FOREGROUND);
fList = listToFill;
}
@Override
public void handleData(ITmfEvent event) {
super.handleData(event);
fList.add(event);
}
}
}