package org.opendaylight.openflowplugin.api.openflow.device.handlers;
import javax.annotation.Nonnull;
+import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.EventIdentifier;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.MultipartReply;
/**
*/
void addMultipartMsg(@Nonnull MultipartReply reply);
+ void addMultipartMsg(@Nonnull MultipartReply reply, @Nonnull EventIdentifier eventIdentifier);
+
/**
* Null response could be a valid end multipart collecting event for barrier response scenario.
* We are not able to resolve an issue (it is or it isn't barrier scenario) so we have to finish
* collecting multipart messages successfully.
*/
void endCollecting();
+
+ void endCollecting(EventIdentifier eventIdentifier);
+
}
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. 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.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific;
+
+/**
+ * Created by Martin Bobak <mbobak@cisco.com> on 29.5.2015.
+ */
+public final class EventIdentifier {
+
+ private final String eventName;
+ private final String deviceId;
+
+ public EventIdentifier(final String eventName, final String deviceId) {
+ this.eventName = eventName;
+ this.deviceId = deviceId;
+ }
+
+ public String getEventName() {
+ return eventName;
+ }
+
+ public String getDeviceId() {
+ return deviceId;
+ }
+}
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.List;
+import javax.annotation.Nonnull;
import org.opendaylight.openflowplugin.api.openflow.device.RequestContext;
import org.opendaylight.openflowplugin.api.openflow.device.handlers.DeviceReplyProcessor;
import org.opendaylight.openflowplugin.api.openflow.device.handlers.MultiMsgCollector;
+import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.EventIdentifier;
+import org.opendaylight.openflowplugin.impl.statistics.ofpspecific.EventsTimeCounter;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.common.types.rev130731.MultipartType;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.ErrorMessage;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.ErrorMessageBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.MultipartReply;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
/**
* <p>
-
* Implementation for {@link MultiMsgCollector} interface
*
* @author <a href="mailto:vdemcak@cisco.com">Vaclav Demcak</a>
@Override
public void addMultipartMsg(final MultipartReply reply) {
+ addMultipartMsg(reply, null);
+ }
+
+ @Override
+ public void addMultipartMsg(@Nonnull final MultipartReply reply, @Nonnull final EventIdentifier eventIdentifier) {
Preconditions.checkNotNull(reply);
Preconditions.checkArgument(requestContext.getXid().getValue().equals(reply.getXid()));
LOG.trace("Try to add Multipart reply msg with XID {}", reply.getXid());
replyCollection.add(reply);
if (!reply.getFlags().isOFPMPFREQMORE()) {
- endCollecting();
+ endCollecting(eventIdentifier);
}
}
public void endCollecting() {
+ endCollecting(null);
+ }
+
+ public void endCollecting(final EventIdentifier eventIdentifier) {
final RpcResult<List<MultipartReply>> rpcResult = RpcResultBuilder.success(replyCollection).build();
+ if (null != eventIdentifier) {
+ EventsTimeCounter.markEnd(eventIdentifier);
+ }
requestContext.setResult(rpcResult);
requestContext.close();
deviceReplyProcessor.processReply(requestContext.getXid(), replyCollection);
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. 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.opendaylight.openflowplugin.impl.karaf;
+
+import java.io.PrintStream;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.console.OsgiCommandSupport;
+import org.opendaylight.openflowplugin.impl.statistics.ofpspecific.EventsTimeCounter;
+
+/**
+ * Created by Martin Bobak <mbobak@cisco.com> on 28.5.2015.
+ */
+@Command(scope = "ofp", name = "reset-time-counters", description = "Resets events time counters.")
+public class ResetEventTimesComandProvider extends OsgiCommandSupport {
+
+ @Override
+ protected Object doExecute() throws Exception {
+ PrintStream out = session.getConsole();
+ EventsTimeCounter.resetAllCounters();
+ out.print("Events time counters reset.\n");
+ return null;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. 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.opendaylight.openflowplugin.impl.karaf;
+
+import java.io.PrintStream;
+import java.util.List;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.console.OsgiCommandSupport;
+import org.opendaylight.openflowplugin.impl.statistics.ofpspecific.EventsTimeCounter;
+
+/**
+ * Created by Martin Bobak <mbobak@cisco.com> on 28.5.2015.
+ */
+@Command(scope = "ofp", name = "show-time-counters", description = "Shows time counts for events.")
+public class ShowEventTimesComandProvider extends OsgiCommandSupport {
+
+ @Override
+ protected Object doExecute() throws Exception {
+ PrintStream out = session.getConsole();
+ final List<String> statistics = EventsTimeCounter.provideTimes();
+ final StringBuilder result = new StringBuilder();
+ for (String line : statistics) {
+ result.append(line);
+ result.append("\n");
+ }
+ out.print(result.toString());
+ return null;
+ }
+}
@Override
protected final FutureCallback<OfHeader> createCallback(final RequestContext<List<MultipartReply>> context, final Class<?> requestType) {
- return new MultipartRequestCallback(context, requestType, getDeviceContext());
+ return new MultipartRequestCallback(context, requestType, getDeviceContext(), getEventIdentifier());
}
+
+
}
import javax.annotation.Nullable;
import org.opendaylight.openflowjava.protocol.api.connection.DeviceRequestFailedException;
import org.opendaylight.openflowplugin.api.openflow.device.RequestContext;
+import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.EventIdentifier;
import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.MessageSpy;
import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.MessageSpy.STATISTIC_GROUP;
+import org.opendaylight.openflowplugin.impl.statistics.ofpspecific.EventsTimeCounter;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.Error;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OfHeader;
import org.opendaylight.yangtools.yang.common.RpcError;
private final RequestContext<T> context;
private final Class<?> requestType;
private final MessageSpy spy;
+ private EventIdentifier eventIdentifier;
+
protected AbstractRequestCallback(final RequestContext<T> context, final Class<?> requestType, final MessageSpy spy) {
this.context = Preconditions.checkNotNull(context);
this.spy = Preconditions.checkNotNull(spy);
}
+ protected AbstractRequestCallback(final RequestContext<T> context,
+ final Class<?> requestType,
+ final MessageSpy spy,
+ final EventIdentifier eventIdentifier) {
+ this.context = Preconditions.checkNotNull(context);
+ this.requestType = Preconditions.checkNotNull(requestType);
+ this.spy = Preconditions.checkNotNull(spy);
+ this.eventIdentifier = eventIdentifier;
+ }
+
protected final void setResult(@Nullable final RpcResult<T> result) {
context.setResult(result);
context.close();
spy.spyMessage(requestType, Preconditions.checkNotNull(group));
}
+ public EventIdentifier getEventIdentifier() {
+ return eventIdentifier;
+ }
+
@Override
public final void onFailure(final Throwable t) {
final RpcResultBuilder<T> builder;
+ if (null != eventIdentifier) {
+ EventsTimeCounter.markEnd(eventIdentifier);
+ }
if (t instanceof DeviceRequestFailedException) {
final Error err = ((DeviceRequestFailedException) t).getError();
final String errorString = String.format("Device reported error type %s code %s", err.getTypeString(), err.getCodeString());
import javax.annotation.Nonnull;
import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
import org.opendaylight.openflowjava.protocol.api.connection.OutboundQueue;
-import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionContext;
import org.opendaylight.openflowplugin.api.openflow.device.DeviceContext;
import org.opendaylight.openflowplugin.api.openflow.device.RequestContext;
import org.opendaylight.openflowplugin.api.openflow.device.RequestContextStack;
import org.opendaylight.openflowplugin.api.openflow.device.Xid;
+import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.EventIdentifier;
import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.MessageSpy;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.FeaturesReply;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OfHeader;
private final DeviceContext deviceContext;
private final ConnectionAdapter primaryConnectionAdapter;
private final MessageSpy messageSpy;
+ private EventIdentifier eventIdentifier;
public AbstractService(final RequestContextStack requestContextStack, final DeviceContext deviceContext) {
this.requestContextStack = requestContextStack;
this.messageSpy = deviceContext.getMessageSpy();
}
+ public EventIdentifier getEventIdentifier() {
+ return eventIdentifier;
+ }
+
+ public void setEventIdentifier(final EventIdentifier eventIdentifier) {
+ this.eventIdentifier = eventIdentifier;
+ }
+
public short getVersion() {
return version;
}
}
protected abstract OfHeader buildRequest(Xid xid, I input);
+
protected abstract FutureCallback<OfHeader> createCallback(RequestContext<O> context, Class<?> requestType);
public final ListenableFuture<RpcResult<O>> handleServiceCall(@Nonnull final I input) {
import org.opendaylight.openflowplugin.api.openflow.device.DeviceContext;
import org.opendaylight.openflowplugin.api.openflow.device.RequestContext;
import org.opendaylight.openflowplugin.api.openflow.device.handlers.MultiMsgCollector;
+import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.EventIdentifier;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.MultipartReply;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OfHeader;
import org.opendaylight.yangtools.yang.common.RpcError;
collector = deviceContext.getMultiMsgCollector(context);
}
+ public MultipartRequestCallback(final RequestContext<List<MultipartReply>> context,
+ final Class<?> requestType,
+ final DeviceContext deviceContext,
+ final EventIdentifier eventIdentifier) {
+ super(context, requestType, deviceContext.getMessageSpy(), eventIdentifier);
+ collector = deviceContext.getMultiMsgCollector(context);
+ }
+
@Override
public void onSuccess(final OfHeader result) {
if (result == null) {
LOG.info("Ofheader was null.");
- collector.endCollecting();
+ collector.endCollecting(getEventIdentifier());
return;
}
LOG.info("Unexpected response type received {}.", result.getClass());
final RpcResultBuilder<List<MultipartReply>> rpcResultBuilder =
RpcResultBuilder.<List<MultipartReply>>failed().withError(RpcError.ErrorType.APPLICATION,
- String.format("Unexpected response type received %s.", result.getClass()));
+ String.format("Unexpected response type received %s.", result.getClass()));
setResult(rpcResultBuilder.build());
} else {
- collector.addMultipartMsg((MultipartReply) result);
+ collector.addMultipartMsg((MultipartReply) result, getEventIdentifier());
}
}
import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
import org.opendaylight.openflowplugin.api.openflow.device.DeviceContext;
import org.opendaylight.openflowplugin.api.openflow.registry.flow.FlowRegistryKey;
+import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.EventIdentifier;
import org.opendaylight.openflowplugin.impl.registry.flow.FlowRegistryKeyFactory;
+import org.opendaylight.openflowplugin.impl.statistics.ofpspecific.EventsTimeCounter;
import org.opendaylight.openflowplugin.impl.statistics.services.dedicated.StatisticsGatheringService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector;
private static final Logger LOG = LoggerFactory.getLogger(StatisticsGatheringUtils.class);
private static final SinglePurposeMultipartReplyTranslator MULTIPART_REPLY_TRANSLATOR = new SinglePurposeMultipartReplyTranslator();
+ public static final String QUEUE2_REQCTX = "QUEUE2REQCTX-";
private StatisticsGatheringUtils() {
throw new IllegalStateException("This class should not be instantiated.");
}
-
public static ListenableFuture<Boolean> gatherStatistics(final StatisticsGatheringService statisticsGatheringService,
final DeviceContext deviceContext,
final MultipartType type) {
//FIXME : anytype listener must not be send as parameter, it has to be extracted from device context inside service
+ final String deviceId = deviceContext.getPrimaryConnectionContext().getNodeId().toString();
+ EventIdentifier wholeProcessEventIdentifier = null;
+ if (MultipartType.OFPMPFLOW.equals(type)) {
+ wholeProcessEventIdentifier = new EventIdentifier(type.toString(), deviceId);
+ EventsTimeCounter.markStart(wholeProcessEventIdentifier);
+ }
+ EventIdentifier ofpQueuToRequestContextEventIdentifier = new EventIdentifier(QUEUE2_REQCTX + type.toString(), deviceId);
final ListenableFuture<RpcResult<List<MultipartReply>>> statisticsDataInFuture =
- JdkFutureAdapters.listenInPoolThread(statisticsGatheringService.getStatisticsOfType(type));
- return transformAndStoreStatisticsData(statisticsDataInFuture, deviceContext);
+ JdkFutureAdapters.listenInPoolThread(statisticsGatheringService.getStatisticsOfType(ofpQueuToRequestContextEventIdentifier, type));
+ return transformAndStoreStatisticsData(statisticsDataInFuture, deviceContext, wholeProcessEventIdentifier);
}
private static ListenableFuture<Boolean> transformAndStoreStatisticsData(final ListenableFuture<RpcResult<List<MultipartReply>>> statisticsDataInFuture,
- final DeviceContext deviceContext) {
+ final DeviceContext deviceContext,
+ final EventIdentifier eventIdentifier) {
return Futures.transform(statisticsDataInFuture, new Function<RpcResult<List<MultipartReply>>, Boolean>() {
@Nullable
@Override
processQueueStatistics((Iterable<QueueStatisticsUpdate>) allMultipartData, deviceContext);
} else if (multipartData instanceof FlowsStatisticsUpdate) {
processFlowStatistics((Iterable<FlowsStatisticsUpdate>) allMultipartData, deviceContext);
+ EventsTimeCounter.markEnd(eventIdentifier);
} else if (multipartData instanceof GroupDescStatsUpdated) {
processGroupDescStats((Iterable<GroupDescStatsUpdated>) allMultipartData, deviceContext);
} else if (multipartData instanceof MeterConfigStatsUpdated) {
private static void deleteAllKnownFlows(final DeviceContext deviceContext, final InstanceIdentifier<Node> nodeIdent) {
if (deviceContext.getDeviceState().deviceSynchronized()) {
final Short numOfTablesOnDevice = deviceContext.getDeviceState().getFeatures().getTables();
- for (short i=0; i<numOfTablesOnDevice; i++) {
+ for (short i = 0; i < numOfTablesOnDevice; i++) {
final KeyedInstanceIdentifier<Table, TableKey> iiToTable
- = nodeIdent.augmentation(FlowCapableNode.class).child( Table.class, new TableKey(i));
+ = nodeIdent.augmentation(FlowCapableNode.class).child(Table.class, new TableKey(i));
final ReadTransaction readTx = deviceContext.getReadTransaction();
final CheckedFuture<Optional<Table>, ReadFailedException> tableDataFuture = readTx.read(LogicalDatastoreType.OPERATIONAL, iiToTable);
try {
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. 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.opendaylight.openflowplugin.impl.statistics.ofpspecific;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.EventIdentifier;
+
+/**
+ * Created by Martin Bobak <mbobak@cisco.com> on 28.5.2015.
+ */
+public final class EventsTimeCounter {
+
+ private static Map<String, Map<String, EventTimeCounter>> devicesEvents = new HashMap<>();
+
+ public static void markStart(final EventIdentifier eventIdentifier) {
+ Map<String, EventTimeCounter> deviceEvents = getOrCreateCountersForDevice(eventIdentifier.getDeviceId());
+ EventTimeCounter eventTimeCounter = getOrCreateEventOfType(eventIdentifier.getEventName(), deviceEvents);
+ eventTimeCounter.markStart();
+ }
+
+ public static void markEnd(final EventIdentifier eventIdentifier) {
+ Map<String, EventTimeCounter> deviceEvents = getOrCreateCountersForDevice(eventIdentifier.getDeviceId());
+ EventTimeCounter eventTimeCounter = getOrCreateEventOfType(eventIdentifier.getEventName(), deviceEvents);
+ eventTimeCounter.markEnd();
+ }
+
+ private static EventTimeCounter getOrCreateEventOfType(final String event, final Map<String, EventTimeCounter> deviceEvents) {
+ EventTimeCounter lookup = deviceEvents.get(event);
+ if (null == lookup) {
+ lookup = new EventTimeCounter();
+ deviceEvents.put(event, lookup);
+ }
+ return lookup;
+ }
+
+ private static Map<String, EventTimeCounter> getOrCreateCountersForDevice(final String deviceId) {
+ Map<String, EventTimeCounter> lookup = devicesEvents.get(deviceId);
+ if (null == lookup) {
+ lookup = new HashMap<String, EventTimeCounter>();
+ devicesEvents.put(deviceId, lookup);
+ }
+
+ return lookup;
+ }
+
+ public static List<String> provideTimes() {
+ List<String> dump = new ArrayList<>();
+ for (Map.Entry<String, Map<String, EventTimeCounter>> deviceEntry : devicesEvents.entrySet()) {
+ Map<String, EventTimeCounter> eventsMap = deviceEntry.getValue();
+ dump.add("================================================");
+ dump.add(String.format("DEVICE : %s", deviceEntry.getKey()));
+ for (Map.Entry<String, EventTimeCounter> eventEntry : eventsMap.entrySet()) {
+ final String eventName = eventEntry.getKey();
+ final EventTimeCounter eventTimeCounter = eventEntry.getValue();
+ dump.add(String.format("%s", eventName));
+ dump.add(String.format(" MIN TIME (ms): %d",
+ TimeUnit.MILLISECONDS.convert(eventTimeCounter.getMinimum(), TimeUnit.NANOSECONDS)));
+ dump.add(String.format(" MAX TIME (ms): %d",
+ TimeUnit.MILLISECONDS.convert(eventTimeCounter.getMaximum(), TimeUnit.NANOSECONDS)));
+ dump.add(String.format(" AVG TIME (ms): %d",
+ TimeUnit.MILLISECONDS.convert(eventTimeCounter.getAverage(), TimeUnit.NANOSECONDS)));
+
+ }
+ }
+ return dump;
+ }
+
+ public static void resetAllCounters() {
+ devicesEvents = new HashMap<>();
+ }
+
+
+ private static final class EventTimeCounter {
+
+ private volatile long delta = 0;
+ private volatile long average = 0;
+ private volatile long minimum = 0;
+ private volatile long maximum = 0;
+ private volatile long summary = 0;
+ private volatile int counter = 0;
+
+ public synchronized void markStart() {
+ delta = System.nanoTime();
+ }
+
+ public synchronized void markEnd() {
+ if (0 == delta) {
+ return;
+ }
+ counter++;
+ delta = System.nanoTime() - delta;
+ if (delta < minimum || minimum == 0) {
+ minimum = delta;
+ }
+ if (delta > maximum) {
+ maximum = delta;
+ }
+ summary += delta;
+ average = summary / counter;
+ }
+
+ public synchronized void resetCounters() {
+ delta = 0;
+ average = 0;
+ minimum = 0;
+ maximum = 0;
+ summary = 0;
+ counter = 0;
+
+ }
+
+ public synchronized long getAverage() {
+ return average;
+ }
+
+ public synchronized long getMinimum() {
+ return minimum;
+ }
+
+ public synchronized long getMaximum() {
+ return maximum;
+ }
+
+ }
+
+
+}
import org.opendaylight.openflowplugin.api.openflow.device.DeviceContext;
import org.opendaylight.openflowplugin.api.openflow.device.RequestContextStack;
import org.opendaylight.openflowplugin.api.openflow.device.Xid;
+import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.EventIdentifier;
import org.opendaylight.openflowplugin.impl.common.MultipartRequestInputFactory;
import org.opendaylight.openflowplugin.impl.services.AbstractMultipartService;
+import org.opendaylight.openflowplugin.impl.statistics.ofpspecific.EventsTimeCounter;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.common.types.rev130731.MultipartType;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.MultipartReply;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OfHeader;
* Created by Martin Bobak <mbobak@cisco.com> on 4.4.2015.
*/
public class StatisticsGatheringService extends AbstractMultipartService<MultipartType> {
+
public StatisticsGatheringService(final RequestContextStack requestContextStack, final DeviceContext deviceContext) {
super(requestContextStack, deviceContext);
}
- public Future<RpcResult<List<MultipartReply>>> getStatisticsOfType(final MultipartType type) {
+ public Future<RpcResult<List<MultipartReply>>> getStatisticsOfType(final EventIdentifier eventIdentifier, final MultipartType type) {
+ EventsTimeCounter.markStart(eventIdentifier);
+ setEventIdentifier(eventIdentifier);
return handleServiceCall(type);
}
<command name="ofp/clearStats">
<action class="org.opendaylight.openflowplugin.impl.karaf.ClearStatsCommandProvider"/>
</command>
+ <command name="ofp/show-time-counters">
+ <action class="org.opendaylight.openflowplugin.impl.karaf.ShowEventTimesComandProvider"/>
+ </command>
+ <command name="ofp/reset-time-counters">
+ <action class="org.opendaylight.openflowplugin.impl.karaf.ResetEventTimesComandProvider"/>
+ </command>
</command-bundle>