blob: defa9bb9616a79581929151aec7e5eacbd167245 [file] [log] [blame]
/**********************************************************************
* Copyright (c) 2014 École Polytechnique de Montréal
*
* 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:
* Guilliano Molaire - Initial API and implementation
*********************************************************************/
package org.eclipse.tracecompass.lttng2.control.core.session;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tracecompass.common.core.xml.XmlUtils;
import org.eclipse.tracecompass.internal.lttng2.control.core.Activator;
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.ISessionInfo;
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.TraceLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceSessionState;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* Class for generating a session configuration file. A session configuration is
* used to configure a trace session. It is a XML formatted file that contains
* values defining the behavior of that specific trace session.
* <p>
* Kernel session configuration example:
*
* <pre>
* {@code
* <sessions>
* <session>
* <name>test_kernel</name>
* <domains>
* <domain>
* <type>KERNEL</type>
* <buffer_type>GLOBAL</buffer_type>
* <channels>
* <channel>
* <name>channel0</name>
* <enabled>false</enabled>
* <overwrite_mode>DISCARD</overwrite_mode>
* <subbuffer_size>262144</subbuffer_size>
* <subbuffer_count>4</subbuffer_count>
* <switch_timer_interval>0</switch_timer_interval>
* <read_timer_interval>200000</read_timer_interval>
* <output_type>SPLICE</output_type>
* <tracefile_size>0</tracefile_size>
* <tracefile_count>0</tracefile_count>
* <live_timer_interval>0</live_timer_interval>
* <events>
* <event>
* <enabled>true</enabled>
* <type>SYSCALL</type>
* </event>
* <event>
* <name>snd_soc_cache_sync</name>
* <enabled>true</enabled>
* <type>TRACEPOINT</type>
* </event>
* </events>
* </channel>
* </channels>
* </domain>
* </domains>
* <started>false</started>
* <output>
* <consumer_output>
* <enabled>true</enabled>
* <destination>
* <path>/home/user/lttng-traces/test_kernel</path>
* </destination>
* </consumer_output>
* </output>
* </session>
* </sessions>
* }
* </pre>
*
* </p>
*
* @author Guilliano Molaire
*/
public final class SessionConfigGenerator {
/** The name of the session schema */
private static final String SESSION_XSD_FILENAME = "session.xsd"; //$NON-NLS-1$
/** The indent size used for the session configuration XML file */
private static final String INDENT_AMOUNT_PROPERTY_NAME = "{http://xml.apache.org/xslt}indent-amount"; //$NON-NLS-1$
private static final String INDENT_AMOUNT_PROPERTY_VALUE = "4"; //$NON-NLS-1$
/**
* Private constructor. The class should not be instantiated.
*/
private SessionConfigGenerator() {
}
// ---------------------------------------------------------
// Methods to generate session configuration files
// ---------------------------------------------------------
/**
* Generates a session configuration file from a set of session information.
*
* @param sessions
* The session informations
* @param sessionFileDestination
* The path of the locally saved session configuration file
* @return The status of the session configuration generation
*/
public static IStatus generateSessionConfig(Set<ISessionInfo> sessions, IPath sessionFileDestination) {
/* Parameters validation */
if (sessions == null || sessions.isEmpty()) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, Messages.SessionConfigXML_InvalidSessionInfoList);
} else if (sessionFileDestination == null) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, Messages.SessionConfigXML_InvalidTraceSessionPath);
}
/* Generate the session configuration file */
try {
Document sessionConfigDocument = generateSessionConfig(sessions);
saveSessionConfig(sessionConfigDocument, sessionFileDestination.toString());
} catch (TransformerException | IllegalArgumentException | ParserConfigurationException e) {
Activator.getDefault().logError("Error generating the session configuration file: " + sessionFileDestination.toString(), e); //$NON-NLS-1$
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage());
}
return Status.OK_STATUS;
}
/**
* Generates a session configuration from a set of session informations.
*
* @param sessions
* The session informations
* @return The document with all session configuration nodes
* @throws IllegalArgumentException
* On an illegal argument inside sessions
* @throws ParserConfigurationException
* On an parser configuration error
*/
private static @NonNull Document generateSessionConfig(Iterable<ISessionInfo> sessions) throws IllegalArgumentException, ParserConfigurationException {
DocumentBuilder docBuilder = XmlUtils.newSafeDocumentBuilderFactory().newDocumentBuilder();
Document document = docBuilder.newDocument();
Element rootElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_SESSIONS);
document.appendChild(rootElement);
for (ISessionInfo session : sessions) {
/* All elements under "sessions" elements */
Element sessionElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_SESSION);
/* Contents of session element */
String enabled = session.getSessionState().equals(TraceSessionState.ACTIVE) ? SessionConfigStrings.CONFIG_STRING_TRUE : SessionConfigStrings.CONFIG_STRING_FALSE;
addElementContent(document, sessionElement, SessionConfigStrings.CONFIG_ELEMENT_NAME, session.getName());
addElementContent(document, sessionElement, SessionConfigStrings.CONFIG_ELEMENT_STARTED, enabled);
if (session.isSnapshotSession()) {
/* If it's a snapshot, we must add an attribute telling it is */
Element attributesElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_ATTRIBUTES);
addElementContent(document, attributesElement, SessionConfigStrings.CONFIG_ELEMENT_SNAPSHOT_MODE, SessionConfigStrings.CONFIG_STRING_TRUE);
sessionElement.appendChild(attributesElement);
}
sessionElement.appendChild(getDomainsElement(document, session));
sessionElement.appendChild(getOutputElement(document, session));
rootElement.appendChild(sessionElement);
}
return document;
}
// ---------------------------------------------------------
// Getters for each element of the configuration file
// ---------------------------------------------------------
/**
* Gets the 'domains' element after creating it.
*
* @param document
* The document in which the nodes are being added
* @param session
* The session informations
* @return The domains element as an XML element
*/
private static Element getDomainsElement(Document document, ISessionInfo session) {
Element domainsElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_DOMAINS);
for (IDomainInfo domain : session.getDomains()) {
Element domainElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_DOMAIN);
String domainType = null;
switch (domain.getDomain()) {
case KERNEL:
domainType = SessionConfigStrings.CONFIG_DOMAIN_TYPE_KERNEL;
break;
case UST:
domainType = SessionConfigStrings.CONFIG_DOMAIN_TYPE_UST;
break;
// TODO: Add the following cases when more domains are supported
case JUL:
case LOG4J:
case PYTHON:
case UNKNOWN:
default:
break;
}
addElementContent(document, domainElement, SessionConfigStrings.CONFIG_ELEMENT_TYPE, domainType);
String bufferType = null;
switch (domain.getBufferType()) {
case BUFFER_PER_UID:
bufferType = SessionConfigStrings.CONFIG_BUFFER_TYPE_PER_UID;
break;
case BUFFER_PER_PID:
bufferType = SessionConfigStrings.CONFIG_BUFFER_TYPE_PER_PID;
break;
case BUFFER_SHARED:
bufferType = SessionConfigStrings.CONFIG_BUFFER_TYPE_GLOBAL;
break;
case BUFFER_TYPE_UNKNOWN:
default:
throw new IllegalArgumentException(Messages.SessionConfigXML_UnknownDomainBufferType);
}
addElementContent(document, domainElement, SessionConfigStrings.CONFIG_ELEMENT_DOMAIN_BUFFER_TYPE, bufferType);
/* Add the channels */
domainElement.appendChild(getChannelsElement(document, domain.getDomain(), domain.getChannels()));
domainsElement.appendChild(domainElement);
}
return domainsElement;
}
/**
* Gets the 'output' element after creating it. If the session is a
* snapshot, it will be composed of a snapshot outputs element. Otherwise,
* it will contain the consumer output element.
*
* @param document
* The document in which the nodes are being added
* @param session
* The session informations
* @return The output element as an XML node
*/
private static Element getOutputElement(Document document, ISessionInfo session) {
Element outputElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_OUTPUT);
if (session.isSnapshotSession()) {
outputElement.appendChild(getSnapshotOuputsElement(document, session));
} else if (session.isStreamedTrace()) {
outputElement.appendChild(getNetOutputElement(document, session));
} else {
outputElement.appendChild(getConsumerOutputElement(document, session));
}
return outputElement;
}
/**
* Gets the 'channels' element after creating it.
*
* @param document
* The document in which the nodes are being added
* @param domain
* The domain type ({@link TraceDomainType})
* @param channels
* The channels to be added as elements
* @return The channels element as an XML element
*/
private static Element getChannelsElement(Document document, TraceDomainType domain, IChannelInfo[] channels) {
Element channelsElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_CHANNELS);
for (IChannelInfo channel : channels) {
Element channelElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_CHANNEL);
/* Add everything related to a channel */
addElementContent(document, channelElement, SessionConfigStrings.CONFIG_ELEMENT_NAME, channel.getName());
String overwriteMode = channel.isOverwriteMode() ? SessionConfigStrings.CONFIG_OVERWRITE_MODE_OVERWRITE : SessionConfigStrings.CONFIG_OVERWRITE_MODE_DISCARD;
String enabled = channel.getState().equals(TraceEnablement.ENABLED) ? SessionConfigStrings.CONFIG_STRING_TRUE : SessionConfigStrings.CONFIG_STRING_FALSE;
addElementContent(document, channelElement, SessionConfigStrings.CONFIG_ELEMENT_ENABLED, enabled);
addElementContent(document, channelElement, SessionConfigStrings.CONFIG_ELEMENT_OVERWRITE_MODE, overwriteMode);
addElementContent(document, channelElement, SessionConfigStrings.CONFIG_ELEMENT_SUBBUFFER_SIZE, channel.getSubBufferSize());
addElementContent(document, channelElement, SessionConfigStrings.CONFIG_ELEMENT_SUBBUFFER_COUNT, channel.getNumberOfSubBuffers());
addElementContent(document, channelElement, SessionConfigStrings.CONFIG_ELEMENT_SWITCH_TIMER_INTERVAL, channel.getSwitchTimer());
addElementContent(document, channelElement, SessionConfigStrings.CONFIG_ELEMENT_READ_TIMER_INTERVAL, channel.getReadTimer());
String outputType = channel.getOutputType().getInName().startsWith(SessionConfigStrings.CONFIG_OUTPUT_TYPE_MMAP) ?
SessionConfigStrings.CONFIG_OUTPUT_TYPE_MMAP : SessionConfigStrings.CONFIG_OUTPUT_TYPE_SPLICE;
addElementContent(document, channelElement, SessionConfigStrings.CONFIG_ELEMENT_OUTPUT_TYPE, outputType);
addElementContent(document, channelElement, SessionConfigStrings.CONFIG_ELEMENT_TRACEFILE_SIZE, channel.getMaxSizeTraceFiles());
addElementContent(document, channelElement, SessionConfigStrings.CONFIG_ELEMENT_TRACEFILE_COUNT, channel.getMaxNumberTraceFiles());
/*
* TODO: Replace the 0 value by the channel live timer property from
* SessionInfo once live session tracing is supported
*/
addElementContent(document, channelElement, SessionConfigStrings.CONFIG_ELEMENT_LIVE_TIMER_INTERVAL, SessionConfigStrings.CONFIG_STRING_ZERO);
/* Add the events */
channelElement.appendChild(getEventsElement(document, domain, channel.getEvents()));
channelsElement.appendChild(channelElement);
}
return channelsElement;
}
/**
* Gets the 'events' element after creating it. It is composed of the event
* informations from a list of IEventInfo.
*
* @param document
* The document in which the nodes are being added
* @param domain
* The domain type ({@link TraceDomainType})
* @param events
* The event informations to be added
* @return An element containing all the event informations as XML elements
*/
private static Element getEventsElement(Document document, TraceDomainType domain, IEventInfo[] events) {
Element eventsElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_EVENTS);
for (IEventInfo event : events) {
Element eventElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_EVENT);
/* Enabled attribute */
String enabled = event.getState().equals(TraceEnablement.ENABLED) ? SessionConfigStrings.CONFIG_STRING_TRUE : SessionConfigStrings.CONFIG_STRING_FALSE;
/* Add the attributes to the event node */
addElementContent(document, eventElement, SessionConfigStrings.CONFIG_ELEMENT_NAME, event.getName());
addElementContent(document, eventElement, SessionConfigStrings.CONFIG_ELEMENT_ENABLED, enabled);
TraceEventType eventType = event.getEventType();
if (!eventType.equals(TraceEventType.UNKNOWN)) {
addElementContent(document, eventElement, SessionConfigStrings.CONFIG_ELEMENT_TYPE, eventType.getInName().toUpperCase());
} else {
throw new IllegalArgumentException(Messages.SessionConfigXML_UnknownEventType);
}
/* Specific to UST session config: the log level */
if (domain.equals(TraceDomainType.UST) && !event.getLogLevel().equals(TraceLogLevel.LEVEL_UNKNOWN)) {
addElementContent(document, eventElement, SessionConfigStrings.CONFIG_ELEMENT_LOGLEVEL, event.getLogLevel().ordinal());
}
/* Add the node to the parent node events */
eventsElement.appendChild(eventElement);
}
return eventsElement;
}
/**
* Gets the 'consumer_output' element after creating it.
*
* @param document
* The document in which the nodes are being added
* @param session
* The session informations
* @return The consumer output element with his informations as XML elements
*/
private static Element getConsumerOutputElement(Document document, ISessionInfo session) {
Element consumerOutputElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_CONSUMER_OUTPUT);
Element destinationElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_DESTINATION);
/* Value of consumer output element */
addElementContent(document, consumerOutputElement, SessionConfigStrings.CONFIG_ELEMENT_ENABLED, SessionConfigStrings.CONFIG_STRING_TRUE);
if (session.isStreamedTrace()) {
/* If it is a streamed session, add the net output element */
destinationElement.appendChild(getNetOutputElement(document, session));
} else {
addElementContent(document, destinationElement, SessionConfigStrings.CONFIG_ELEMENT_PATH, session.getSessionPath());
}
consumerOutputElement.appendChild(destinationElement);
return consumerOutputElement;
}
/**
* Gets the 'net_output' element after creating it. It is composed of the
* control and data URIs.
*
* @param document
* The document in which the nodes are being added
* @param session
* The session informations
* @return The net output element
*/
private static Element getNetOutputElement(Document document, ISessionInfo session) {
Element netOutputElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_NET_OUTPUT);
String networkUrl = session.getNetworkUrl();
String controlUri = networkUrl == null ? session.getControlUrl() : networkUrl;
String dataUri = networkUrl == null ? session.getDataUrl() : networkUrl;
addElementContent(document, netOutputElement, SessionConfigStrings.CONFIG_ELEMENT_CONTROL_URI, controlUri);
addElementContent(document, netOutputElement, SessionConfigStrings.CONFIG_ELEMENT_DATA_URI, dataUri);
return netOutputElement;
}
/**
* Gets the 'snapshot_outputs' element after creating it.
*
* @param document
* The document in which the nodes are being added
* @param session
* The session informations
* @return The snapshot outputs element with snapshot informations as XML
* elements
*/
private static Element getSnapshotOuputsElement(Document document, ISessionInfo session) {
Element snapshotOutputsElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_SNAPSHOT_OUTPUTS);
Element outputElement = document.createElement(SessionConfigStrings.CONFIG_ELEMENT_OUTPUT);
/* Add the name of the snapshot and the max size element */
addElementContent(document, outputElement, SessionConfigStrings.CONFIG_ELEMENT_NAME, session.getSnapshotInfo().getName());
/*
* TODO: find the proper max size value of output element. For now it is
* set to the default 0 value which means unlimited for lttng.
*/
addElementContent(document, outputElement, SessionConfigStrings.CONFIG_ELEMENT_MAX_SIZE, SessionConfigStrings.CONFIG_STRING_ZERO);
outputElement.appendChild(getConsumerOutputElement(document, session));
snapshotOutputsElement.appendChild(outputElement);
return snapshotOutputsElement;
}
// ---------------------------------------------------------
// Utilities
// ---------------------------------------------------------
/**
* Validates the session configuration file against its schema.
*
* @param sessionFile
* The session configuration file
* @return The status of the validation
*/
public static IStatus sessionValidate(File sessionFile) {
URL url = SessionConfigGenerator.class.getResource(SESSION_XSD_FILENAME);
Source xmlSource = new StreamSource(sessionFile);
try {
Schema schema = XmlUtils.newSafeSchemaFactory().newSchema(url);
XmlUtils.safeValidate(schema, xmlSource);
} catch (SAXParseException e) {
String error = NLS.bind(Messages.SessionConfigXML_XmlParseError, e.getLineNumber(), e.getLocalizedMessage());
Activator.getDefault().logError(error);
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, error, e);
} catch (SAXException e) {
String error = NLS.bind(Messages.SessionConfigXML_XmlValidationError, e.getLocalizedMessage());
Activator.getDefault().logError(error);
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, error, e);
} catch (IOException e) {
String error = Messages.SessionConfigXML_XmlValidateError;
Activator.getDefault().logError("IO exception occurred", e); //$NON-NLS-1$
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, error, e);
}
return Status.OK_STATUS;
}
/**
* Saves the session configuration into a XML file.
*
* @param document
* The document representing the session configuration file
* @param destination
* The path of the locally saved session configuration file
* @throws TransformerException
* On an transformation process
*/
private static void saveSessionConfig(Document document, String destination) throws TransformerException {
/* Write the content into a XML file */
Transformer transformer = XmlUtils.newSecureTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
transformer.setOutputProperty(INDENT_AMOUNT_PROPERTY_NAME, INDENT_AMOUNT_PROPERTY_VALUE);
DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(new File(destination));
transformer.transform(source, result);
}
/**
* Adds to a parent node an element with his content.
*
* @param document
* The document in which the nodes are being added
* @param parent
* The parent node that contains the element and his content
* @param elementName
* The element container name
* @param elementContent
* The content itself
*/
private static void addElementContent(Document document, Element parent, String elementName, Object elementContent) {
Element contentElement = document.createElement(elementName);
contentElement.appendChild(document.createTextNode(elementContent.toString()));
parent.appendChild(contentElement);
}
}