LatencyReportEntry.java

/**
 * Copyright (c) 2004-2025 Carnegie Mellon University and others. (see Contributors file).
 * All Rights Reserved.
 *
 * NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
 * KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE
 * OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT
 * MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 *
 * This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 * SPDX-License-Identifier: EPL-2.0
 *
 * Created, in part, with funding and support from the United States Government. (see Acknowledgments file).
 *
 * This program includes and/or can make use of certain third party source code, object code, documentation and other
 * files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system
 * configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and
 * conditions contained in any such Third Party Software or separate license file distributed with such Third Party
 * Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici-
 * aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li-
 * censes only apply to the Third Party Software and not any other portion of this program or this program as a whole.
 */
package org.osate.analysis.flows.model;

import static org.osate.result.util.ResultUtil.addRealValue;
import static org.osate.result.util.ResultUtil.addStringValue;
import static org.osate.ui.UiUtil.BestDecPoint;

import java.util.ArrayList;
import java.util.List;

import org.osate.aadl2.contrib.aadlproject.TimeUnits;
import org.osate.aadl2.contrib.communication.CommunicationProperties;
import org.osate.aadl2.instance.ConnectionInstance;
import org.osate.aadl2.instance.EndToEndFlowInstance;
import org.osate.aadl2.instance.SystemInstance;
import org.osate.aadl2.instance.SystemOperationMode;
import org.osate.aadl2.util.Aadl2Util;
import org.osate.analysis.flows.internal.utils.FlowLatencyUtil;
import org.osate.analysis.flows.model.LatencyContributor.LatencyContributorMethod;
import org.osate.analysis.flows.reporting.model.Line;
import org.osate.analysis.flows.reporting.model.ReportSeverity;
import org.osate.analysis.flows.reporting.model.ReportedCell;
import org.osate.analysis.flows.reporting.model.Section;
import org.osate.pluginsupport.properties.PropertyUtils;
import org.osate.pluginsupport.properties.RealRange;
import org.osate.result.Diagnostic;
import org.osate.result.Result;
import org.osate.result.ResultFactory;
import org.osate.result.ResultType;
import org.osate.result.util.ResultUtil;

/*
 * A report entry corresponds to the entry within the report.
 * It just contains all the contributors for a flow latency.
 * We should have one report entry for each end to end flow
 */

public class LatencyReportEntry {

	List<LatencyContributor> contributors;
	EndToEndFlowInstance relatedEndToEndFlow;
	List<Diagnostic> issues;
	// lastSampled may be a task, partition if no tasks inside the partition, sampling bus, or a sampling device/system
	LatencyContributor lastSampled = null;
	SystemOperationMode som = null;
	boolean asynchronousSystem = true;
	private final boolean majorFrameDelay;
	double expectedMaxLatency = 0;
	double expectedMinLatency = 0;
	double minValue = 0;
	double maxValue = 0;
	double minSpecifiedValue = 0;
	double maxSpecifiedValue = 0;

	public LatencyReportEntry(EndToEndFlowInstance etef, SystemOperationMode som, boolean asynchronousSystem,
			boolean majorFrameDelay) {
		this.contributors = new ArrayList<LatencyContributor>();
		this.relatedEndToEndFlow = etef;
		this.som = som;
		this.asynchronousSystem = asynchronousSystem;
		this.majorFrameDelay = majorFrameDelay;

		final RealRange expectedLatency = PropertyUtils
				.getScaledRange(CommunicationProperties::getLatency, relatedEndToEndFlow, TimeUnits.MS)
				.orElse(RealRange.ZEROED);

		expectedMaxLatency = expectedLatency.getMaximum();
		expectedMinLatency = expectedLatency.getMinimum();
	}

	public void finalizeReportEntry() {
		minValue = Math.round(getActualLatency(false) * 1000.0) / 1000.0;
		maxValue = Math.round(getActualLatency(true) * 1000.0) / 1000.0;

		minSpecifiedValue = Math.round(getMinimumSpecifiedLatency() * 1000.0) / 1000.0;
		maxSpecifiedValue = Math.round(getMaximumSpecifiedLatency() * 1000.0) / 1000.0;

	}

	public boolean doSynchronous() {
		return !asynchronousSystem;
	}

	public SystemOperationMode getSOM() {
		return this.som;
	}

	public EndToEndFlowInstance getETEF() {
		return this.relatedEndToEndFlow;
	}

	public List<Diagnostic> getIssues() {
		return this.issues;
	}

	public void addContributor(LatencyContributor lc) {
		this.contributors.add(lc);
	}

	public void setLastSampled(LatencyContributor lc) {
		lastSampled = lc;
	}

	public boolean wasSampled() {
		return lastSampled != null;
	}

	public double getMinimumCumLatency(LatencyContributor current) {
		double result = 0;
		if (lastSampled == null) {
			return 0;
		}
		int idx = this.contributors.indexOf(lastSampled);
		int me = this.contributors.indexOf(current);
		if (idx < 0 || me < 0) {
			return 0;
		}
		for (int i = idx + 1; i < me; i++) {
			LatencyContributor lc = this.contributors.get(i);
			result = result + lc.getTotalMinimum();
		}
		return result;
	}

	/**
	 * get cumulative latency since last sampled component. Ignore any sampling contributors.
	 * do not include current.
	 * @param current LatencyContributor
	 * @param doMax boolean true do max, false do min
	 * @return sum of latencies
	 */
	public double getCumLatency(LatencyContributor current, boolean doMax) {
		if (doMax) {
			return getMaximumCumLatency(current);
		} else {
			return getMinimumCumLatency(current);
		}
	}

	public double getMaximumCumLatency(LatencyContributor current) {
		double result = 0;
		if (lastSampled == null) {
			return 0;
		}
		int idx = this.contributors.indexOf(lastSampled);
		int me = this.contributors.indexOf(current);
		if (idx < 0 || me < 0) {
			return 0;
		}
		for (int i = idx + 1; i < me; i++) {
			LatencyContributor lc = this.contributors.get(i);
			result = result + lc.getTotalMaximum();
		}
		return result;
	}

	public List<LatencyContributor> getContributors() {
		return this.contributors;
	}

	public LatencyContributor getPrevious(LatencyContributor lc) {
		int idx = contributors.indexOf(lc);
		return idx == 0 ? null : contributors.get(idx - 1);
	}

	public boolean isConnection(LatencyContributor lc) {
		return (lc.getContributor() instanceof ConnectionInstance);
	}

	public boolean isPreviousConnectionSynchronous(LatencyContributor lc) {
		int idx = contributors.indexOf(lc);
		for (int i = idx - 1; i >= 0; i--) {
			LatencyContributor plc = contributors.get(i);
			if (plc.getContributor() instanceof ConnectionInstance) {
				return plc.isSynchronous();
			}
		}
		return false;
	}

	public boolean isPreviousConnectionSyncUnknown(LatencyContributor lc) {
		int idx = contributors.indexOf(lc);
		for (int i = idx - 1; i >= 0; i--) {
			LatencyContributor plc = contributors.get(i);
			if (plc.getContributor() instanceof ConnectionInstance) {
				return plc.isSyncUnknown();
			}
		}
		return false;
	}

	public double getLastPartitionOffset(LatencyContributor lc) {
		int idx = contributors.indexOf(lc);
		for (int i = idx - 1; i >= 0; i--) {
			LatencyContributor plc = contributors.get(i);
			if (plc.isPartitionOffset()) {
				return plc.getPartitionOffset();
			}
		}
		return -1;
	}

	public double getActualLatency(boolean doMaximum) {
		double res = 0.0;
		for (LatencyContributor lc : contributors) {
			// do some consistency checking
			if (lc.getImmediateDeadline() > 0.0) {
				// No sampling. we are the last of an immediate connection sequence.
				double cum = this.getCumLatency(lc, doMaximum) + lc.getTotal(doMaximum);
				if (cum > lc.getImmediateDeadline()) {
					lc.reportError(doMaximum,
							"immediate latency sequence exceeds deadline " + lc.getImmediateDeadline() + "ms");
				}
			}

			if (lc.getLatencyContributorMethod(doMaximum).equals(LatencyContributorMethod.FIRST_PERIODIC)) {
				// skip the first sampled if it is the first element in the contributor list
				// and remember initial periodic
				lastSampled = lc;
			} else if (lc.getLatencyContributorMethod(doMaximum).equals(LatencyContributorMethod.SAMPLED)) {
				// lets deal with the sampling case
				LatencyContributor last = getPrevious(lc);
				if (last != null && last.isPartition()) {
					// if it is a task and is preceded by a partition then accommodate for additional sampling latency
					if (lc.getSamplingPeriod() > last.getSamplingPeriod()) {
						double diff = lc.getSamplingPeriod() - last.getSamplingPeriod();
						res = res + diff;
						lc.setActualValue(diff, doMaximum);
						lc.reportInfoOnce(doMaximum, "Sampling period " + lc.getSamplingPeriod() + "ms");
					} else if ( // XXX added check for > 0
							lc.getSamplingPeriod() > 0 && lc.getSamplingPeriod() < last.getSamplingPeriod()) {
						lc.reportWarningOnce(doMaximum, "Task period smaller than partition period");
					} else {
						lc.reportInfoOnce(doMaximum, "Latency accounted for in partition latency");
					}
				} else if (((doSynchronous() && isPreviousConnectionSyncUnknown(lc))
						|| isPreviousConnectionSynchronous(lc)) && wasSampled()) {
					// there was a previous sampling component. We can to the roundup game.
					// We do this only for maximum. For minimum we assume no sampling delay.
					double diff = 0.0;
					if (doMaximum) {
						diff = FlowLatencyUtil.roundUpDiff(getCumLatency(lc, doMaximum), lc.getSamplingPeriod());
					}
					res = res + diff;
					lc.setActualValue(diff, doMaximum);
					if (doMaximum) {
						lc.reportInfo(doMaximum,
								"Worst case: Round up sampling delay to period " + lc.getSamplingPeriod() + "ms");
					} else {
						lc.reportInfo(doMaximum, "Best case: 0 ms sampling delay");
					}
					if (doSynchronous() && isPreviousConnectionSyncUnknown(lc)) {
						lc.reportInfoOnce(doMaximum, "Assume synchronous communication");
					} else if (isPreviousConnectionSynchronous(lc)) {
						lc.reportInfoOnce(doMaximum, "Synchronous communication on same platform");
					} else {
						lc.reportInfoOnce(doMaximum, "Assume synchronous communication");
					}
				} else {
					// we have the asynchronous case
					if (doMaximum) {
						res = res + lc.getSamplingPeriod();
						lc.setActualValue(lc.getSamplingPeriod(), doMaximum);
						lc.reportInfo(
								"Best case 0 ms worst case " + lc.getSamplingPeriod() + "ms (period) sampling delay");
					} else {
//						TODO: may want to enable				lc.reportInfo(doMaximum, "Best case: no sampling delay");
					}
				}

				lastSampled = lc;
			} else if (lc.getLatencyContributorMethod(doMaximum).equals(LatencyContributorMethod.DELAYED)) {
				// if it is a task and is preceded by a partition then accommodate for additional sampling latency
				LatencyContributor last = getPrevious(lc);
				if (last.isPartition()) {
					if (lc.getSamplingPeriod() > last.getSamplingPeriod()) {
						double diff = lc.getSamplingPeriod() - last.getSamplingPeriod();
						res = res + diff;
						lc.setActualValue(diff, doMaximum);
						lc.reportInfo(doMaximum, "Delay to next sampling period " + lc.getSamplingPeriod() + "ms");
					} else if (lc.getSamplingPeriod() < last.getSamplingPeriod()) {
						lc.reportWarning(doMaximum, "Task period smaller than partition period");
					} else {
						lc.reportInfo(doMaximum, "No added latency");
					}
				} else if (((doSynchronous() && isPreviousConnectionSyncUnknown(lc))
						|| isPreviousConnectionSynchronous(lc)) && wasSampled()) {
					// there was a previous sampling component. We can to the roundup game.
					double cumMin = getMinimumCumLatency(lc);
					// for delayed the frame delay for the minimum and the maximum should be the same
					// if both cumulative are within the same frame we are ok
					// if one of them goes into the next frame we give a warning
					double framediff = FlowLatencyUtil.roundUp(getMaximumCumLatency(lc), lc.getSamplingPeriod())
							- FlowLatencyUtil.roundUp(cumMin, lc.getSamplingPeriod());
					if (framediff > 0) {
						lc.reportWarning(doMaximum,
								"Min and max delay for delayed connection differ by a frame or more");
					}
					double diff = FlowLatencyUtil.roundUpDiff(getCumLatency(lc, doMaximum), lc.getSamplingPeriod());
					res = res + diff;
					lc.setActualValue(diff, doMaximum);
					lc.reportInfo(doMaximum, "Sampling period " + lc.getSamplingPeriod() + "ms");
					if (doSynchronous() && isPreviousConnectionSyncUnknown(lc)) {
						lc.reportInfoOnce(doMaximum, "Assume synchronous communication");
					} else if (isPreviousConnectionSynchronous(lc)) {
						lc.reportInfoOnce(doMaximum, "Synchronous communication on same platform");
					} else {
						lc.reportInfoOnce(doMaximum, "Assume synchronous communication");
					}
				} else {
					res = res + lc.getSamplingPeriod();
					lc.setActualValue(lc.getSamplingPeriod(), doMaximum);
					lc.reportInfo(doMaximum, "Sampling period " + lc.getSamplingPeriod() + "ms");
				}
				lastSampled = lc;
			} else if (lc.isPartition()) {
				// ignore if partition is the first entry as it goes along with FIRST_SAMPLED
				// partition boundary has been crossed
				if (contributors.indexOf(lc) > 0) {
					// add partition latency unless the first component
					if (((doSynchronous() && isPreviousConnectionSyncUnknown(lc))
							|| isPreviousConnectionSynchronous(lc)) && wasSampled()) {
						// there was a previous sampling component. We can to the roundup game.
						if (lc.isPartitionFrame()) {
							double diff = FlowLatencyUtil.roundUpDiff(getCumLatency(lc, doMaximum),
									lc.getSamplingPeriod());
							res = res + diff;
							lc.setActualValue(diff, doMaximum);
							lc.reportInfo(doMaximum, "(S) major frame " + lc.getSamplingPeriod() + "ms");
						} else {
							// we have a partition offset.
							double cum = getCumLatency(lc, doMaximum);
							double myOffset = lc.getPartitionOffset();
							if (myOffset > -1) {
								// get the previous partition offset
								// it could be the lastSampled or the partition latency contributor right before that
								double prevOffset = getLastPartitionOffset(lc);
								if (prevOffset > -1) {
									// now we do the offset based roundup
									double prevPlus = prevOffset + cum;
									while ((myOffset - prevPlus) < 0) {
										myOffset = myOffset + lc.getSamplingPeriod();
									}
									double diff = myOffset - prevPlus;
									res = res + diff;
									lc.setActualValue(diff, doMaximum);
								} else {
									// the previous one is not based on a schedule
									// this branch should not be reached since both partitions are on same processor
									// thus they have the same schedule
									double diff = FlowLatencyUtil.roundUpDiff(cum, lc.getSamplingPeriod());
									res = res + diff;
									lc.setActualValue(diff, doMaximum);
									lc.reportInfo(doMaximum, "(S) major frame " + lc.getSamplingPeriod() + "ms");
								}
							} else {
								// the current does not have an offset despite being marked as PARTITION_SCHEDULE
								// this branch should not be reached
								double diff = FlowLatencyUtil.roundUpDiff(cum, lc.getSamplingPeriod());
								res = res + diff;
								lc.setActualValue(diff, doMaximum);
								lc.reportWarning(doMaximum, "Partition schedule without partition offset");
							}
						}
						if (doSynchronous() && isPreviousConnectionSyncUnknown(lc)) {
							lc.reportInfoOnce(doMaximum, "Assume synchronous communication");
						} else if (isPreviousConnectionSynchronous(lc)) {
							lc.reportInfoOnce(doMaximum, "Synchronous communication on same platform");
						} else {
							lc.reportInfoOnce(doMaximum, "Assume synchronous communication");
						}
					} else {
						// add the period. Even for partition with offset we have the worst case of a period
						if (doMaximum) {
							res = res + lc.getSamplingPeriod();
							lc.setActualValue(lc.getSamplingPeriod(), doMaximum);
						}
					}
				} else {
					lc.reportInfoOnce(doMaximum,
							"Initial " + lc.getSamplingPeriod() + "ms partition latency not added");

				}
				// remember the partition in all cases as a sampling unit
				lastSampled = lc;
			} else if (lc.isPartitionOutputDelay()) {
				// deal with partition I/O delay, then add communication latency
				// do it as major frame delay or as partition end delay depending on preference setting
				if (majorFrameDelay) {
					// round up to next major frame
					double diff = FlowLatencyUtil.roundUpDiff(getCumLatency(lc, doMaximum) + lc.getPartitionOffset(),
							lc.getSamplingPeriod());
					res = res + diff;
					lc.setActualValue(diff, doMaximum);
					lc.reportInfoOnce(doMaximum, "Output at " + lc.getSamplingPeriod() + "ms major frame");
				} else {
					// round up to window duration. Note the cumulative could be more than the window
					double diff = FlowLatencyUtil.roundUpDiff(getCumLatency(lc, doMaximum), lc.getSamplingPeriod(),
							lc.getPartitionDuration());
					res = res + diff;
					lc.setActualValue(diff, doMaximum);
					lc.reportInfoOnce(doMaximum, "Output at " + lc.getPartitionDuration() + "ms partition end");
				}
			} else if (lc instanceof LatencyContributorConnection) {
				// check recursively for sampling protocol
				doSampledProtocol(lc, doMaximum, null);
				res = res + lc.getTotal(doMaximum);
			} else {
				res = res + lc.getTotal(doMaximum);
			}
		}

		return res;
	}

	public void doSampledProtocol(LatencyContributor lc, boolean doMaximum, LatencyContributor enclosing) {
		double sp = lc.getSamplingPeriod();
		if (lc.getWorstcaseLatencyContributorMethod().equals(LatencyContributorMethod.SAMPLED_PROTOCOL) && sp > 0.0) {
			double enclosingsp = enclosing == null ? 0.0 : enclosing.getSamplingPeriod();
			if (enclosing == null || sp > enclosingsp) {
				if (enclosing != null) {
					// reset sampling contribution by enclosing as the inner is higher
					enclosing.setActualValue(0.0, doMaximum);
					enclosing.reportInfoOnce(doMaximum,
							"Sampling period of " + enclosingsp + "ms accounted for in suceeding protocol");
				}
				if (doSynchronous() && wasSampled()) {
					// there was a previous sampling component. We can to the roundup game.
					double diff = FlowLatencyUtil.roundUpDiff(getCumLatency(lc, doMaximum), sp);
					lc.setActualValue(diff, doMaximum);
					enclosing = lc;
					lc.reportInfo(doMaximum, "Round up to sampling period " + lc.getSamplingPeriod() + "ms");
					if (doSynchronous() && isPreviousConnectionSyncUnknown(lc)) {
						lc.reportInfoOnce(doMaximum, "Assume synchronous communication");
					} else if (isPreviousConnectionSynchronous(lc)) {
						lc.reportInfoOnce(doMaximum, "Synchronous communication on same platform");
					} else {
						lc.reportInfoOnce(doMaximum, "Assume synchronous communication");
					}
				} else {
					if (doMaximum) {
						lc.setActualValue(sp, doMaximum);
						enclosing = lc;
						lc.reportInfo(
								"Best case 0 ms worst case " + sp + "ms (period) sampling delay");
					} else {
//				TODO: may want to enable				lc.reportInfo(doMaximum, "Best case: no sampling delay");
					}
				}
			} else {
				lc.reportInfoOnce(doMaximum,
						"Sampling contribution of " + sp + "ms accounted for in enclosing protocol");
			}
		}
		List<LatencyContributor> sublc = lc.getSubContributors();
		for (LatencyContributor latencyContributor : sublc) {
			doSampledProtocol(latencyContributor, doMaximum, enclosing);
		}
		return;
	}

	public double getMaximumSpecifiedLatency() {
		double res = 0.0;
		for (LatencyContributor lc : contributors) {
			res = res + lc.getTotalMaximumSpecified();
		}

		return res;
	}

	public double getMinimumSpecifiedLatency() {
		double res = 0.0;
		for (LatencyContributor lc : contributors) {
			res = res + lc.getTotalMinimumSpecified();
		}

		return res;
	}

	private void reportSummaryError(String str) {
		issues.add(ResultUtil.createErrorDiagnostic(str, relatedEndToEndFlow));
	}

	private void reportSummaryInfo(String str) {
		issues.add(ResultUtil.createInfoDiagnostic(str, relatedEndToEndFlow));
	}

	private void reportSummaryWarning(String str) {
		issues.add(ResultUtil.createWarningDiagnostic(str, relatedEndToEndFlow));
	}

	/**
	 * Get the name associated with the related end-to-end flow
	 * @return The related end-to-end flow's name
	 */
	public String getRelatedEndToEndFlowName() {
		return this.relatedEndToEndFlow.getName();
	}

	public Result genResult() {

		issues = new ArrayList<Diagnostic>();

		String inMode = Aadl2Util.isPrintableSOMName(som) ? som.getName() : "";
		String SOMMembers = Aadl2Util.getPrintableSOMMembers(som);
		Result result = ResultFactory.eINSTANCE.createResult();
		result.setModelElement(relatedEndToEndFlow);
		result.setMessage("Latency results for " + relatedEndToEndFlow.getName());

		addStringValue(result, inMode + SOMMembers);

		addRealValue(result, minValue);
		addRealValue(result,maxValue);
		addRealValue(result,minSpecifiedValue);
		addRealValue(result,maxSpecifiedValue);
		addRealValue(result,expectedMinLatency);
		addRealValue(result,expectedMaxLatency);

		/*
		 * In that case, the end to end flow has a minimum latency
		 */
		if (expectedMaxLatency > 0) {
			if (minSpecifiedValue > expectedMaxLatency) {
				reportSummaryError("Minimum specified flow latency total " + BestDecPoint(minSpecifiedValue)
				+ "ms exceeds expected maximum latency " + BestDecPoint(expectedMaxLatency) + "ms");
			} else if (minSpecifiedValue < expectedMinLatency) {
				reportSummaryWarning("Minimum specified flow latency total " + BestDecPoint(minSpecifiedValue)
				+ "ms less than expected minimum end to end latency " + BestDecPoint(expectedMinLatency)
				+ "ms (better response time)");
			}

			if (minValue > expectedMaxLatency) {
				reportSummaryError("Minimum actual latency total " + BestDecPoint(minValue)
				+ "ms exceeds expected maximum end to end latency " + BestDecPoint(expectedMaxLatency) + "ms");
			} else if (minValue < expectedMinLatency) {
				reportSummaryWarning("Minimum actual latency total " + BestDecPoint(minValue)
				+ "ms less than expected minimum end to end latency " + BestDecPoint(expectedMinLatency)
				+ "ms (faster actual minimum response time)");
			} else {
				reportSummaryInfo("Minimum actual latency total " + BestDecPoint(minValue)
				+ "ms is greater or equal to expected minimum end to end latency "
				+ BestDecPoint(expectedMinLatency) + "ms");
			}

			if (maxValue > 0) {
				if (expectedMaxLatency < maxSpecifiedValue) {
					reportSummaryError("Maximum specified flow latency total " + BestDecPoint(maxSpecifiedValue)
					+ "ms exceeds expected maximum end to end latency " + BestDecPoint(expectedMaxLatency)
					+ "ms");
				}

				if (expectedMaxLatency < maxValue) {
					reportSummaryError("Maximum actual latency total " + BestDecPoint(maxValue)
					+ "ms exceeds expected maximum end to end latency " + BestDecPoint(expectedMaxLatency)
					+ "ms");
				} else {
					reportSummaryInfo("Maximum actual latency total " + BestDecPoint(maxValue)
					+ "ms is less or equal to expected maximum end to end latency "
					+ BestDecPoint(expectedMaxLatency) + "ms");
				}
				// do jitter analysis
				if (maxSpecifiedValue - minSpecifiedValue > expectedMaxLatency - expectedMinLatency) {
					reportSummaryWarning("Jitter of specified latency total " + BestDecPoint(minSpecifiedValue) + ".."
							+ BestDecPoint(maxSpecifiedValue) + "ms exceeds expected end to end latency jitter "
							+ BestDecPoint(expectedMinLatency) + ".." + BestDecPoint(expectedMaxLatency) + "ms");
				}
				if (maxValue - minValue > expectedMaxLatency - expectedMinLatency) {
					reportSummaryWarning("Jitter of actual latency total " + BestDecPoint(minValue) + ".."
							+ BestDecPoint(maxValue) + "ms exceeds expected end to end latency jitter "
							+ BestDecPoint(expectedMinLatency) + ".." + BestDecPoint(expectedMaxLatency) + "ms");
				}
				if ((minValue > expectedMinLatency) && (expectedMaxLatency > maxValue)) {
					reportSummaryInfo("Jitter of actual flow latency " + BestDecPoint(minValue) + ".."
							+ BestDecPoint(maxValue) + "ms is within expected end to end latency jitter "
							+ BestDecPoint(expectedMinLatency) + ".." + BestDecPoint(expectedMaxLatency) + "ms");
				}
			}
		} else {
			reportSummaryWarning("Expected end to end latency is not specified");
		}
		result.getDiagnostics().addAll(issues);
		if (ResultUtil.hasDiagnosticErrors(result.getDiagnostics())) {
			result.setResultType(ResultType.FAILURE);
		} else {
			result.setResultType(ResultType.SUCCESS);
		}

		for (LatencyContributor latencyContributor : contributors) {
			result.getSubResults().add(latencyContributor.genResult());
		}

		return result;
	}

	public Section export() {

		Section section;
		Line line;
		String sectionName;

		issues = new ArrayList<Diagnostic>();

		String systemName;
		if (relatedEndToEndFlow != null) {
			sectionName = relatedEndToEndFlow.getComponentInstancePath();
			SystemInstance si = (SystemInstance) relatedEndToEndFlow.getElementRoot();
			systemName = si.getComponentClassifier().getName();
		} else {
			sectionName = "Unnamed flow";
			systemName = "Unnamed system";
		}
		String inMode = Aadl2Util.isPrintableSOMName(som) ? " in mode " + som.getName() : "";
		String SOMMembers = Aadl2Util.getPrintableSOMMembers(som);

		section = new Section(sectionName + inMode);
		line = new Line();
		line.addHeaderContent(
				"Latency results for end-to-end flow '" + sectionName + "' of system '" + systemName + "'" + inMode
						+ SOMMembers);
		section.addLine(line);
		line = new Line();
		section.addLine(line);
		line = new Line();
		line.addHeaderContent("Result");
		line.addHeaderContent("Min Specified");
		line.addHeaderContent("Min Actual");
		line.addHeaderContent("Min Method");
		line.addHeaderContent("Max Specified");
		line.addHeaderContent("Max Actual");
		line.addHeaderContent("Max Method");
		line.addHeaderContent("Comments");
		section.addLine(line);
		// will populate the comments section

		// reporting each entry
		for (LatencyContributor lc : this.contributors) {
			for (Line l : lc.export()) {
				section.addLine(l);
			}
		}

		line = new Line();
		line.addContent("Latency Total");
		line.addContent(minSpecifiedValue + "ms");
		line.addContent(minValue + "ms");
		line.addContent("");
		line.addContent(maxSpecifiedValue + "ms");
		line.addContent(maxValue + "ms");
		line.addContent("");
		section.addLine(line);

		line = new Line();
		line.setSeverity(ReportSeverity.SUCCESS);

		line.addContent("Specified End To End Latency");
		line.addContent("");
		line.addContent(expectedMinLatency + "ms");
		line.addContent("");
		line.addContent("");
		line.addContent(expectedMaxLatency + "ms");
		line.addContent("");

		/*
		 * In that case, the end to end flow has a minimum latency
		 */
		if (expectedMaxLatency > 0) {
			if (minSpecifiedValue > expectedMaxLatency) {
				reportSummaryError("Minimum specified flow latency total " + BestDecPoint(minSpecifiedValue)
						+ "ms exceeds expected maximum latency " + BestDecPoint(expectedMaxLatency) + "ms");
			} else if (minSpecifiedValue < expectedMinLatency) {
				reportSummaryWarning("Minimum specified flow latency total " + BestDecPoint(minSpecifiedValue)
						+ "ms less than expected minimum end to end latency " + BestDecPoint(expectedMinLatency)
						+ "ms (better response time)");
			}

			if (minValue > expectedMaxLatency) {
				reportSummaryError("Minimum actual latency total " + BestDecPoint(minValue)
						+ "ms exceeds expected maximum end to end latency " + BestDecPoint(expectedMaxLatency) + "ms");
			} else if (minValue < expectedMinLatency) {
				reportSummaryWarning("Minimum actual latency total " + BestDecPoint(minValue)
						+ "ms less than expected minimum end to end latency " + BestDecPoint(expectedMinLatency)
						+ "ms (faster actual minimum response time)");
			} else {
				reportSummaryInfo("Minimum actual latency total " + BestDecPoint(minValue)
						+ "ms is greater or equal to expected minimum end to end latency "
						+ BestDecPoint(expectedMinLatency) + "ms");
			}

			if (maxValue > 0) {
				if (expectedMaxLatency < maxSpecifiedValue) {
					reportSummaryError("Maximum specified flow latency total " + BestDecPoint(maxSpecifiedValue)
							+ "ms exceeds expected maximum end to end latency " + BestDecPoint(expectedMaxLatency)
							+ "ms");
				}

				if (expectedMaxLatency < maxValue) {
					reportSummaryError("Maximum actual latency total " + BestDecPoint(maxValue)
							+ "ms exceeds expected maximum end to end latency " + BestDecPoint(expectedMaxLatency)
							+ "ms");
				} else {
					reportSummaryInfo("Maximum actual latency total " + BestDecPoint(maxValue)
							+ "ms is less or equal to expected maximum end to end latency "
							+ BestDecPoint(expectedMaxLatency) + "ms");
				}
				// do jitter analysis
				if (maxSpecifiedValue - minSpecifiedValue > expectedMaxLatency - expectedMinLatency) {
					reportSummaryWarning("Jitter of specified latency total " + BestDecPoint(minSpecifiedValue) + ".."
							+ BestDecPoint(maxSpecifiedValue) + "ms exceeds expected end to end latency jitter "
							+ BestDecPoint(expectedMinLatency) + ".." + BestDecPoint(expectedMaxLatency) + "ms");
				}
				if (maxValue - minValue > expectedMaxLatency - expectedMinLatency) {
					reportSummaryWarning("Jitter of actual latency total " + BestDecPoint(minValue) + ".."
							+ BestDecPoint(maxValue) + "ms exceeds expected end to end latency jitter "
							+ BestDecPoint(expectedMinLatency) + ".." + BestDecPoint(expectedMaxLatency) + "ms");
				}
				if ((minValue > expectedMinLatency) && (expectedMaxLatency > maxValue)) {
					reportSummaryInfo("Jitter of actual flow latency " + BestDecPoint(minValue) + ".."
							+ BestDecPoint(maxValue) + "ms is within expected end to end latency jitter "
							+ BestDecPoint(expectedMinLatency) + ".." + BestDecPoint(expectedMaxLatency) + "ms");
				}
			}
		} else {
			reportSummaryWarning("Expected end to end latency is not specified");
		}

		section.addLine(line);

		if (issues.size() > 0) {
			line = new Line();
			line.addHeaderContent("End to end Latency Summary");
			section.addLine(line);
			for (Diagnostic issue : issues) {
				line = new Line();
				String msg = issue.getMessage();
				ReportedCell issueLabel = new ReportedCell(issue.getDiagnosticType(),
						issue.getDiagnosticType().toString());
				line.addCell(issueLabel);
				line.addContent(msg);
				section.addLine(line);
			}
		}

		return section;
	}

}