blob: 2d77f1938bc48781b71532d120690fd4650285c5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 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.uftrace.core.trace;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
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 org.apache.commons.io.FilenameUtils;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.analysis.os.linux.core.event.aspect.LinuxPidAspect;
import org.eclipse.tracecompass.analysis.os.linux.core.event.aspect.LinuxTidAspect;
import org.eclipse.tracecompass.incubator.internal.uftrace.core.Activator;
import org.eclipse.tracecompass.incubator.internal.uftrace.core.trace.SymParser.Symbol;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.ITmfEventType;
import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;
import org.eclipse.tracecompass.tmf.core.event.aspect.TmfBaseAspects;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException;
import org.eclipse.tracecompass.tmf.core.project.model.ITmfPropertiesProvider;
import org.eclipse.tracecompass.tmf.core.symbols.ISymbolProvider;
import org.eclipse.tracecompass.tmf.core.symbols.ISymbolProviderFactory;
import org.eclipse.tracecompass.tmf.core.symbols.TmfResolvedSymbol;
import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTraceKnownSize;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTraceWithPreDefinedEvents;
import org.eclipse.tracecompass.tmf.core.trace.TmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TraceValidationStatus;
import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation;
import org.eclipse.tracecompass.tmf.core.trace.location.TmfLongLocation;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
/**
* Trace with the following data
* <ul>
* <li>info : various information about uftrace and running process</li>
* <li>task.txt : task and session information</li>
* <li>sid-<SESSION_ID>.map : memory mapping for a session</li>
* <li>[PROGRAM].sym : (function) symbol address and name</li>
* <li>[TID].dat : trace data for each task of given <TID></li>
* <li>kernel_header : kernel ftrace header info (only if kernel tracing was
* used)</li>
* <li>kallsyms : kernel symbol information (ditto)</li>
* <li>kernel-cpuX.dat : per-cpu kernel tracing data (ditto)</li>
* </ul>
*
* @author Matthew Khouzam
*/
public class Uftrace extends TmfTrace implements ITmfPropertiesProvider,
ITmfTraceKnownSize, ITmfTraceWithPreDefinedEvents {
private Collection<DatParser> fDats = new ArrayList<>();
private Map<Long, MapParser> fMap = new HashMap<>();
private Map<String, SymParser> fSyms = new HashMap<>();
private TaskParser fTasks;
private TmfLongLocation fCurrentLoc = new TmfLongLocation(0L);
private InfoParser fInfo;
private long fSize;
private final ISymbolProvider fSymbolProvider = new UfTraceSymbolProvider();
private final @NonNull TidAspect fTidAspect = new TidAspect();
private final @NonNull PidAspect fPidAspect = new PidAspect();
private final @NonNull ExecAspect fExecAspect = new ExecAspect();
private @Nullable String fHostId = null;
@Override
public Iterable<ITmfEventAspect<?>> getEventAspects() {
return ImmutableList.of(TmfBaseAspects.getTimestampAspect(), TmfBaseAspects.getEventTypeAspect(),
new ITmfEventAspect<Integer>() {
@Override
public String getName() {
return "depth"; //$NON-NLS-1$
}
@Override
public String getHelpText() {
return ""; //$NON-NLS-1$
}
@Override
public @Nullable Integer resolve(ITmfEvent event) {
Object fieldValue = event.getContent().getValue();
if (fieldValue instanceof DatEvent) {
return ((DatEvent) fieldValue).getDepth();
}
return null;
}
}, new ITmfEventAspect<String>() {
@Override
public String getName() {
return "Address"; //$NON-NLS-1$
}
@Override
public String getHelpText() {
return ""; //$NON-NLS-1$
}
@Override
public @Nullable String resolve(ITmfEvent event) {
if (event.getContent().getValue() instanceof DatEvent) {
DatEvent datEvent = (DatEvent) event.getContent().getValue();
TmfResolvedSymbol symbol = fSymbolProvider.getSymbol(datEvent.getTid(), 0, datEvent.getAddress());
if (symbol != null) {
return symbol.getSymbolName();
}
}
return null;
}
}, getTidAspect(), getPidAspect(), getExecAspect()
);
}
@Override
public IStatus validate(IProject project, String path) {
File dir = new File(path);
int confidence = 0;
if (dir.isDirectory()) {
confidence += 5;
List<File> data = Arrays.asList(dir.listFiles());
boolean hasDat = false;
boolean hasMap = false;
boolean hasSym = false;
for (File file : data) {
String extension = FilenameUtils.getExtension(file.getName());
if (extension.equals("dat")) { //$NON-NLS-1$
try {
// TODO: Also support the perf-cpu data. For now, it
// does not contain uftrace data
if (file.getName().startsWith("perf-cpu")) { //$NON-NLS-1$
continue;
}
DatParser dp = new DatParser(file);
// read first event (really check magic number header)
dp.iterator().next();
confidence += 4;
hasDat = true;
} catch (IllegalArgumentException e) {
// we don't want a failing trace
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "dat parsing error", e); //$NON-NLS-1$
}
}
if (extension.equals("map")) { //$NON-NLS-1$
try {
if (MapParser.create(file) != null) {
confidence += 4;
}
hasMap = true;
} catch (IOException e) {
// we don't want a failing trace
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "map parsing error", e); //$NON-NLS-1$
}
}
if (extension.equals("sym")) { //$NON-NLS-1$
hasSym = true;
confidence += 3;
}
}
if (hasMap && hasDat && hasSym && confidence > 10) {
return new TraceValidationStatus(confidence, Activator.class.getCanonicalName());
}
}
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Most probably not a UFTrace"); //$NON-NLS-1$
}
@Override
public void initTrace(IResource resource, String path, Class<? extends ITmfEvent> type) throws TmfTraceException {
File dir = new File(path);
if (!dir.isDirectory()) {
throw new TmfTraceException("trace is not a directory"); //$NON-NLS-1$
}
super.initTrace(resource, path, type);
for (File child : dir.listFiles()) {
String name = child.getName();
try {
if (name.endsWith(".dat")) { //$NON-NLS-1$
// TODO: Also support the perf-cpu data. For now, it does
// not contain uftrace data
if (name.startsWith("perf-cpu")) { //$NON-NLS-1$
continue;
}
fSize += child.length();
fDats.add(new DatParser(child));
} else if (name.endsWith(".map")) { //$NON-NLS-1$
MapParser create = MapParser.create(child);
if (create != null) {
fMap.put(create.getSessionId(), create);
}
} else if (name.endsWith(".sym")) { //$NON-NLS-1$
fSyms.put(name.substring(0, name.length() - 4), SymParser.parse(child));
} else if (name.equals("task.txt")) { //$NON-NLS-1$
fTasks = new TaskParser(child);
} else if (name.equals("info")) { //$NON-NLS-1$
fInfo = InfoParser.parse(child);
} else if (name.equals("hostid")) { //$NON-NLS-1$
/*
* A 'hostid' file can be added which contains only one line
* with the host ID in it. This can be used to correlate
* with some other traces' data like an lttng kernel trace
*/
List<String> fileLines = Files.readAllLines(child.toPath());
if (!fileLines.isEmpty()) {
fHostId = fileLines.get(0);
}
}
} catch (IOException e) {
throw new TmfTraceException(e.getMessage(), e);
}
}
}
@Override
public ITmfLocation getCurrentLocation() {
return fCurrentLoc;
}
@Override
public double getLocationRatio(ITmfLocation location) {
return (long) location.getLocationInfo() / fSize;
}
@Override
public ITmfContext seekEvent(ITmfLocation location) {
UfContext context = new UfContext(fDats, this);
if (location == null) {
return context;
}
if (location instanceof TmfLongLocation) {
TmfLongLocation longLocation = (TmfLongLocation) location;
if (longLocation.getLocationInfo().longValue() == 0) {
return context;
}
while (context.getLocation() != null && Long.compare(longLocation.getLocationInfo(), Objects.requireNonNull(context.getLocation()).getLocationInfo()) >= 0) {
ITmfEvent next = context.getNext();
if (next == null) {
break;
}
updateAttributes(context, next);
if (context.getLocation() == null) {
return context;
}
}
}
return context;
}
@Override
public ITmfContext seekEvent(double ratio) {
return seekEvent(new TmfLongLocation((long) (ratio * fSize)));
}
@Override
public ITmfEvent parseEvent(@Nullable ITmfContext ctx) {
ITmfContext context = ctx;
if (context == null) {
context = seekEvent(0);
}
if (ctx instanceof UfContext) {
UfContext ufContext = (UfContext) ctx;
ITmfEvent tmfEvent = ufContext.getNext();
if (tmfEvent != null) {
fCurrentLoc = new TmfLongLocation(fCurrentLoc.getLocationInfo() + Long.BYTES * 2);
ctx.setLocation(fCurrentLoc);
updateAttributes(context, tmfEvent);
return tmfEvent;
}
}
return null;
}
@Override
public Set<@NonNull ? extends ITmfEventType> getContainedEventTypes() {
return UfEventType.TYPES;
}
@Override
public int size() {
return (int) (fSize / 1024);
}
@Override
public int progress() {
return (int) (fCurrentLoc.getLocationInfo() / 1024);
}
@Override
public @NonNull String getHostId() {
if (fHostId != null) {
return fHostId;
}
return super.getHostId();
}
/**
* Get the symbol provider for this trace
*
* @return the symbol provider
*/
public ISymbolProvider getSymbolProvider() {
return fSymbolProvider;
}
/**
* TID aspect for UFTrace
*
* @author Matthew Khouzam
*/
public final class TidAspect extends LinuxTidAspect {
@Override
public @Nullable Integer resolve(ITmfEvent event) {
if (event.getContent().getValue() instanceof DatEvent) {
DatEvent datEvent = (DatEvent) event.getContent().getValue();
return datEvent.getTid();
}
return null;
}
}
/**
* PID aspect for UFTrace
*
* @author Matthew Khouzam
*/
public final class PidAspect extends LinuxPidAspect {
@Override
public @Nullable Integer resolve(ITmfEvent event) {
if (event.getContent().getValue() instanceof DatEvent) {
DatEvent datEvent = (DatEvent) event.getContent().getValue();
int tid = datEvent.getTid();
return fTasks.getPid(tid);
}
return null;
}
}
/**
* Executable name aspect for UFTrace
*
* @author Matthew Khouzam
*/
public final class ExecAspect implements ITmfEventAspect<String> {
@Override
public @NonNull String getName() {
return "Executable"; //$NON-NLS-1$
}
@Override
public @NonNull String getHelpText() {
return ""; //$NON-NLS-1$
}
@Override
public @Nullable String resolve(@NonNull ITmfEvent event) {
if (event.getContent().getValue() instanceof DatEvent) {
DatEvent datEvent = (DatEvent) event.getContent().getValue();
int tid = datEvent.getTid();
return fTasks.getExecName(tid);
}
return null;
}
}
/**
* Alleged symbol provider factory
*
* @author Matthew Khouzam
*
*/
public static class UfTraceSymbolProviderAllegedFactory implements ISymbolProviderFactory {
@Override
public @Nullable ISymbolProvider createProvider(@NonNull ITmfTrace trace) {
if (trace instanceof Uftrace) {
return ((Uftrace) trace).getSymbolProvider();
}
return null;
}
}
@Override
public @NonNull Map<@NonNull String, @NonNull String> getProperties() {
if(fInfo == null) {
return Collections.emptyMap();
}
Map<@NonNull String, @NonNull String> properties = new LinkedHashMap<>();
properties.put("version", String.valueOf(fInfo.getVersion())); //$NON-NLS-1$
properties.put("address length", String.valueOf(fInfo.getAddressSize())); //$NON-NLS-1$
properties.put("endianness", String.valueOf(fInfo.getByteOrder())); //$NON-NLS-1$
properties.put("maximum depth", String.valueOf(fInfo.getMaxDepth())); //$NON-NLS-1$
properties.put("feature bit mask", Long.toBinaryString(fInfo.getFeatures())); //$NON-NLS-1$
String hostId = fHostId;
if (hostId != null) {
properties.put("host ID", hostId); //$NON-NLS-1$
}
properties.putAll(fInfo.getData());
return properties;
}
/**
* get the tasks parser
*
* @return The tasks parser
*/
public TaskParser getTasks() {
return fTasks;
}
/**
* Get the aspect to retrieve the executable name from a uftrace event
*
* @return The executable name aspect
*/
public @NonNull ExecAspect getExecAspect() {
return fExecAspect;
}
/**
* Get the aspect to retrieve the PID of a uftrace event
*
* @return the pid aspect
*/
public @NonNull PidAspect getPidAspect() {
return fPidAspect;
}
/**
* Get the aspect to retrieve the TID of a uftrace event
*
* @return the TID aspect
*/
public @NonNull TidAspect getTidAspect() {
return fTidAspect;
}
/**
* overly complicated, should clean up
*
* @author Matthew Khouzam
*
*/
private class UfTraceSymbolProvider implements ISymbolProvider {
@Override
public TmfResolvedSymbol getSymbol(int tid, long timestamp, long address) {
String execName = fTasks.getExecName(tid);
if (execName == null) {
return new TmfResolvedSymbol(address, "0x" + Long.toHexString(address)); //$NON-NLS-1$
}
Long session = fTasks.getSessName(tid);
if (session == null) {
return new TmfResolvedSymbol(address, "0x" + Long.toHexString(address)); //$NON-NLS-1$
}
MapParser mapParser = fMap.get(session);
if (mapParser == null) {
return new TmfResolvedSymbol(address, "0x" + Long.toHexString(address)); //$NON-NLS-1$
}
Entry<Long, MapEntry> key = mapParser.getData().floorEntry(address);
if (key == null) {
return new TmfResolvedSymbol(address, "0x" + Long.toHexString(address)); //$NON-NLS-1$
}
long offset = address - key.getValue().getAddrLow();
String pathName = key.getValue().getPathName();
String substring = pathName.substring(pathName.lastIndexOf(File.separator) + 1);
SymParser sym = fSyms.get(substring);
if (sym == null) {
return new TmfResolvedSymbol(address, pathName + ":0x" + Long.toHexString(address)); //$NON-NLS-1$
}
Entry<Long, Symbol> floorEntry = sym.getMap().floorEntry(offset);
if (floorEntry != null) {
Symbol value = floorEntry.getValue();
if (value != null) {
String name = String.valueOf(value.getName());
return new TmfResolvedSymbol(address, name);
}
}
return new TmfResolvedSymbol(address, "0x" + Long.toHexString(address)); //$NON-NLS-1$
}
/* needed for ISymbolProvider */
@Override
public @NonNull ITmfTrace getTrace() {
return (ITmfTrace) this;
}
@Override
public void loadConfiguration(@Nullable IProgressMonitor monitor) {
// do nothing
}
@Override
public @Nullable TmfResolvedSymbol getSymbol(long address) {
return getSymbol(Iterables.getFirst(fTasks.getTids(), -1), 0, address);
}
}
}