blob: f8842b9819b561de7245889ef3938ad4d8c0b04d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018, 2020 Ericsson and others
*
* 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
*******************************************************************************/
package org.eclipse.tracecompass.incubator.internal.kernel.core.fileaccess;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.incubator.analysis.core.model.ModelManager;
import org.eclipse.tracecompass.incubator.internal.kernel.core.Activator;
import org.eclipse.tracecompass.incubator.internal.kernel.core.fileaccess.FileEntryModel.Type;
import org.eclipse.tracecompass.incubator.internal.kernel.core.filedescriptor.FileDescriptorStateProvider;
import org.eclipse.tracecompass.incubator.internal.kernel.core.filedescriptor.ThreadEntryModel;
import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
import org.eclipse.tracecompass.internal.tmf.core.model.timegraph.AbstractTimeGraphDataProvider;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils;
import org.eclipse.tracecompass.tmf.core.dataprovider.X11ColorUtils;
import org.eclipse.tracecompass.tmf.core.model.CommonStatusMessage;
import org.eclipse.tracecompass.tmf.core.model.IOutputStyleProvider;
import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle;
import org.eclipse.tracecompass.tmf.core.model.OutputStyleModel;
import org.eclipse.tracecompass.tmf.core.model.StyleProperties;
import org.eclipse.tracecompass.tmf.core.model.filters.SelectionTimeQueryFilter;
import org.eclipse.tracecompass.tmf.core.model.filters.TimeQueryFilter;
import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphArrow;
import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphRowModel;
import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphState;
import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphEntryModel;
import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphModel;
import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphRowModel;
import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphState;
import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeModel;
import org.eclipse.tracecompass.tmf.core.response.ITmfResponse;
import org.eclipse.tracecompass.tmf.core.response.ITmfResponse.Status;
import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.common.collect.TreeMultimap;
/**
* <p>
* File Descriptor data Provider
* </p>
* <p>
* Shows per-file access
* </p>
*
* @author Matthew Khouzam
*/
@SuppressWarnings("restriction")
public class FileAccessDataProvider extends AbstractTimeGraphDataProvider<@NonNull FileAccessAnalysis, @NonNull TimeGraphEntryModel>
implements IOutputStyleProvider {
/**
* Suffix for dataprovider ID
*/
public static final String SUFFIX = ".dataprovider"; //$NON-NLS-1$ ;
/**
* String for the tid parameter of this view
*/
public static final String TID_PARAM = "tid"; //$NON-NLS-1$
private static final Pattern IS_INTEGER = Pattern.compile("\\d+"); //$NON-NLS-1$
private static final String META_IO_NAME = "Meta IO"; //$NON-NLS-1$
private static final String IO_NAME = "IO"; //$NON-NLS-1$
/* The map of basic styles */
private static final Map<String, OutputElementStyle> STATE_MAP;
/*
* A map of styles names to a style that has the basic style as parent, to
* avoid returning complete styles for each state
*/
private static final Map<String, OutputElementStyle> STYLE_MAP = new HashMap<>();
static {
/* Build three different styles to use as examples */
ImmutableMap.Builder<String, OutputElementStyle> builder = new ImmutableMap.Builder<>();
builder.put(META_IO_NAME, new OutputElementStyle(null, ImmutableMap.of(StyleProperties.STYLE_NAME, META_IO_NAME,
StyleProperties.BACKGROUND_COLOR, String.valueOf(X11ColorUtils.toHexColor(174, 123, 131)))));
builder.put(IO_NAME, new OutputElementStyle(null, ImmutableMap.of(StyleProperties.STYLE_NAME, IO_NAME,
StyleProperties.BACKGROUND_COLOR, String.valueOf(X11ColorUtils.toHexColor(140, 180, 165)))));
STATE_MAP = builder.build();
}
private static final int OFFSET = 100000;
private static final AtomicInteger STRING_VALUE = new AtomicInteger(OFFSET);
private Map<String, Integer> fileIds = new HashMap<>();
/**
* Constructor
*
* @param trace
* the trace this provider represents
* @param analysisModule
* the analysis encapsulated by this provider
*/
public FileAccessDataProvider(ITmfTrace trace, FileAccessAnalysis analysisModule) {
super(trace, analysisModule);
}
@Deprecated
@Override
public @NonNull TmfModelResponse<@NonNull List<@NonNull ITimeGraphArrow>> fetchArrows(@NonNull TimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
Map<String, Object> parameters = FetchParametersUtils.timeQueryToMap(filter);
return fetchArrows(parameters, monitor);
}
@Override
public TmfModelResponse<List<ITimeGraphArrow>> fetchArrows(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
return new TmfModelResponse<>(null, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
@Override
public @NonNull String getId() {
return getAnalysisModule().getId() + SUFFIX;
}
@Override
protected @Nullable TimeGraphModel getRowModel(ITmfStateSystem ss, Map<String, Object> parameters, @Nullable IProgressMonitor monitor) throws StateSystemDisposedException {
SelectionTimeQueryFilter filter = FetchParametersUtils.createSelectionTimeQuery(parameters);
if (filter == null) {
return null;
}
TreeMultimap<Integer, ITmfStateInterval> intervals = TreeMultimap.create(Comparator.naturalOrder(),
Comparator.comparing(ITmfStateInterval::getStartTime));
Map<@NonNull Long, @NonNull Integer> entries = getSelectedEntries(filter);
Collection<Long> times = getTimes(filter, ss.getStartTime(), ss.getCurrentEndTime());
/* Do the actual query */
for (ITmfStateInterval interval : ss.query2D(entries.values(), times)) {
if (monitor != null && monitor.isCanceled()) {
return new TimeGraphModel(Collections.emptyList());
}
intervals.put(interval.getAttribute(), interval);
}
Map<@NonNull Integer, @NonNull Predicate<@NonNull Multimap<@NonNull String, @NonNull Object>>> predicates = new HashMap<>();
Multimap<@NonNull Integer, @NonNull String> regexesMap = DataProviderParameterUtils.extractRegexFilter(parameters);
if (regexesMap != null) {
predicates.putAll(computeRegexPredicate(regexesMap));
}
List<@NonNull ITimeGraphRowModel> rows = new ArrayList<>();
for (Map.Entry<@NonNull Long, @NonNull Integer> entry : entries.entrySet()) {
if (monitor != null && monitor.isCanceled()) {
return new TimeGraphModel(Collections.emptyList());
}
List<ITimeGraphState> eventList = new ArrayList<>();
for (ITmfStateInterval interval : intervals.get(entry.getValue())) {
long startTime = interval.getStartTime();
long duration = interval.getEndTime() - startTime + 1;
Object state = interval.getValue();
String label = null;
TimeGraphState value = null;
if (Integer.valueOf(1).equals(state)) {
ITmfTrace trace = getTrace();
List<Integer> sub = ss.getSubAttributes(interval.getAttribute(), false);
for (ITmfStateInterval threadInterval : ss.query2D(sub, Collections.singleton(startTime))) {
Object sv = threadInterval.getValue();
if (sv != null) {
int tid = getTid(ss, threadInterval.getAttribute());
label = getThreadName(tid, interval.getStartTime(), trace) + " (" + tid + ")"; //$NON-NLS-1$ //$NON-NLS-2$
value = new TimeGraphState(startTime, duration, label,
STYLE_MAP.computeIfAbsent(META_IO_NAME, n -> new OutputElementStyle(n)));
break;
}
}
}
if (state != null && value == null) {
value = new TimeGraphState(startTime, duration, null, STYLE_MAP.computeIfAbsent(IO_NAME, n -> new OutputElementStyle(n)));
}
if (value == null) {
value = new TimeGraphState(startTime, duration, Integer.MIN_VALUE);
}
applyFilterAndAddState(eventList, value, entry.getKey(), predicates, monitor);
}
rows.add(new TimeGraphRowModel(entry.getKey(), eventList));
}
return new TimeGraphModel(rows);
}
@Override
protected boolean isCacheable() {
return false;
}
@Deprecated
@Override
public TmfModelResponse<Map<String, String>> fetchTooltip(SelectionTimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
Map<String, Object> parameters = FetchParametersUtils.selectionTimeQueryToMap(filter);
return fetchTooltip(parameters, monitor);
}
@Override
public TmfModelResponse<Map<String, String>> fetchTooltip(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
ITmfStateSystem ss = getAnalysisModule().getStateSystem();
SelectionTimeQueryFilter filter = FetchParametersUtils.createSelectionTimeQuery(fetchParameters);
if (filter == null) {
return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.INCORRECT_QUERY_PARAMETERS);
}
Collection<@NonNull Integer> quarks = getSelectedEntries(filter).values();
Map<String, String> retMap = new LinkedHashMap<>();
if (quarks.size() != 1) {
return new TmfModelResponse<>(retMap, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
long start = filter.getStart();
if (ss == null || quarks.size() != 1 || !getAnalysisModule().isQueryable(start)) {
/*
* We need the ss to query, we should only be querying one attribute
* and the query times should be valid.
*/
return new TmfModelResponse<>(retMap, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
int quark = quarks.iterator().next();
try {
ITmfStateInterval current = ss.querySingleState(start, quark);
int resQuark = ss.optQuarkAbsolute(FileDescriptorStateProvider.RESOURCES);
if (resQuark == ITmfStateSystem.INVALID_ATTRIBUTE) {
return new TmfModelResponse<>(retMap, ITmfResponse.Status.FAILED, "Bizarre quark value for the file resources"); //$NON-NLS-1$
}
int parentQuark = ss.getParentAttributeQuark(quark);
if (parentQuark == resQuark) {
// This is a file name, add the number of opened fd and full
// file name
String fileName = ss.getAttributeName(quark);
retMap.put("File Name", fileName); //$NON-NLS-1$
Object value = current.getValue();
retMap.put("Number of opened FD", (value instanceof Number) ? String.valueOf(value) : "0"); //$NON-NLS-1$ //$NON-NLS-2$
return new TmfModelResponse<>(retMap, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
int parentQuark2 = ss.getParentAttributeQuark(parentQuark);
if (parentQuark2 == resQuark) {
// This is a thread, add the fd and full file name
String fileName = ss.getAttributeName(parentQuark);
retMap.put("File Name", fileName); //$NON-NLS-1$
Object value = current.getValue();
if (value instanceof Number) {
retMap.put("FD", String.valueOf(value)); //$NON-NLS-1$
}
return new TmfModelResponse<>(retMap, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
// Not something we know what to do
return new TmfModelResponse<>(retMap, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
} catch (StateSystemDisposedException e) {
// Ignore, nothing to do
}
return new TmfModelResponse<>(null, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
@Override
protected TmfTreeModel<TimeGraphEntryModel> getTree(ITmfStateSystem ss, Map<String, Object> parameters, @Nullable IProgressMonitor monitor) throws StateSystemDisposedException {
Object tidParam = parameters.get(TID_PARAM);
Integer selectedTids = (tidParam instanceof Integer) ? (Integer) tidParam : -1;
Builder<@NonNull TimeGraphEntryModel> builder = new Builder<>();
long rootId = getId(ITmfStateSystem.ROOT_ATTRIBUTE);
ITmfTrace trace = getTrace();
builder.add(new TimeGraphEntryModel(rootId, -1, String.valueOf(trace.getName()), ss.getStartTime(), ss.getCurrentEndTime()));
try {
addResources(ss, builder, ss.getQuarkAbsolute(FileDescriptorStateProvider.RESOURCES), rootId, selectedTids);
} catch (AttributeNotFoundException e) {
Activator.getInstance().logError(e.getMessage(), e);
}
return new TmfTreeModel<>(Collections.emptyList(), builder.build());
}
private void addResources(ITmfStateSystem ss, Builder<@NonNull TimeGraphEntryModel> builder, int quark, long parentId, Integer filter) {
List<@NonNull Integer> fileQuarks = ss.getSubAttributes(quark, false);
String ramFiles = "in memory"; //$NON-NLS-1$
boolean hasMemfile = false;
int ramHash = fileIds.computeIfAbsent(ramFiles, a -> STRING_VALUE.incrementAndGet());
long ramId = getId(ramHash);
TimeGraphEntryModel ramElement = new TimeGraphEntryModel(ramId, parentId, ramFiles, ss.getStartTime(), ss.getCurrentEndTime());
for (Integer fileQuark : fileQuarks) {
String name = ss.getAttributeName(fileQuark);
long id = getId(fileQuark);
List<Integer> sub = ss.getSubAttributes(fileQuark, false);
Set<Integer> contributingTids = sub.stream()
.map(ss::getAttributeName)
.filter(FileAccessDataProvider::isInteger)
.map(Integer::parseInt)
.collect(Collectors.toSet());
if (filter == -1 || contributingTids.contains(filter)) {
if (name.startsWith(File.separator)) {
String[] segments = name.split(File.separator);
StringBuilder sb = new StringBuilder();
long parent = parentId;
builder.add(new FileEntryModel(getId(fileIds.computeIfAbsent(File.separator, a -> STRING_VALUE.incrementAndGet())), parentId, File.separator, ss.getStartTime(), ss.getCurrentEndTime(), false, Type.Directory));
for (int i = 0; i < segments.length - 1; i++) {
sb.append('/').append(segments[i]);
String fileName = sb.toString();
Long fileId = getId(fileIds.computeIfAbsent(fileName, a -> STRING_VALUE.incrementAndGet()));
builder.add(new FileEntryModel(fileId, parent, segments[i] + File.separator, ss.getStartTime(), ss.getCurrentEndTime(), false, Type.Directory));
parent = fileId;
}
builder.add(new FileEntryModel(id, parent, name.substring(1 + name.lastIndexOf(File.separator)), ss.getStartTime(), ss.getCurrentEndTime(), true, Type.File));
} else {
if (!hasMemfile) {
builder.add(ramElement);
hasMemfile = true;
}
builder.add(new FileEntryModel(id, ramId, name, ss.getStartTime(), ss.getCurrentEndTime(), true, FileEntryModel.Type.InRam));
}
List<@NonNull Integer> threadQuarks = ss.getSubAttributes(fileQuark, false);
if (threadQuarks.size() == 1) {
continue;
}
for (Integer threadQuark : threadQuarks) {
ITmfTrace trace = getTrace();
// Broken
int tid = getTid(ss, threadQuark);
if (tid != -1) {
String threadName = getThreadName(tid, ss.getStartTime(), trace);
builder.add(new ThreadEntryModel(getId(threadQuark), id, String.valueOf(threadName), ss.getStartTime(), ss.getCurrentEndTime(), true, tid));
}
}
}
}
}
private static @Nullable String getThreadName(int tid, long time, ITmfTrace trace) {
if (tid != -1) {
return ModelManager.getModelFor(trace.getHostId()).getExecName(tid, time);
}
return null;
}
private static int getTid(ITmfStateSystem ss, int threadQuark) {
String attributeName = ss.getAttributeName(threadQuark);
if (isInteger(attributeName)) {
return Integer.parseInt(attributeName);
}
return -1;
}
private static boolean isInteger(String attributeName) {
return IS_INTEGER.matcher(attributeName).matches();
}
public @Nullable Long getBytesRead(long start, long end, long attributeId) {
ITmfStateSystem ss = getAnalysisModule().getStateSystem();
if (ss == null) {
return null;
}
Map<Long, Integer> selectedEntries = getSelectedEntries(new SelectionTimeQueryFilter(Arrays.asList(start, end), Collections.singleton(attributeId)));
Integer startingNodeQuark = selectedEntries.get(attributeId);
if (startingNodeQuark == null || startingNodeQuark >= OFFSET) {
return null;
}
int readQuark = ss.optQuarkRelative(startingNodeQuark, FileDescriptorStateProvider.READ);
return getdelta(start, end, ss, readQuark);
}
public @Nullable Long getBytesWrite(long start, long end, long attributeId) {
ITmfStateSystem ss = getAnalysisModule().getStateSystem();
if (ss == null) {
return null;
}
Map<Long, Integer> selectedEntries = getSelectedEntries(new SelectionTimeQueryFilter(Arrays.asList(start, end), Collections.singleton(attributeId)));
Integer startingNodeQuark = selectedEntries.get(attributeId);
if (startingNodeQuark == null || startingNodeQuark >= OFFSET) {
return null;
}
int readQuark = ss.optQuarkRelative(startingNodeQuark, FileDescriptorStateProvider.WRITE);
return getdelta(start, end, ss, readQuark);
}
private static @Nullable Long getdelta(long start, long end, ITmfStateSystem ss, int readQuark) {
if (readQuark == ITmfStateSystem.INVALID_ATTRIBUTE) {
return null;
}
try {
ITmfStateInterval startInterval = ss.querySingleState(start, readQuark);
ITmfStateInterval endInterval = ss.querySingleState(end, readQuark);
return endInterval.getValueLong() - startInterval.getValueLong();
} catch (StateSystemDisposedException e) {
return null;
}
}
@Override
public @NonNull Multimap<@NonNull String, @NonNull Object> getFilterData(long entryId, long time, @Nullable IProgressMonitor monitor) {
Multimap<@NonNull String, @NonNull Object> data = HashMultimap.create();
data.putAll(super.getFilterData(entryId, time, monitor));
SelectionTimeQueryFilter filter = new SelectionTimeQueryFilter(Collections.singletonList(time), Collections.singleton(Objects.requireNonNull(entryId)));
TmfModelResponse<Map<String, String>> response = fetchTooltip(filter, monitor);
Map<@NonNull String, @NonNull String> model = response.getModel();
if (model != null) {
for (Entry<String, String> entry : model.entrySet()) {
data.put(entry.getKey(), entry.getValue());
}
}
return data;
}
@Override
public TmfModelResponse<OutputStyleModel> fetchStyle(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
return new TmfModelResponse<>(new OutputStyleModel(STATE_MAP), Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
}