blob: f0d61ac911ca88d0dadf67d844eb0ed5f42d150d [file] [log] [blame]
/**********************************************************************
* Copyright (c) 2014, 2015 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:
* Jonathan Rajotte - Initial support for machine interface lttng 2.6
* Bernd Hufmann - Fix check for live session
**********************************************************************/
package org.eclipse.tracecompass.internal.lttng2.control.ui.views.service;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigInteger;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.validation.SchemaFactory;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tracecompass.common.core.xml.XmlUtils;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IBaseEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IChannelInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IDomainInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IFieldInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.ILoggerInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IProbeEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.ISessionInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.ISnapshotInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IUstProviderInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.LogLevelType;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceDomainType;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceEnablement;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceEventType;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceJulLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceLog4jLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TracePythonLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.BaseEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.BufferType;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.ChannelInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.DomainInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.EventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.FieldInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.LoggerInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.ProbeEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.SessionInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.SnapshotInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.UstProviderInfo;
import org.eclipse.tracecompass.internal.lttng2.control.ui.Activator;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.handlers.XmlMiValidationErrorHandler;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.messages.Messages;
import org.eclipse.tracecompass.tmf.remote.core.shell.ICommandInput;
import org.eclipse.tracecompass.tmf.remote.core.shell.ICommandResult;
import org.eclipse.tracecompass.tmf.remote.core.shell.ICommandShell;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Service for sending LTTng trace control commands to remote host via machine
* interface mode.
*
* @author Jonathan Rajotte
*/
public class LTTngControlServiceMI extends LTTngControlService {
/**
* The tracing key (.options) and System property to control whether or not schema validation should be used.
*/
public static final String MI_SCHEMA_VALIDATION_KEY = Activator.PLUGIN_ID + "/mi/schema-validation"; //$NON-NLS-1$
// ------------------------------------------------------------------------
// Attributes
// ------------------------------------------------------------------------
private final DocumentBuilder fDocumentBuilder;
// ------------------------------------------------------------------------
// Constructors
// ------------------------------------------------------------------------
/**
* Constructor
*
* @param shell
* the command shell implementation to use
* @param version
* the lttng version
* @throws ExecutionException
* if the creation of the Schema and DocumentBuilder objects
* fails
*/
public LTTngControlServiceMI(@NonNull ICommandShell shell, @Nullable LttngVersion version) throws ExecutionException {
super(shell);
setVersion(version);
DocumentBuilderFactory docBuilderFactory = XmlUtils.newSafeDocumentBuilderFactory();
docBuilderFactory.setExpandEntityReferences(false);
docBuilderFactory.setValidating(false);
// TODO: remove check for 2.11 when new schema is available
if (isSchemaValidationEnabled() && (version != null && (version.compareTo(new LttngVersion(2, 11, 0, null, null, null, null, null, null)) < 0))) {
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try {
URL xsdUrl = LTTngControlService.class.getResource(LTTngControlServiceConstants.MI_XSD_FILENAME);
if (version.compareTo(new LttngVersion(2, 8, 0, null, null, null, null, null, null)) >= 0) {
xsdUrl = LTTngControlService.class.getResource(LTTngControlServiceConstants.MI3_XSD_FILENAME);
// MI 3.0 added name spaces. It will fail to validate if this is not set to true.
docBuilderFactory.setNamespaceAware(true);
}
docBuilderFactory.setSchema(schemaFactory.newSchema(xsdUrl));
} catch (SAXException e) {
throw new ExecutionException(Messages.TraceControl_InvalidSchemaError, e);
}
}
try {
fDocumentBuilder = docBuilderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new ExecutionException(Messages.TraceControl_XmlDocumentBuilderError, e);
}
fDocumentBuilder.setErrorHandler(new XmlMiValidationErrorHandler());
}
private static boolean isSchemaValidationEnabled() {
String schemaValidationKey = Platform.getDebugOption(MI_SCHEMA_VALIDATION_KEY);
String systemProperty = System.getProperty(MI_SCHEMA_VALIDATION_KEY);
return schemaValidationKey != null && Boolean.parseBoolean(schemaValidationKey) || systemProperty != null && Boolean.parseBoolean(systemProperty);
}
/**
* Generate a Document object from an list of Strings.
*
* @param xmlStrings
* list of strings representing an xml input
* @param documentBuilder
* the builder used to get the document
* @return Document generated from strings input
* @throws ExecutionException
* when parsing has failed
*/
private static Document getDocumentFromStrings(List<String> xmlStrings, DocumentBuilder documentBuilder) throws ExecutionException {
StringBuilder concatenedString = new StringBuilder();
for (String string : xmlStrings) {
concatenedString.append(string);
}
InputSource stream = new InputSource(new StringReader(concatenedString.toString()));
Document document;
try {
document = documentBuilder.parse(stream);
} catch (SAXException | IOException e) {
throw new ExecutionException(Messages.TraceControl_XmlParsingError + ':' + e.toString(), e);
}
return document;
}
/**
* Parse LTTng version from a MI command result
*
* @param commandResult
* the result obtained from a MI command
* @return the LTTng version
* @throws ExecutionException
* when xml extraction fail
*/
public static LttngVersion parseVersion(ICommandResult commandResult) throws ExecutionException {
DocumentBuilderFactory docBuilderFactory = XmlUtils.newSafeDocumentBuilderFactory();
DocumentBuilder documentBuilder;
try {
documentBuilder = docBuilderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new ExecutionException(Messages.TraceControl_XmlDocumentBuilderError, e);
}
Document doc = getDocumentFromStrings(commandResult.getOutput(), documentBuilder);
NodeList element = doc.getElementsByTagName(MIStrings.VERSION);
if (element.getLength() != 1) {
throw new ExecutionException(Messages.TraceControl_UnsupportedVersionError);
}
int major = 0;
int minor = 0;
int patchLevel = 0;
String license = ""; //$NON-NLS-1$
String commit = ""; //$NON-NLS-1$
String name = ""; //$NON-NLS-1$
String description = ""; //$NON-NLS-1$
String url = ""; //$NON-NLS-1$
String fullVersion = ""; //$NON-NLS-1$
NodeList child = element.item(0).getChildNodes();
// Get basic information
for (int i = 0; i < child.getLength(); i++) {
Node node = child.item(i);
switch (node.getNodeName()) {
case MIStrings.VERSION_MAJOR:
major = Integer.parseInt(node.getTextContent());
break;
case MIStrings.VERSION_MINOR:
minor = Integer.parseInt(node.getTextContent());
break;
case MIStrings.VERSION_PATCH_LEVEL:
patchLevel = Integer.parseInt(node.getTextContent());
break;
case MIStrings.VERSION_COMMIT:
commit = node.getTextContent();
break;
case MIStrings.VERSION_DESCRIPTION:
description = node.getTextContent();
break;
case MIStrings.VERSION_LICENSE:
license = node.getTextContent();
break;
case MIStrings.VERSION_NAME:
name = node.getTextContent();
break;
case MIStrings.VERSION_STR:
fullVersion = node.getTextContent();
break;
case MIStrings.VERSION_WEB:
url = node.getTextContent();
break;
default:
break;
}
}
return new LttngVersion(major, minor, patchLevel, license, commit, name, description, url, fullVersion);
}
@Override
public List<String> getSessionNames(IProgressMonitor monitor) throws ExecutionException {
ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_LIST);
ICommandResult result = executeCommand(command, monitor);
Document doc = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
NodeList elements = doc.getElementsByTagName(MIStrings.NAME);
ArrayList<String> retArray = new ArrayList<>();
for (int i = 0; i < elements.getLength(); i++) {
Node node = elements.item(i);
if (node.getParentNode().getNodeName().equalsIgnoreCase(MIStrings.SESSION)) {
retArray.add(node.getTextContent());
}
}
return retArray;
}
@Override
public ISessionInfo getSession(String sessionName, IProgressMonitor monitor) throws ExecutionException {
ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_LIST, sessionName);
ICommandResult result = executeCommand(command, monitor);
ISessionInfo sessionInfo = new SessionInfo(sessionName);
Document document = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
NodeList sessionsNode = document.getElementsByTagName(MIStrings.SESSION);
// There should be only one session
if (sessionsNode.getLength() != 1) {
throw new ExecutionException(NLS.bind(Messages.TraceControl_MiInvalidNumberOfElementError, MIStrings.SESSION));
}
// Populate session information
Node rawSession = sessionsNode.item(0);
parseSession(sessionInfo, rawSession);
// Fetch the snapshot info
if (sessionInfo.isSnapshotSession()) {
ISnapshotInfo snapshot = getSnapshotInfo(sessionName, monitor);
sessionInfo.setSnapshotInfo(snapshot);
}
return sessionInfo;
}
/**
* @param sessionInfo
* @param rawSession
* @throws ExecutionException
*/
private void parseSession(ISessionInfo sessionInfo, Node rawSession) throws ExecutionException {
if (!rawSession.getNodeName().equalsIgnoreCase(MIStrings.SESSION)) {
throw new ExecutionException(Messages.TraceControl_MiInvalidElementError);
}
NodeList rawSessionInfos = rawSession.getChildNodes();
for (int i = 0; i < rawSessionInfos.getLength(); i++) {
Node rawInfo = rawSessionInfos.item(i);
switch (rawInfo.getNodeName()) {
case MIStrings.NAME:
sessionInfo.setName(rawInfo.getTextContent());
break;
case MIStrings.PATH:
sessionInfo.setSessionPath(rawInfo.getTextContent());
break;
case MIStrings.ENABLED:
sessionInfo.setSessionState(rawInfo.getTextContent());
break;
case MIStrings.SNAPSHOT_MODE:
if (rawInfo.getTextContent().equals(LTTngControlServiceConstants.TRUE_NUMERICAL)) {
// real name will be set later
ISnapshotInfo snapshotInfo = new SnapshotInfo(""); //$NON-NLS-1$
sessionInfo.setSnapshotInfo(snapshotInfo);
}
break;
case MIStrings.LIVE_TIMER_INTERVAL:
long liveDelay = Long.parseLong(rawInfo.getTextContent());
if ((liveDelay > 0 && (liveDelay <= LTTngControlServiceConstants.MAX_LIVE_TIMER_INTERVAL))) {
sessionInfo.setLive(true);
sessionInfo.setLiveUrl(SessionInfo.DEFAULT_LIVE_NETWORK_URL);
sessionInfo.setLivePort(SessionInfo.DEFAULT_LIVE_PORT);
sessionInfo.setLiveDelay(liveDelay);
}
break;
case MIStrings.DOMAINS:
// Extract the domains node
NodeList rawDomains = rawInfo.getChildNodes();
IDomainInfo domain = null;
for (int j = 0; j < rawDomains.getLength(); j++) {
if (rawDomains.item(j).getNodeName().equalsIgnoreCase(MIStrings.DOMAIN)) {
domain = parseDomain(rawDomains.item(j));
sessionInfo.addDomain(domain);
}
}
break;
default:
break;
}
}
if (!sessionInfo.isSnapshotSession()) {
Matcher matcher = LTTngControlServiceConstants.TRACE_NETWORK_PATTERN.matcher(sessionInfo.getSessionPath());
if (matcher.matches()) {
sessionInfo.setStreamedTrace(true);
}
}
}
/**
* Parse a raw domain XML node to a IDomainInfo object
*
* @param rawDomain
* a domain xml node
* @return a populated {@link DomainInfo} object
* @throws ExecutionException
* when missing required xml element (type)
*/
protected IDomainInfo parseDomain(Node rawDomain) throws ExecutionException {
IDomainInfo domain = null;
// Get the type
Node rawType = getFirstOf(rawDomain.getChildNodes(), MIStrings.TYPE);
if (rawType == null) {
throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
}
String rawTypeString = rawType.getTextContent().toLowerCase();
TraceDomainType domainType = TraceDomainType.valueOfString(rawTypeString);
switch (domainType) {
case KERNEL:
domain = new DomainInfo(Messages.TraceControl_KernelProviderDisplayName);
domain.setDomain(TraceDomainType.KERNEL);
break;
case UST:
domain = new DomainInfo(Messages.TraceControl_UstGlobalDomainDisplayName);
domain.setDomain(TraceDomainType.UST);
break;
case JUL:
domain = new DomainInfo(Messages.TraceControl_JULDomainDisplayName);
domain.setDomain(TraceDomainType.JUL);
break;
case LOG4J:
domain = new DomainInfo(Messages.TraceControl_LOG4JDomainDisplayName);
domain.setDomain(TraceDomainType.LOG4J);
break;
case PYTHON:
domain = new DomainInfo(Messages.TraceControl_PythonDomainDisplayName);
domain.setDomain(TraceDomainType.PYTHON);
break;
case UNKNOWN:
domain = new DomainInfo(Messages.TraceControl_UnknownDomainDisplayName);
domain.setDomain(TraceDomainType.UNKNOWN);
break;
//$CASES-OMITTED$
default:
throw new ExecutionException(Messages.TraceControl_MiInvalidElementError);
}
NodeList rawInfos = rawDomain.getChildNodes();
for (int i = 0; i < rawInfos.getLength(); i++) {
Node rawInfo = rawInfos.item(i);
switch (rawInfo.getNodeName()) {
case MIStrings.BUFFER_TYPE:
BufferType bufferType = BufferType.valueOfString(rawInfo.getTextContent());
domain.setBufferType(bufferType);
break;
case MIStrings.CHANNELS:
ArrayList<IChannelInfo> channels = new ArrayList<>();
parseChannels(rawInfo.getChildNodes(), channels);
if (!channels.isEmpty()) {
domain.setChannels(channels);
}
break;
case MIStrings.EVENTS:
ArrayList<ILoggerInfo> loggers = new ArrayList<>();
getLoggerInfo(rawInfo.getChildNodes(), loggers, domain.getDomain());
domain.setLoggers(loggers);
break;
default:
break;
}
}
return domain;
}
/**
* Parse a list of raw channel XML node into an ArrayList of IChannelInfo
*
* @param rawChannes
* List of raw channel XML node
* @param channels
* the parsed channels list
* @throws ExecutionException
* when missing required xml element (type)
*/
private static void parseChannels(NodeList rawChannels, ArrayList<IChannelInfo> channels) throws ExecutionException {
IChannelInfo channel = null;
for (int i = 0; i < rawChannels.getLength(); i++) {
Node rawChannel = rawChannels.item(i);
if (rawChannel.getNodeName().equalsIgnoreCase(MIStrings.CHANNEL)) {
channel = new ChannelInfo(""); //$NON-NLS-1$
// Populate the channel
NodeList rawInfos = rawChannel.getChildNodes();
Node rawInfo = null;
for (int j = 0; j < rawInfos.getLength(); j++) {
rawInfo = rawInfos.item(j);
switch (rawInfo.getNodeName()) {
case MIStrings.NAME:
channel.setName(rawInfo.getTextContent());
break;
case MIStrings.ENABLED:
channel.setState(TraceEnablement.valueOfString(rawInfo.getTextContent()));
break;
case MIStrings.EVENTS:
List<IEventInfo> events = new ArrayList<>();
getEventInfo(rawInfo.getChildNodes(), events);
channel.setEvents(events);
break;
case MIStrings.ATTRIBUTES:
NodeList rawAttributes = rawInfo.getChildNodes();
for (int k = 0; k < rawAttributes.getLength(); k++) {
Node attribute = rawAttributes.item(k);
switch (attribute.getNodeName()) {
case MIStrings.OVERWRITE_MODE:
channel.setOverwriteMode(!LTTngControlServiceConstants.OVERWRITE_MODE_ATTRIBUTE_FALSE_MI.equalsIgnoreCase(attribute.getTextContent()));
break;
case MIStrings.SUBBUF_SIZE:
channel.setSubBufferSize(Long.valueOf(attribute.getTextContent()));
break;
case MIStrings.NUM_SUBBUF:
channel.setNumberOfSubBuffers(Integer.valueOf(attribute.getTextContent()));
break;
case MIStrings.SWITCH_TIMER_INTERVAL:
channel.setSwitchTimer(Long.valueOf(attribute.getTextContent()));
break;
case MIStrings.READ_TIMER_INTERVAL:
channel.setReadTimer(Long.valueOf(attribute.getTextContent()));
break;
case MIStrings.OUTPUT_TYPE:
channel.setOutputType(attribute.getTextContent());
break;
case MIStrings.TRACEFILE_SIZE:
channel.setMaxSizeTraceFiles(Long.parseLong(attribute.getTextContent()));
break;
case MIStrings.TRACEFILE_COUNT:
channel.setMaxNumberTraceFiles(Integer.parseInt(attribute.getTextContent()));
break;
case MIStrings.LIVE_TIMER_INTERVAL:
// TODO: currently not supported by tmf
break;
case MIStrings.DISCARDED_EVENTS:
channel.setNumberOfDiscardedEvents(Long.parseLong(attribute.getTextContent()));
break;
case MIStrings.LOST_PACKETS:
channel.setNumberOfLostPackets(Long.parseLong(attribute.getTextContent()));
break;
default:
break;
}
}
break;
default:
break;
}
}
channels.add(channel);
}
}
}
@Override
public ISnapshotInfo getSnapshotInfo(String sessionName, IProgressMonitor monitor) throws ExecutionException {
// TODO A session can have multiple snapshot output. This need to be
// supported in the future.
// Currently the SessionInfo object does not support multiple snashot
// output.
// For now only keep the last one.
ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_SNAPSHOT, LTTngControlServiceConstants.COMMAND_LIST_SNAPSHOT_OUTPUT, LTTngControlServiceConstants.OPTION_SESSION, sessionName);
ICommandResult result = executeCommand(command, monitor);
Document doc = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
NodeList rawSnapshotsOutputs = doc.getElementsByTagName(MIStrings.SNAPSHOT_OUTPUTS);
ISnapshotInfo snapshotInfo = new SnapshotInfo(""); //$NON-NLS-1$
// TODO: tmf does not have a notion of a ctrl url.
for (int i = 0; i < rawSnapshotsOutputs.getLength(); i++) {
NodeList rawSnapshotOutput = rawSnapshotsOutputs.item(i).getChildNodes();
for (int j = 0; j < rawSnapshotOutput.getLength(); j++) {
Node rawInfo = rawSnapshotOutput.item(j);
switch (rawInfo.getNodeName()) {
case MIStrings.ID:
snapshotInfo.setId(Integer.parseInt(rawInfo.getTextContent()));
break;
case MIStrings.NAME:
snapshotInfo.setName(rawInfo.getTextContent());
break;
case MIStrings.SNAPSHOT_CTRL_URL:
// The use of the ctrl_url for the snapshot path is to assure
// basic support. Refactoring is necessary in lttng and
// tmf side.
// See http://bugs.lttng.org/issues/828 (+comment)
snapshotInfo.setSnapshotPath(rawInfo.getTextContent());
break;
default:
break;
}
}
}
// Check if the snapshot output is Streamed
Matcher matcher2 = LTTngControlServiceConstants.TRACE_NETWORK_PATTERN.matcher(snapshotInfo.getSnapshotPath());
if (matcher2.matches()) {
snapshotInfo.setStreamedSnapshot(true);
}
return snapshotInfo;
}
@Override
public List<IBaseEventInfo> getKernelProvider(IProgressMonitor monitor) throws ExecutionException {
// Tracepoint events
ICommandInput tracepointCommand = createCommand(LTTngControlServiceConstants.COMMAND_LIST, LTTngControlServiceConstants.OPTION_KERNEL);
ICommandResult tracepointResult = executeCommand(tracepointCommand, monitor, false);
List<IBaseEventInfo> events = new ArrayList<>();
if (isError(tracepointResult)) {
// Ignore the following 2 cases:
// Spawning a session daemon
// Error: Unable to list kernel events
// or:
// Error: Unable to list kernel events
if (ignoredPattern(tracepointResult.getErrorOutput(), LTTngControlServiceConstants.LIST_KERNEL_NO_KERNEL_PROVIDER_PATTERN)) {
return events;
}
throw new ExecutionException(Messages.TraceControl_CommandError + tracepointCommand.toString());
}
Document tracepointDocument = getDocumentFromStrings(tracepointResult.getOutput(), fDocumentBuilder);
NodeList rawTracepointEvents = tracepointDocument.getElementsByTagName(MIStrings.EVENT);
getBaseEventInfo(rawTracepointEvents, events);
// Syscall events
ICommandInput syscallCommand = createCommand(LTTngControlServiceConstants.COMMAND_LIST, LTTngControlServiceConstants.OPTION_KERNEL, LTTngControlServiceConstants.OPTION_SYSCALL);
ICommandResult syscallResult = executeCommand(syscallCommand, monitor, false);
List<IBaseEventInfo> syscallEvents = new ArrayList<>();
if (isError(syscallResult)) {
throw new ExecutionException(Messages.TraceControl_CommandError + syscallCommand.toString());
}
Document syscallDocument = getDocumentFromStrings(syscallResult.getOutput(), fDocumentBuilder);
NodeList rawSyscallEvents = syscallDocument.getElementsByTagName(MIStrings.EVENT);
getBaseEventInfo(rawSyscallEvents, syscallEvents);
// Merge the tracepoint events with the syscall events (all under the Kernel provider)
events.addAll(syscallEvents);
return events;
}
@Override
public List<IUstProviderInfo> getUstProvider(IProgressMonitor monitor) throws ExecutionException {
ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_LIST, LTTngControlServiceConstants.OPTION_UST);
// Get the field too
command.add(LTTngControlServiceConstants.OPTION_FIELDS);
// Execute UST listing
ICommandResult result = executeCommand(command, monitor, false);
List<IUstProviderInfo> allProviders = new ArrayList<>();
if (isError(result)) {
// Ignore the following 2 cases:
// Spawning a session daemon
// Error: Unable to list UST events: Listing UST events failed
// or:
// Error: Unable to list UST events: Listing UST events failed
if (ignoredPattern(result.getErrorOutput(), LTTngControlServiceConstants.LIST_UST_NO_UST_PROVIDER_PATTERN)) {
return allProviders;
}
throw new ExecutionException(Messages.TraceControl_CommandError + command.toString());
}
Document document = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
NodeList rawProviders = document.getElementsByTagName(MIStrings.PID);
IUstProviderInfo providerInfo = null;
for (int i = 0; i < rawProviders.getLength(); i++) {
Node provider = rawProviders.item(i);
Node name = getFirstOf(provider.getChildNodes(), MIStrings.NAME);
if (name == null) {
throw new ExecutionException(Messages.TraceControl_MiInvalidProviderError);
}
providerInfo = new UstProviderInfo(name.getTextContent());
// Populate provider
NodeList infos = provider.getChildNodes();
for (int j = 0; j < infos.getLength(); j++) {
Node info = infos.item(j);
switch (info.getNodeName()) {
case MIStrings.PID_ID:
providerInfo.setPid(Integer.parseInt(info.getTextContent()));
break;
case MIStrings.EVENTS:
List<IBaseEventInfo> events = new ArrayList<>();
NodeList rawEvents = info.getChildNodes();
getBaseEventInfo(rawEvents, events);
providerInfo.setEvents(events);
break;
default:
break;
}
}
allProviders.add(providerInfo);
}
if (isVersionSupported("2.6")) { //$NON-NLS-1$
getUstProviderLoggers(allProviders, TraceDomainType.JUL, monitor);
getUstProviderLoggers(allProviders, TraceDomainType.LOG4J, monitor);
if (isVersionSupported("2.7")) { //$NON-NLS-1$
getUstProviderLoggers(allProviders, TraceDomainType.PYTHON, monitor);
}
}
return allProviders;
}
@Override
public ISessionInfo createSession(ISessionInfo sessionInfo, IProgressMonitor monitor) throws ExecutionException {
if (sessionInfo.isStreamedTrace()) {
return createStreamedSession(sessionInfo, monitor);
}
ICommandInput command = prepareSessionCreationCommand(sessionInfo);
ICommandResult result = executeCommand(command, monitor);
Document document = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
NodeList sessions = document.getElementsByTagName(MIStrings.SESSION);
// Number of session should be equal to 1
if (sessions.getLength() != 1) {
throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" //$NON-NLS-1$//$NON-NLS-2$
+ NLS.bind(Messages.TraceControl_UnexpectedNumberOfElementError, MIStrings.SESSION) + " " + sessions.getLength()); //$NON-NLS-1$
}
// Fetch a session from output
ISessionInfo outputSession = new SessionInfo(""); //$NON-NLS-1$
parseSession(outputSession, sessions.item(0));
// Verify session name
if ((outputSession.getName().equals("")) || (!"".equals(sessionInfo.getName()) && !outputSession.getName().equals(sessionInfo.getName()))) { //$NON-NLS-1$ //$NON-NLS-2$
// Unexpected name returned
throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
Messages.TraceControl_UnexpectedNameError + ": " + outputSession.getName()); //$NON-NLS-1$
}
// Verify session path
if (!sessionInfo.isSnapshotSession() &&
((outputSession.getSessionPath() == null) || ((sessionInfo.getSessionPath() != null) && (!outputSession.getSessionPath().contains(sessionInfo.getSessionPath()))))) {
// Unexpected path
throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
Messages.TraceControl_UnexpectedPathError + ": " + outputSession.getName()); //$NON-NLS-1$
}
if (sessionInfo.isSnapshotSession()) {
// Make it a snapshot session - content of snapshot info need to
// set afterwards using getSession() or getSnapshotInfo()
outputSession.setSnapshotInfo(new SnapshotInfo("")); //$NON-NLS-1$
}
return outputSession;
}
private @NonNull ISessionInfo createStreamedSession(ISessionInfo sessionInfo, IProgressMonitor monitor) throws ExecutionException {
ICommandInput command = prepareStreamedSessionCreationCommand(sessionInfo);
ICommandResult result = executeCommand(command, monitor);
Document document = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
NodeList sessions = document.getElementsByTagName(MIStrings.SESSION);
// Number of session should be equal to 1
if (sessions.getLength() != 1) {
throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" //$NON-NLS-1$//$NON-NLS-2$
+ NLS.bind(Messages.TraceControl_UnexpectedNumberOfElementError, MIStrings.SESSION) + " " + sessions.getLength()); //$NON-NLS-1$
}
// Fetch a session from output
ISessionInfo outputSession = new SessionInfo(""); //$NON-NLS-1$
parseSession(outputSession, sessions.item(0));
// Verify session name
if ((outputSession.getName().equals("")) || (!"".equals(sessionInfo.getName()) && !outputSession.getName().equals(sessionInfo.getName()))) { //$NON-NLS-1$ //$NON-NLS-2$
// Unexpected name returned
throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
Messages.TraceControl_UnexpectedNameError + ": " + outputSession.getName()); //$NON-NLS-1$
}
sessionInfo.setName(outputSession.getName());
sessionInfo.setStreamedTrace(true);
// Verify session path
if (sessionInfo.getNetworkUrl() != null) {
if (!sessionInfo.isSnapshotSession() && (outputSession.getSessionPath() == null)) {
// Unexpected path
throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
Messages.TraceControl_UnexpectedPathError + ": " + outputSession.getName()); //$NON-NLS-1$
}
if (sessionInfo.isSnapshotSession()) {
sessionInfo.setStreamedTrace(false);
} else {
sessionInfo.setSessionPath(outputSession.getSessionPath());
// Check file protocol
Matcher matcher = LTTngControlServiceConstants.TRACE_FILE_PROTOCOL_PATTERN.matcher(outputSession.getSessionPath());
if (matcher.matches()) {
sessionInfo.setStreamedTrace(false);
}
}
}
// When using controlUrl and dataUrl the full session path is not known
// yet
// and will be set later on when listing the session
return sessionInfo;
}
@Override
public void destroySession(String sessionName, IProgressMonitor monitor) throws ExecutionException {
ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_DESTROY_SESSION, sessionName);
ICommandResult result = executeCommand(command, monitor, false);
List<String> errorOutput = result.getErrorOutput();
if (isError(result)) {
// Don't treat this as an error
if (ignoredPattern(errorOutput, LTTngControlServiceConstants.SESSION_NOT_FOUND_ERROR_PATTERN)) {
return;
}
throw new ExecutionException(Messages.TraceControl_CommandError + " " + command.toString() + "\n" + result.toString()); //$NON-NLS-1$ //$NON-NLS-2$
}
// Check for action effect
Document doc = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
NodeList sessions = doc.getElementsByTagName(MIStrings.SESSION);
if (sessions.getLength() != 1) {
throw new ExecutionException(NLS.bind(Messages.TraceControl_MiInvalidNumberOfElementError, MIStrings.SESSION));
}
Node rawSessionName = getFirstOf(sessions.item(0).getChildNodes(), MIStrings.NAME);
if (rawSessionName == null) {
throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
}
// Validity check
if (!rawSessionName.getTextContent().equals(sessionName)) {
throw new ExecutionException(NLS.bind(Messages.TraceControl_UnexpectedValueError, rawSessionName.getTextContent(), sessionName));
}
}
@Override
protected ICommandInput createCommand(String... strings) {
ICommandInput command = getCommandShell().createCommand();
command.add(LTTngControlServiceConstants.CONTROL_COMMAND);
List<@NonNull String> groupOption = getTracingGroupOption();
if (!groupOption.isEmpty()) {
command.addAll(groupOption);
}
command.add(LTTngControlServiceConstants.CONTROL_COMMAND_MI_OPTION);
command.add(LTTngControlServiceConstants.CONTROL_COMMAND_MI_XML);
for (String string : strings) {
command.add(checkNotNull(string));
}
return command;
}
/**
* @param xmlBaseEvents
* a Node list of base xml event element
* @param events
* list of event generated by the parsing of the xml event
* element
* @throws ExecutionException
* when a raw event is not a complete/valid xml event
*/
private static void getBaseEventInfo(NodeList xmlBaseEvents, List<IBaseEventInfo> events) throws ExecutionException {
IBaseEventInfo eventInfo = null;
for (int i = 0; i < xmlBaseEvents.getLength(); i++) {
NodeList rawInfos = xmlBaseEvents.item(i).getChildNodes();
// Search for name
if (xmlBaseEvents.item(i).getNodeName().equalsIgnoreCase(MIStrings.EVENT)) {
Node rawName = getFirstOf(rawInfos, MIStrings.NAME);
if (rawName == null) {
throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
}
eventInfo = new BaseEventInfo(rawName.getTextContent());
// Populate the event
for (int j = 0; j < rawInfos.getLength(); j++) {
Node infoNode = rawInfos.item(j);
switch (infoNode.getNodeName()) {
case MIStrings.TYPE:
eventInfo.setEventType(infoNode.getTextContent());
break;
case MIStrings.LOGLEVEL:
eventInfo.setLogLevel(infoNode.getTextContent());
break;
case MIStrings.EVENT_FIELDS:
List<IFieldInfo> fields = new ArrayList<>();
getFieldInfo(infoNode.getChildNodes(), fields);
eventInfo.setFields(fields);
break;
default:
break;
}
}
events.add(eventInfo);
}
}
}
/**
* @param xmlBaseEvents
* a Node list of xml event element linked to a session
* @param events
* list of event generated by the parsing of the xml event
* element
* @throws ExecutionException
* when a raw event is not a complete/valid xml event
*/
static void getEventInfo(NodeList xmlEvents, List<IEventInfo> events) throws ExecutionException {
IEventInfo eventInfo = null;
for (int i = 0; i < xmlEvents.getLength(); i++) {
NodeList rawInfos = xmlEvents.item(i).getChildNodes();
// Search for name
if (xmlEvents.item(i).getNodeName().equalsIgnoreCase(MIStrings.EVENT)) {
Node rawName = getFirstOf(rawInfos, MIStrings.NAME);
if (rawName == null) {
throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
}
eventInfo = new EventInfo(rawName.getTextContent());
// Basic information
for (int j = 0; j < rawInfos.getLength(); j++) {
Node infoNode = rawInfos.item(j);
switch (infoNode.getNodeName()) {
case MIStrings.TYPE:
eventInfo.setEventType(infoNode.getTextContent());
break;
case MIStrings.LOGLEVEL_TYPE:
eventInfo.setLogLevelType(LogLevelType.valueOfString(infoNode.getTextContent()));
break;
case MIStrings.LOGLEVEL:
eventInfo.setLogLevel(TraceLogLevel.valueOfString(infoNode.getTextContent()));
break;
case MIStrings.ENABLED:
eventInfo.setState(TraceEnablement.valueOfString(infoNode.getTextContent()));
break;
case MIStrings.FILTER:
// Before LTTng 2.8: We emulate the non-mi behavior and simply put
// "with filter"
if (Boolean.TRUE.toString().equals(infoNode.getTextContent())) {
eventInfo.setFilterExpression(Messages.TraceControl_DefaultEventFilterString);
}
break;
case MIStrings.FILTER_EXPRESSION:
eventInfo.setFilterExpression(infoNode.getTextContent());
break;
case MIStrings.EXCLUSION:
// Before LTTng 2.8: We emulate the non-mi behavior and simply put
// "with exclude"
if (Boolean.TRUE.toString().equals(infoNode.getTextContent())) {
eventInfo.setExcludedEvents(Messages.TraceControl_DefaultEventExcludeString);
}
break;
case MIStrings.EXCLUSIONS:
StringBuilder tmpString = new StringBuilder();
// If there is multiple events excluded.
for (int k = 0; k < infoNode.getChildNodes().getLength(); k++) {
if (k > 0) {
tmpString.append(", "); //$NON-NLS-1$
}
tmpString.append(infoNode.getChildNodes().item(k).getTextContent());
}
eventInfo.setExcludedEvents(tmpString.toString());
break;
default:
break;
}
}
boolean isProbeFunction = (eventInfo.getEventType().equals(TraceEventType.PROBE)) || (eventInfo.getEventType().equals(TraceEventType.FUNCTION));
if (isProbeFunction) {
IProbeEventInfo probeEvent = new ProbeEventInfo(eventInfo);
eventInfo = probeEvent;
Node rawDataNode = null;
switch (probeEvent.getEventType()) {
case FUNCTION:
case PROBE: {
// get attributes
Node rawAttributes = getFirstOf(rawInfos, MIStrings.ATTRIBUTES);
if (rawAttributes == null) {
throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
}
rawDataNode = getFirstOf(rawAttributes.getChildNodes(), MIStrings.PROBE_ATTRIBUTES);
break;
}
case SYSCALL:
case TRACEPOINT:
case UNKNOWN:
default:
throw new ExecutionException(Messages.TraceControl_MiInvalidElementError);
}
if (rawDataNode == null) {
throw new ExecutionException(Messages.TraceControl_MiInvalidElementError);
}
// Extract info
NodeList rawDatas = rawDataNode.getChildNodes();
for (int j = 0; j < rawDatas.getLength(); j++) {
Node rawData = rawDatas.item(j);
switch (rawData.getNodeName()) {
case MIStrings.SYMBOL_NAME:
probeEvent.setSymbol(rawData.getTextContent());
break;
case MIStrings.ADDRESS:
probeEvent.setAddress(String.format("%#016x", new BigInteger(rawData.getTextContent()))); //$NON-NLS-1$
break;
case MIStrings.OFFSET:
probeEvent.setOffset(String.format("%#016x", new BigInteger(rawData.getTextContent()))); //$NON-NLS-1$
break;
default:
break;
}
}
}
// Add the event
events.add(eventInfo);
}
}
}
static void getLoggerInfo(NodeList xmlEvents, List<ILoggerInfo> loggers, TraceDomainType domain) throws ExecutionException {
ILoggerInfo loggerInfo = null;
for (int i = 0; i < xmlEvents.getLength(); i++) {
NodeList rawInfos = xmlEvents.item(i).getChildNodes();
// Search for name
if (xmlEvents.item(i).getNodeName().equalsIgnoreCase(MIStrings.EVENT)) {
Node rawName = getFirstOf(rawInfos, MIStrings.NAME);
if (rawName == null) {
throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
}
loggerInfo = new LoggerInfo(rawName.getTextContent());
loggerInfo.setDomain(domain);
// Basic information
for (int j = 0; j < rawInfos.getLength(); j++) {
Node infoNode = rawInfos.item(j);
switch (infoNode.getNodeName()) {
case MIStrings.LOGLEVEL_TYPE:
loggerInfo.setLogLevelType(LogLevelType.valueOfString(infoNode.getTextContent()));
break;
case MIStrings.LOGLEVEL:
switch (domain) {
case JUL:
loggerInfo.setLogLevel(TraceJulLogLevel.valueOfString(infoNode.getTextContent()));
break;
case LOG4J:
loggerInfo.setLogLevel(TraceLog4jLogLevel.valueOfString(infoNode.getTextContent()));
break;
case PYTHON:
loggerInfo.setLogLevel(TracePythonLogLevel.valueOfString(infoNode.getTextContent()));
break;
//$CASES-OMITTED$
default:
break;
}
break;
case MIStrings.ENABLED:
loggerInfo.setState(TraceEnablement.valueOfString(infoNode.getTextContent()));
break;
default:
break;
}
}
// Add the event
loggers.add(loggerInfo);
}
}
}
/**
* @param fieldsList
* a list of xml event_field element
* @param fields
* a list of field generated by xml parsing
* @throws ExecutionException
* when parsing fail or required elements are missing
*/
private static void getFieldInfo(NodeList fieldsList, List<IFieldInfo> fields) throws ExecutionException {
IFieldInfo fieldInfo = null;
for (int i = 0; i < fieldsList.getLength(); i++) {
Node field = fieldsList.item(i);
if (field.getNodeName().equalsIgnoreCase(MIStrings.EVENT_FIELD)) {
// Get name
Node name = getFirstOf(field.getChildNodes(), MIStrings.NAME);
if (name == null) {
throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
}
fieldInfo = new FieldInfo(name.getTextContent());
// Populate the field information
NodeList infos = field.getChildNodes();
for (int j = 0; j < infos.getLength(); j++) {
Node info = infos.item(j);
switch (info.getNodeName()) {
case MIStrings.TYPE:
fieldInfo.setFieldType(info.getTextContent());
break;
default:
break;
}
}
fields.add(fieldInfo);
}
}
}
/**
* Retrieve the fist instance of a given node with tag name equal to tagName
* parameter
*
* @param nodeList
* the list of Node to search against
* @param tagName
* the tag name of the desired node
* @return the first occurrence of a node with a tag name equals to tagName
*/
private static @Nullable Node getFirstOf(NodeList nodeList, String tagName) {
Node node = null;
for (int i = 0; i < nodeList.getLength(); i++) {
if (Objects.equals(nodeList.item(i).getNodeName(), tagName)) {
node = nodeList.item(i);
break;
}
}
return node;
}
/**
* Retrieve the loggers of a certain domain type for the UST provider.
*
* @param allProviders
* the list of UST providers
* @param domain
* the loggers domain
* @param monitor
* progress monitor
* @throws ExecutionException
*/
private void getUstProviderLoggers(List<IUstProviderInfo> allProviders, TraceDomainType domain, IProgressMonitor monitor) throws ExecutionException {
// Getting the loggers information since those are under the UST provider
ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_LIST);
switch (domain) {
case JUL:
command.add(LTTngControlServiceConstants.OPTION_JUL);
break;
case LOG4J:
command.add(LTTngControlServiceConstants.OPTION_LOG4J);
break;
case PYTHON:
command.add(LTTngControlServiceConstants.OPTION_PYTHON);
break;
//$CASES-OMITTED$
default:
break;
}
// Execute listing
ICommandResult result = executeCommand(command, monitor, false);
if (isError(result)) {
throw new ExecutionException(Messages.TraceControl_CommandError + command.toString());
}
Document document = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
NodeList rawProviders = document.getElementsByTagName(MIStrings.PID);
for (int i = 0; i < rawProviders.getLength(); i++) {
Node provider = rawProviders.item(i);
Node name = getFirstOf(provider.getChildNodes(), MIStrings.NAME);
if (name == null) {
throw new ExecutionException(Messages.TraceControl_MiInvalidProviderError);
}
Node id = getFirstOf(provider.getChildNodes(), MIStrings.PID_ID);
if (id != null) {
for (int k = 0; k < allProviders.size(); k++) {
if (allProviders.get(k).getPid() == Integer.parseInt(id.getTextContent())) {
Node events = getFirstOf(provider.getChildNodes(), MIStrings.EVENTS);
if (events != null) {
List<ILoggerInfo> loggers = new ArrayList<>();
NodeList rawEvents = events.getChildNodes();
getLoggerInfo(rawEvents, loggers, domain);
for (ILoggerInfo logger : loggers) {
logger.setDomain(domain);
}
allProviders.get(k).addLoggers(loggers);
}
}
}
}
}
}
@Override
public @NonNull List<String> getContextList(IProgressMonitor monitor) throws ExecutionException {
if (!isVersionSupported("2.8.0")) { //$NON-NLS-1$
return super.getContextList(monitor);
}
ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_ADD_CONTEXT, LTTngControlServiceConstants.OPTION_LIST);
ICommandResult result = executeCommand(command, monitor);
if (!isVersionSupported("2.11.0")) { //$NON-NLS-1$
return result.getOutput();
}
if (isError(result)) {
throw new ExecutionException(Messages.TraceControl_CommandError + command.toString());
}
Document document = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
NodeList rawContexts = document.getElementsByTagName(MIStrings.CONTEXT);
List<String> returnedContexts = new ArrayList<>();
for (int i = 0; i < rawContexts.getLength(); i++) {
Node contextNode = rawContexts.item(i);
Node symbol = getFirstOf(contextNode.getChildNodes(), MIStrings.SYMBOL);
if (symbol != null) {
returnedContexts.add(symbol.getTextContent());
}
}
return returnedContexts;
}
}