blob: b4111aaea63a065ce3d33320192b05fafb82f42e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 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
*******************************************************************************/
package org.eclipse.tracecompass.incubator.internal.fieldcount.core;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiGenericAspect;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiAnalysis;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableClass;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiData;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiLongNumber;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimeRange;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimestamp;
import org.eclipse.tracecompass.tmf.core.TmfCommonConstants;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;
import org.eclipse.tracecompass.tmf.core.event.aspect.TmfBaseAspects;
import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest.ExecutionType;
import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multiset;
/**
* Event count analysis, an on-demand analysis that generates Lami Tables while
* not being an LTTng Analysis report. This will help with textual logs in order
* to de-duplicate fields.
*
* @author Matthew Khouzam
*
*/
public class FieldCountAnalysis extends LamiAnalysis {
private static final long MASK = (1 << 10) - 1L;
/**
* This is a simple way to remove unique elements. There should be a smarter
* way to do this.
*/
private static final int MEMORY_SANITY_LIMIT = 10000;
/**
* Constructor
*/
public FieldCountAnalysis() {
super("Event Fields Count", false, trace -> true, Collections.emptyList());
}
@Override
protected synchronized void initialize() {
// do nothing
}
@Override
public boolean canExecute(ITmfTrace trace) {
return true;
}
private static int workRemaining(ITmfTrace trace) {
return (int) Math.min(trace.getNbEvents() / (MASK + 1), Integer.MAX_VALUE);
}
@Override
public List<LamiResultTable> execute(ITmfTrace trace, @Nullable TmfTimeRange timeRange, String extraParamsString, IProgressMonitor monitor) throws CoreException {
List<LamiResultTable> results = new ArrayList<>();
List<ITmfEventAspect<?>> aspects = new ArrayList<>();
TmfTimeRange tr = timeRange == null ? TmfTimeRange.ETERNITY : timeRange;
Set<String> forbidden = TmfBaseAspects.getBaseAspects().stream().map(aspect -> aspect.getName()).collect(Collectors.toSet());
Iterable<ITmfEventAspect<?>> eventAspects = getTraceAspects(trace);
for (ITmfEventAspect<?> aspect : eventAspects) {
Type[] genericInterfaces = aspect.getClass().getGenericInterfaces();
if (genericInterfaces.length > 0) {
Type type = genericInterfaces[0];
if (!type.getClass().isAssignableFrom(Number.class) && !(forbidden.contains(aspect.getName()))) {
aspects.add(aspect);
}
}
}
SubMonitor mon = SubMonitor.convert(monitor, "Event Count Analysis", workRemaining(trace));
AtomicLong done = new AtomicLong();
Map<String, Multiset<String>> eventAspectCounts = new HashMap<>();
TmfEventRequest req = new TmfEventRequest(ITmfEvent.class, tr, 0, Integer.MAX_VALUE, ExecutionType.BACKGROUND) {
@Override
public void handleData(ITmfEvent event) {
if (monitor.isCanceled()) {
cancel();
}
for (ITmfEventAspect<?> aspect : aspects) {
Object resolved = aspect.resolve(event);
if (resolved != null) {
Multiset<String> dataSet = eventAspectCounts.computeIfAbsent(aspect.getName(), unused -> HashMultiset.create());
if (dataSet.size() < MEMORY_SANITY_LIMIT || dataSet.contains(resolved)) {
dataSet.add(String.valueOf(resolved));
}
}
}
if ((done.incrementAndGet() & MASK) == 0) {
mon.setWorkRemaining(workRemaining(trace));
mon.worked(1);
monitor.setTaskName("Event Count Analysis (" + done.get() + ")");
}
}
};
trace.sendRequest(req);
try {
req.waitForCompletion();
for (Entry<String, Multiset<String>> entry : eventAspectCounts.entrySet()) {
Multiset<String> dataSet = entry.getValue();
List<LamiTableEntry> entries = new ArrayList<>();
for (String element : dataSet.elementSet()) {
/* A row is an array of cells */
List<LamiData> data = Arrays.asList(new LamiString(element), new LamiLongNumber((long) dataSet.count(element)));
entries.add(new LamiTableEntry(data));
}
List<LamiTableEntryAspect> tableAspects = Arrays.asList(new LamiCategoryAspect(entry.getKey(), 0), new LamiCountAspect("count", 1));
LamiTableClass tableClass = new LamiTableClass(entry.getKey(), entry.getKey(), tableAspects, Collections.emptySet());
LamiResultTable lrt = new LamiResultTable(createTimeRange(tr), tableClass, entries);
results.add(lrt);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return results;
}
// copied from TmfEventsEditor
/**
* Get the event table for the given trace. It will be of the type defined
* by the extension point if applicable, else it will be a default table
* with the extension-point-defined columns (if any).
*
* @param trace
* The event table is for this trace
* @param parent
* The parent composite of the table
* @param cacheSize
* The cache size to use
* @return The event table for the trace
*/
private static Iterable<ITmfEventAspect<?>> getTraceAspects(ITmfTrace trace) {
if (trace instanceof TmfExperiment) {
return getExperimentAspects((TmfExperiment) trace);
}
return trace.getEventAspects();
}
/**
* Get the events table for an experiment. If all traces in the experiment
* are of the same type, use the same behavior as if it was one trace of
* that type.
*
* @param experiment
* the experiment
* @param parent
* the parent Composite
* @param cacheSize
* the event table cache size
* @return An event table of the appropriate type
*/
private static Iterable<ITmfEventAspect<?>> getExperimentAspects(
final TmfExperiment experiment) {
List<ITmfTrace> traces = experiment.getTraces();
ImmutableSet.Builder<ITmfEventAspect<?>> builder = new ImmutableSet.Builder<>();
/* For experiments, we'll add a "trace name" aspect/column */
builder.add(TmfBaseAspects.getTraceNameAspect());
String commonTraceType = getCommonTraceType(experiment);
if (commonTraceType != null) {
/*
* All the traces in this experiment are of the same type, let's
* just use the normal table for that type.
*/
builder.addAll(traces.get(0).getEventAspects());
} else {
/*
* There are different trace types in the experiment, so we are
* definitely using a TmfEventsTable. Aggregate the columns from all
* trace types.
*/
for (ITmfTrace trace : traces) {
Iterable<ITmfEventAspect<?>> traceAspects = trace.getEventAspects();
builder.addAll(traceAspects);
}
}
return builder.build();
}
/**
* Check if an experiment contains traces of all the same type. If so,
* returns this type as a String. If not, returns null.
*
* @param experiment
* The experiment
* @return The common trace type if there is one, or 'null' if there are
* different types.
*/
private static @Nullable String getCommonTraceType(TmfExperiment experiment) {
String commonTraceType = null;
try {
for (final ITmfTrace trace : experiment.getTraces()) {
final IResource resource = trace.getResource();
if (resource == null) {
return null;
}
final String traceType = resource.getPersistentProperty(TmfCommonConstants.TRACETYPE);
if ((commonTraceType != null) && !commonTraceType.equals(traceType)) {
return null;
}
commonTraceType = traceType;
}
} catch (CoreException e) {
/*
* One of the traces didn't advertise its type, we can't infer
* anything.
*/
return null;
}
return commonTraceType;
}
// end of copied from TmfEventsEditor
/**
* Todo, move to LAMI
*/
private static LamiTimeRange createTimeRange(TmfTimeRange timeRange) {
return new LamiTimeRange(new LamiTimestamp(timeRange.getStartTime().toNanos()), new LamiTimestamp(timeRange.getStartTime().toNanos()));
}
/**
* Todo, move to LAMI
*/
private final class LamiString extends LamiData {
private final String fElement;
private LamiString(String element) {
fElement = element;
}
@Override
public @NonNull String toString() {
return fElement;
}
}
/**
* Count aspect, generic
*
* TODO: move to LAMI
*
* @author Matthew Khouzam
*
*/
private final class LamiCountAspect extends LamiGenericAspect {
private LamiCountAspect(String name, int column) {
super(name, null, column, true, false);
}
}
/**
* Category aspect, generic
*
* TODO: move to LAMI
*
* @author Matthew Khouzam
*
*/
private final class LamiCategoryAspect extends LamiGenericAspect {
private LamiCategoryAspect(String name, int column) {
super(name, null, column, false, false);
}
}
}