| /********************************************************************** |
| * 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); |
| } |
| } |