/******************************************************************************* * Copyright (c) 2015 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.internal.ctf.core.event.metadata.tsdl.stream; import static org.eclipse.tracecompass.internal.ctf.core.event.metadata.tsdl.TsdlUtils.concatenateUnaryStrings; import static org.eclipse.tracecompass.internal.ctf.core.event.metadata.tsdl.TsdlUtils.isAnyUnaryString; import java.util.List; import org.antlr.runtime.tree.CommonTree; import org.antlr.runtime.tree.Tree; import org.eclipse.core.runtime.IStatus; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.tracecompass.ctf.core.CTFStrings; import org.eclipse.tracecompass.ctf.core.event.metadata.DeclarationScope; import org.eclipse.tracecompass.ctf.core.event.types.IDeclaration; import org.eclipse.tracecompass.ctf.core.event.types.IEventHeaderDeclaration; import org.eclipse.tracecompass.ctf.core.event.types.StructDeclaration; import org.eclipse.tracecompass.ctf.core.trace.CTFTrace; import org.eclipse.tracecompass.ctf.parser.CTFParser; import org.eclipse.tracecompass.internal.ctf.core.Activator; import org.eclipse.tracecompass.internal.ctf.core.event.metadata.AbstractScopedCommonTreeParser; import org.eclipse.tracecompass.internal.ctf.core.event.metadata.Messages; import org.eclipse.tracecompass.internal.ctf.core.event.metadata.MetadataStrings; import org.eclipse.tracecompass.internal.ctf.core.event.metadata.ParseException; import org.eclipse.tracecompass.internal.ctf.core.event.metadata.tsdl.TypeSpecifierListParser; import org.eclipse.tracecompass.internal.ctf.core.trace.CTFStream; /** * * An <em>event stream</em> can be divided into contiguous event packets of * variable size. An event packet can contain a certain amount of padding at the * end. The stream header is repeated at the beginning of each event packet. * <br> * The event stream header will therefore be referred to as the * <em>event packet header</em> throughout the rest of this document. <br> * * An event stream is divided in contiguous event packets of variable size. * These subdivisions allow the trace analyzer to perform a fast binary search * by time within the stream (typically requiring to index only the event packet * headers) without reading the whole stream. These subdivisions have a variable * size to eliminate the need to transfer the event packet padding when * partially filled event packets must be sent when streaming a trace for live * viewing/analysis. An event packet can contain a certain amount of padding at * the end. Dividing streams into event packets is also useful for network * streaming over UDP and flight recorder mode tracing (a whole event packet can * be swapped out of the buffer atomically for reading). <br> * The stream header is repeated at the beginning of each event packet to allow * flexibility in terms of: * <ul> * <li>streaming support</li> * <li>allowing arbitrary buffers to be discarded without making the trace * unreadable</li> * <li>allow UDP packet loss handling by either dealing with missing event * packet or asking for re-transmission</li> * <li>transparently support flight recorder mode</li> * <li>transparently support crash dump</li> * </ul> * * @author Matthew Khouzam * @author Efficios - Description * */ public final class StreamDeclarationParser extends AbstractScopedCommonTreeParser { private static final String IDENTIFIER_MUST_BE_A_STRING = "Left side of CTF assignment must be a string"; //$NON-NLS-1$ private static final String PACKET_CONTEXT = "packet.context "; //$NON-NLS-1$ private static final String EVENT_CONTEXT = "event.context "; //$NON-NLS-1$ private static final String EVENT_HEADER = "event.header "; //$NON-NLS-1$ private static final String STREAM_ID = "stream id "; //$NON-NLS-1$ private static final String EXPECTS_A_STRUCT = "expects a struct"; //$NON-NLS-1$ private static final String SCOPE_NOT_FOUND = "scope not found"; //$NON-NLS-1$ private static final String EXPECTS_A_TYPE_SPECIFIER = "expects a type specifier"; //$NON-NLS-1$ private static final String ALREADY_DEFINED = "already defined"; //$NON-NLS-1$ /** * A parameter object, contains a trace, a stream and a scope * * @author Matthew Khouzam * */ @NonNullByDefault public static final class Param implements ICommonTreeParserParameter { private final CTFStream fStream; private final CTFTrace fTrace; private final DeclarationScope fDeclarationScope; /** * The parameter object * * @param trace * the trace * @param stream * the stream * @param scope * the scope */ public Param(CTFTrace trace, CTFStream stream, DeclarationScope scope) { fTrace = trace; fStream = stream; fDeclarationScope = scope; } } /** * Instance */ public static final StreamDeclarationParser INSTANCE = new StreamDeclarationParser(); private StreamDeclarationParser() { } /** * Parse a stream declaration. * * @param streamDecl * the AST node containing the STREAM type * @param param * The stream to fill, the trace and the current scope * @return the stream declaration * @throws ParseException * if the stream AST node is malformed */ @Override public CTFStream parse(CommonTree streamDecl, ICommonTreeParserParameter param) throws ParseException { if (!(param instanceof Param)) { throw new IllegalArgumentException("Param must be a " + Param.class.getCanonicalName()); //$NON-NLS-1$ } DeclarationScope scope = (((Param) param).fDeclarationScope); CTFStream stream = ((Param) param).fStream; CTFTrace fTrace = ((Param) param).fTrace; /* There should be a left and right */ CommonTree leftNode = (CommonTree) streamDecl.getChild(0); CommonTree rightNode = (CommonTree) streamDecl.getChild(1); List<CommonTree> leftStrings = leftNode.getChildren(); if (!isAnyUnaryString(leftStrings.get(0))) { throw new ParseException(IDENTIFIER_MUST_BE_A_STRING); } String left = concatenateUnaryStrings(leftStrings); if (left.equals(MetadataStrings.ID)) { if (stream.isIdSet()) { throw new ParseException(STREAM_ID + ALREADY_DEFINED); } long streamID = StreamIdParser.INSTANCE.parse(rightNode, null); stream.setId(streamID); } else if (left.equals(MetadataStrings.EVENT_HEADER)) { if (stream.isEventHeaderSet()) { throw new ParseException(EVENT_HEADER + ALREADY_DEFINED); } CommonTree typeSpecifier = (CommonTree) rightNode.getChild(0); if (typeSpecifier.getType() != CTFParser.TYPE_SPECIFIER_LIST) { throw new ParseException(EVENT_HEADER + EXPECTS_A_TYPE_SPECIFIER); } IDeclaration eventHeaderDecl = TypeSpecifierListParser.INSTANCE.parse(typeSpecifier, new TypeSpecifierListParser.Param(fTrace, null, null, scope)); DeclarationScope eventHeaderScope = lookupStructName(typeSpecifier, scope); if (eventHeaderScope == null) { throw new ParseException(EVENT_HEADER + SCOPE_NOT_FOUND); } DeclarationScope eventScope = new DeclarationScope(scope, MetadataStrings.EVENT); eventScope.addChild(eventHeaderScope); eventHeaderScope.setName(CTFStrings.HEADER); if (eventHeaderDecl instanceof StructDeclaration) { stream.setEventHeader((StructDeclaration) eventHeaderDecl); } else if (eventHeaderDecl instanceof IEventHeaderDeclaration) { stream.setEventHeader((IEventHeaderDeclaration) eventHeaderDecl); } else { throw new ParseException(EVENT_HEADER + EXPECTS_A_STRUCT); } } else if (left.equals(MetadataStrings.EVENT_CONTEXT)) { if (stream.isEventContextSet()) { throw new ParseException(EVENT_CONTEXT + ALREADY_DEFINED); } CommonTree typeSpecifier = (CommonTree) rightNode.getChild(0); if (typeSpecifier.getType() != CTFParser.TYPE_SPECIFIER_LIST) { throw new ParseException(EVENT_CONTEXT + EXPECTS_A_TYPE_SPECIFIER); } IDeclaration eventContextDecl = TypeSpecifierListParser.INSTANCE.parse(typeSpecifier, new TypeSpecifierListParser.Param(fTrace, null, null, scope)); if (!(eventContextDecl instanceof StructDeclaration)) { throw new ParseException(EVENT_CONTEXT + EXPECTS_A_STRUCT); } stream.setEventContext((StructDeclaration) eventContextDecl); } else if (left.equals(MetadataStrings.PACKET_CONTEXT)) { if (stream.isPacketContextSet()) { throw new ParseException(PACKET_CONTEXT + ALREADY_DEFINED); } CommonTree typeSpecifier = (CommonTree) rightNode.getChild(0); if (typeSpecifier.getType() != CTFParser.TYPE_SPECIFIER_LIST) { throw new ParseException(PACKET_CONTEXT + EXPECTS_A_TYPE_SPECIFIER); } IDeclaration packetContextDecl = TypeSpecifierListParser.INSTANCE.parse(typeSpecifier, new TypeSpecifierListParser.Param(fTrace, null, null, scope)); if (!(packetContextDecl instanceof StructDeclaration)) { throw new ParseException(PACKET_CONTEXT + EXPECTS_A_STRUCT); } stream.setPacketContext((StructDeclaration) packetContextDecl); } else { Activator.log(IStatus.WARNING, Messages.IOStructGen_UnknownStreamAttributeWarning + ' ' + left); } return stream; } private static DeclarationScope lookupStructName(CommonTree typeSpecifier, DeclarationScope scope) { /* * This needs a struct.struct_name.name to work, luckily, that is 99.99% * of traces we receive. */ final Tree potentialStruct = typeSpecifier.getChild(0); DeclarationScope eventHeaderScope = null; if (potentialStruct.getType() == (CTFParser.STRUCT)) { final Tree potentialStructName = potentialStruct.getChild(0); if (potentialStructName.getType() == (CTFParser.STRUCT_NAME)) { final String name = potentialStructName.getChild(0).getText(); eventHeaderScope = scope.lookupChildRecursive(name); } } /* * If that fails, maybe the struct is anonymous */ if (eventHeaderScope == null) { eventHeaderScope = scope.lookupChildRecursive(MetadataStrings.STRUCT); } /* * This can still be null */ return eventHeaderScope; } }