AbstractResourceAnalysis.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.resource.budgets.logic;

import java.util.Iterator;

import org.eclipse.emf.common.util.EList;
import org.osate.aadl2.ComponentCategory;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.contrib.aadlproject.SizeUnits;
import org.osate.aadl2.contrib.aadlproject.TimeUnits;
import org.osate.aadl2.contrib.timing.TimingProperties;
import org.osate.aadl2.contrib.util.AadlContribUtils;
import org.osate.aadl2.instance.ComponentInstance;
import org.osate.aadl2.instance.FeatureCategory;
import org.osate.aadl2.instance.FeatureInstance;
import org.osate.aadl2.instance.SystemInstance;
import org.osate.aadl2.instance.SystemOperationMode;
import org.osate.contribution.sei.sei.Instructionvolumeunits;
import org.osate.contribution.sei.sei.ProcessorSpeedUnits;
import org.osate.contribution.sei.sei.Sei;
import org.osate.pluginsupport.properties.PropertyUtils;
import org.osate.pluginsupport.properties.RealRange;
import org.osate.ui.handlers.AbstractAaxlHandler;
import org.osate.xtext.aadl2.properties.util.InstanceModelUtil;

abstract class AbstractResourceAnalysis extends AbstractLoggingAnalysis {
	private final String prefixSymbol = "  ";

	protected int components = 0;
	protected int budgetedComponents = 0;

	protected AbstractResourceAnalysis(AbstractAaxlHandler handler) {
		super(handler);
	}

	protected enum ResourceKind {
		MIPS, RAM, ROM, Memory
	}

	/**
	 * calculate the budget of components with budgets, i.e., application
	 * components and devices For application components they are required,
	 * while for devices they are optional
	 *
	 * @param ci component instance whose subtree is to be added up
	 * @param rk Property Definition of property to be added
	 * @param unit Unit in which the property value should be retrieved
	 * @param somName String name of SOM (used in reporting)
	 * @return double total, zero, if no budget, -1 if hardware only in
	 *         substructure
	 */
	protected double sumBudgets(ComponentInstance ci, ResourceKind rk, final SystemOperationMode som,
			String prefix) {
		if (!ci.isActive(som)) {
			return 0.0;
		}
		double subtotal = 0.0;
		EList<ComponentInstance> subcis = ci.getComponentInstances();
		boolean HWOnly = false;
		boolean isSystemInstance = ci instanceof SystemInstance;
		int subbudgetcount = 0;
		int subcount = 0;
		if (subcis.size() == 0) {
			if (isHardware(ci)) {
				return -1;
			}
		} else {
			// track HWonly if subcomponents
			HWOnly = true;
		}
		for (Iterator<ComponentInstance> it = subcis.iterator(); it.hasNext();) {
			ComponentInstance subci = it.next();
			double subresult = sumBudgets(subci, rk, som, isSystemInstance ? "" : prefix + prefixSymbol);
			if (subresult >= 0) {
				HWOnly = false;
				subtotal += subresult;
				if (subci.getCategory() == ComponentCategory.DEVICE) {
					if (subresult > 0) {
						// only count device if it has a budget
						subcount++;
						subbudgetcount++;
					}
				} else {
					// track how many non-devices and whether they have a budget
					subcount++;
					if (subresult > 0) {
						subbudgetcount++;
					}
				}
			}
		}
		if (HWOnly) {
			return -1;
		}

		final Enum<?> budgetUnit = rk == ResourceKind.MIPS ? ProcessorSpeedUnits.MIPS : SizeUnits.KBYTE;
		double budget = getBudget(ci, rk);
		if (rk.equals(ResourceKind.RAM) || rk.equals(ResourceKind.ROM) || rk.equals(ResourceKind.Memory)) {
			double actualsize = getMemoryUseActual(ci, rk.name(), SizeUnits.KBYTE);
			subtotal += actualsize;
		}
		String resourceName = ci.getCategory().getName();
		String notes = "";
		if (rk == ResourceKind.MIPS && ci.getCategory() == ComponentCategory.THREAD) {
			subtotal = getThreadExecutioninMIPS(ci);
		}
		components = components + subcount;
		budgetedComponents = budgetedComponents + subbudgetcount;
		if (budget > 0 && subtotal > budget) {
			notes = String.format("Error: subtotal/actual exceeds budget %.3f by %.3f " + budgetUnit.name(), budget,
					(subtotal - budget));
		} else if (budget > 0 && subtotal < budget) {
			notes = String.format(
					resourceName + " " + ci.getInstanceObjectPath() + " total %.3f " + budgetUnit.name()
							+ " below budget %.3f " + budgetUnit.name() + " (%.1f %% slack)",
					subtotal, budget, (budget - subtotal) / budget * 100);
		}
		if (!isSystemInstance) {
			detailedLog(prefix, ci, budget, subtotal, resourceName, budgetUnit, notes);
		}
		return subtotal == 0 ? budget : subtotal;
	}

	protected double getMemoryUseActual(final ComponentInstance bci, final String resourceName, final SizeUnits unit) {
		double actualsize = 0.0;
		if (resourceName.equals("ROM")) {
			actualsize = getCodeSize(bci, unit);
		} else if (resourceName.equals("RAM")) {
			actualsize = AadlContribUtils.getDataSize(bci, unit);
			actualsize += getHeapSize(bci, unit);
			actualsize += getStackSize(bci, unit);
		} else {
			actualsize = AadlContribUtils.getDataSize(bci, unit);
			actualsize += getHeapSize(bci, unit);
			actualsize += getStackSize(bci, unit);
			actualsize += getCodeSize(bci, unit);
		}
		return actualsize;
	}

	private double getBudget(NamedElement ne, ResourceKind kind) {
		switch (kind) {
		case MIPS:
			return PropertyUtils.getScaled(Sei::getMipsbudget, ne, ProcessorSpeedUnits.MIPS).orElse(0.0);
		case RAM:
			return PropertyUtils.getScaled(Sei::getRambudget, ne, SizeUnits.KBYTE).orElse(0.0);
		case ROM:
			return PropertyUtils.getScaled(Sei::getRombudget, ne, SizeUnits.KBYTE).orElse(0.0);
		case Memory:
			return PropertyUtils.getScaled(Sei::getRambudget, ne, SizeUnits.KBYTE).orElse(0.0)
					+ PropertyUtils.getScaled(Sei::getRombudget, ne, SizeUnits.KBYTE).orElse(0.0);
		}
		return 0.0;
	}

	private boolean isHardware(ComponentInstance ci) {
		ComponentCategory cat = ci.getCategory();
		if (cat == ComponentCategory.BUS || cat == ComponentCategory.PROCESSOR
				|| cat == ComponentCategory.VIRTUAL_PROCESSOR || cat == ComponentCategory.MEMORY) {
			return true;
		}
		if (cat == ComponentCategory.SYSTEM || cat == ComponentCategory.DEVICE) {
			EList<FeatureInstance> el = ci.getFeatureInstances();
			for (Iterator<FeatureInstance> it = el.iterator(); it.hasNext();) {
				FeatureInstance fi = it.next();
				if (fi.getCategory() != FeatureCategory.BUS_ACCESS) {
					return false;
				}
			}
			return true;
		}
		return false;
	}

	protected static double getHeapSize(final NamedElement ne, final SizeUnits unit) {
		return org.osate.pluginsupport.properties.PropertyUtils
				.getScaled(org.osate.aadl2.contrib.memory.MemoryProperties::getHeapSize, ne, unit)
				.orElseGet(() -> org.osate.pluginsupport.properties.PropertyUtils
						.getScaled(org.osate.aadl2.contrib.memory.MemoryProperties::getSourceHeapSize, ne, unit)
						.orElse(0.0));
	}

	protected static double getStackSize(final NamedElement ne, final SizeUnits unit) {
		return org.osate.pluginsupport.properties.PropertyUtils
				.getScaled(org.osate.aadl2.contrib.memory.MemoryProperties::getStackSize, ne, unit)
				.orElseGet(() -> org.osate.pluginsupport.properties.PropertyUtils
						.getScaled(org.osate.aadl2.contrib.memory.MemoryProperties::getSourceStackSize, ne, unit)
						.orElse(0.0));
	}

	protected static double getCodeSize(final NamedElement ne, final SizeUnits unit) {
		return org.osate.pluginsupport.properties.PropertyUtils
				.getScaled(org.osate.aadl2.contrib.memory.MemoryProperties::getCodeSize, ne, unit)
				.orElseGet(() -> org.osate.pluginsupport.properties.PropertyUtils
						.getScaled(org.osate.aadl2.contrib.memory.MemoryProperties::getSourceCodeSize, ne, unit)
						.orElse(0.0));
	}

	protected static double getThreadExecutioninMIPS(ComponentInstance threadinstance) {
		if (!InstanceModelUtil.isThread(threadinstance)) {
			return 0;
		}
		double mips = getThreadExecutionIPDinMIPS(threadinstance);
		if (mips == 0) {
			double period = PropertyUtils.getScaled(TimingProperties::getPeriod, threadinstance, TimeUnits.SEC)
					.orElse(0.0);
			double exectimeval = PropertyUtils
					.getScaledRange(TimingProperties::getComputeExecutionTime, threadinstance, TimeUnits.SEC)
					.orElse(RealRange.ZEROED).getMaximum();
			if (exectimeval > 0 && period > 0) {
				final ComponentInstance thread = threadinstance;
				double mipspersec = TimingProperties.getReferenceProcessor(thread).map(pci -> getMIPSCapacityInMIPS(pci, 0.0)).orElse(0.0);
				if (mipspersec == 0) {
					mipspersec = getBoundPhysicalProcessorMIPS(threadinstance);
				}
				double time = exectimeval / period;
				mips = time * mipspersec;
			}
		}
		return mips;
	}

	protected static double getThreadExecutionIPDinMIPS(final ComponentInstance threadinstance) {
		final double period = PropertyUtils.getScaled(TimingProperties::getPeriod, threadinstance, TimeUnits.SEC)
				.orElse(0.0);
		final double mipd = PropertyUtils
				.getScaledRange(Sei::getInstructionsperdispatch, threadinstance, Instructionvolumeunits.MIPD)
				.orElse(RealRange.ZEROED).getMaximum();
		return (mipd > 0 && period > 0) ? mipd / period : 0.0;
	}

	protected static double getBoundPhysicalProcessorMIPS(final ComponentInstance thread) {
		final Iterator<ComponentInstance> pcis = InstanceModelUtil.getBoundPhysicalProcessors(thread).iterator();
		return pcis.hasNext() ? getMIPSCapacityInMIPS(pcis.next(), 0.0) : 0.0;
	}

	protected static double getMIPSCapacityInMIPS(final NamedElement ne, final double defaultValue) {
		return PropertyUtils.getScaled(Sei::getMipscapacity, ne, ProcessorSpeedUnits.MIPS).orElseGet(
				() -> PropertyUtils.getScaled(
						TimingProperties::getProcessorCapacity, ne,
						org.osate.aadl2.contrib.aadlproject.ProcessorSpeedUnits.MIPS).
						orElse(defaultValue));
	}

}