BoundResourceAnalysis.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.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.osate.aadl2.ComponentCategory;
import org.osate.aadl2.Element;
import org.osate.aadl2.contrib.aadlproject.SizeUnits;
import org.osate.aadl2.contrib.deployment.DeploymentProperties;
import org.osate.aadl2.contrib.memory.MemoryProperties;
import org.osate.aadl2.instance.ComponentInstance;
import org.osate.aadl2.instance.InstanceObject;
import org.osate.aadl2.instance.SystemInstance;
import org.osate.aadl2.instance.SystemOperationMode;
import org.osate.aadl2.modelsupport.modeltraversal.ForAllElement;
import org.osate.aadl2.modelsupport.modeltraversal.SOMIterator;
import org.osate.aadl2.properties.PropertyDoesNotApplyToHolderException;
import org.osate.aadl2.util.Aadl2Util;
import org.osate.contribution.sei.sei.ProcessorSpeedUnits;
import org.osate.contribution.sei.sei.Sei;
import org.osate.pluginsupport.properties.PropertyUtils;
import org.osate.ui.dialogs.Dialog;
import org.osate.ui.handlers.AbstractAaxlHandler;
import org.osate.xtext.aadl2.properties.util.InstanceModelUtil;
//TODO-LW: assumes connection ends are features
/**
* @since 2.0
*/
public class BoundResourceAnalysis extends AbstractResourceAnalysis {
private final String actionName;
private int count = 0;
public BoundResourceAnalysis(final String actionName, final AbstractAaxlHandler errManager) {
super(errManager);
this.actionName = actionName;
}
public void analysisBody(final IProgressMonitor monitor, final Element obj) {
if (obj instanceof InstanceObject) {
SystemInstance root = ((InstanceObject) obj).getSystemInstance();
monitor.beginTask(actionName, IProgressMonitor.UNKNOWN);
final SOMIterator soms = new SOMIterator(root);
while (soms.hasNext()) {
final SystemOperationMode som = soms.nextSOM();
checkProcessorLoads(root, som);
checkVirtualProcessorLoads(root, som);
checkMemoryLoads(root, som);
}
monitor.done();
} else {
Dialog.showError("Bound Resource Analysis Error", "Can only check system instances");
}
}
private void checkProcessorLoads(SystemInstance si, final SystemOperationMode som) {
count = 0;
ForAllElement countme = new ForAllElement() {
@Override
protected void process(Element obj) {
count = count + 1;
}
};
countme.processPreOrderComponentInstance(si, ComponentCategory.PROCESSOR);
if (count > 0) {
errManager.infoSummaryReportOnly(si, null,
"\nProcessor Summary Report: " + Aadl2Util.getPrintableSOMName(som));
} else {
errManager.infoSummaryReportOnly(si, null,
"\nProcessor Summary Report: " + Aadl2Util.getPrintableSOMName(som) + "\n ** No processors");
}
ForAllElement mal = new ForAllElement() {
@Override
protected void process(Element obj) {
checkProcessorLoad((ComponentInstance) obj, som);
}
};
mal.processPreOrderComponentInstance(si, ComponentCategory.PROCESSOR);
}
private void checkVirtualProcessorLoads(SystemInstance si, final SystemOperationMode som) {
count = 0;
ForAllElement countme = new ForAllElement() {
@Override
protected void process(Element obj) {
count = count + 1;
}
};
countme.processPreOrderComponentInstance(si, ComponentCategory.VIRTUAL_PROCESSOR);
if (count > 0) {
errManager.infoSummaryReportOnly(si, null,
"\nVirtual Processor Summary Report: " + Aadl2Util.getPrintableSOMName(som));
}
ForAllElement mal = new ForAllElement() {
@Override
protected void process(Element obj) {
checkProcessorLoad((ComponentInstance) obj, som);
}
};
mal.processPreOrderComponentInstance(si, ComponentCategory.VIRTUAL_PROCESSOR);
}
/**
* check the load from components bound to the given processor The
* components can be threads or higher level components.
*
* @param curProcessor Component Instance of processor
*/
private void checkProcessorLoad(ComponentInstance curProcessor, final SystemOperationMode som) {
if (!curProcessor.isActive(som)) {
return;
}
double MIPScapacity = getMIPSCapacityInMIPS(curProcessor, 0.0);
if (MIPScapacity == 0 && InstanceModelUtil.isVirtualProcessor(curProcessor)) {
MIPScapacity = PropertyUtils.getScaled(Sei::getMipsbudget, curProcessor, ProcessorSpeedUnits.MIPS)
.orElse(0.0);
}
EList<ComponentInstance> boundComponents = InstanceModelUtil.getBoundSWComponents(curProcessor);
if (boundComponents.size() == 0 && MIPScapacity > 0) {
errManager.infoSummary(curProcessor, som.getName(),
"No application components bound to " + curProcessor.getComponentInstancePath()
+ " with MIPS capacity " + toStringScaled(MIPScapacity, ProcessorSpeedUnits.MIPS));
return;
}
if (MIPScapacity == 0 && InstanceModelUtil.isVirtualProcessor(curProcessor)) {
errManager.warningSummary(curProcessor, som.getName(), "Virtual processor "
+ curProcessor.getComponentInstancePath() + " has no MIPS capacity or budget.");
return;
}
if (MIPScapacity == 0 && InstanceModelUtil.isProcessor(curProcessor)) {
errManager.errorSummary(curProcessor, som.getName(), "Processor " + curProcessor.getComponentInstancePath()
+ " has no MIPS capacity but has bound components.");
}
if (InstanceModelUtil.isVirtualProcessor(curProcessor)) {
logHeader("\n\nDetailed Workload Report: " + Aadl2Util.getPrintableSOMName(som) + " for Virtual Processor "
+ curProcessor.getComponentInstancePath() + " with Capacity "
+ toStringScaled(MIPScapacity, ProcessorSpeedUnits.MIPS) + "\n\nComponent,Budget,Actual");
} else {
logHeader("\n\nDetailed Workload Report: " + Aadl2Util.getPrintableSOMName(som) + " for Processor "
+ curProcessor.getComponentInstancePath() + " with Capacity "
+ toStringScaled(MIPScapacity, ProcessorSpeedUnits.MIPS) + "\n\nComponent,Budget,Actual");
}
double totalMIPS = 0.0;
for (Iterator<ComponentInstance> it = boundComponents.iterator(); it.hasNext();) {
ComponentInstance bci = it.next();
double actualmips = sumBudgets(bci, ResourceKind.MIPS, som, "");
if (actualmips > 0) {
totalMIPS += actualmips;
}
}
logHeader("Total,," + toStringScaled(totalMIPS, ProcessorSpeedUnits.MIPS));
if (totalMIPS > MIPScapacity) {
errManager.errorSummary(curProcessor, null,
" Processor " + curProcessor.getComponentInstancePath() + ": Total MIPS "
+ toStringScaled(totalMIPS, ProcessorSpeedUnits.MIPS)
+ " of bound tasks exceeds MIPS capacity "
+ toStringScaled(MIPScapacity, ProcessorSpeedUnits.MIPS));
} else if (totalMIPS == 0.0) {
errManager.warningSummary(curProcessor, null,
" Processor " + curProcessor.getComponentInstancePath() + ": Bound app's have no MIPS budget.");
} else {
errManager.infoSummary(curProcessor, null,
" Processor " + curProcessor.getComponentInstancePath() + ": Total MIPS "
+ toStringScaled(totalMIPS, ProcessorSpeedUnits.MIPS) + " of bound tasks within "
+ "MIPS capacity " + toStringScaled(MIPScapacity, ProcessorSpeedUnits.MIPS) + " of "
+ curProcessor.getComponentInstancePath());
}
}
private static boolean canHaveMemory(Element e) {
if (e instanceof ComponentInstance) {
final ComponentInstance ci = (ComponentInstance) e;
final ComponentCategory cc = ci.getCategory();
// memory, system, processor, virtual processor, abstract
return cc == ComponentCategory.MEMORY || cc == ComponentCategory.SYSTEM || cc == ComponentCategory.PROCESSOR
|| cc == ComponentCategory.VIRTUAL_PROCESSOR || cc == ComponentCategory.ABSTRACT;
} else {
return false;
}
}
private void checkMemoryLoads(SystemInstance si, final SystemOperationMode som) {
count = 0;
ForAllElement countme = new ForAllElement() {
@Override
protected void process(Element obj) {
if (canHaveMemory(obj)) {
if (getROMCapacityInKB(obj) > 0.0
|| getRAMCapacityInKB(obj) > 0.0
|| getMemorySize(obj) > 0.0) {
count = count + 1;
}
}
}
};
countme.processPreOrderComponentInstance(si);
if (count > 0) {
errManager.infoSummaryReportOnly(si, null,
"\nMemory Summary Report: " + Aadl2Util.getPrintableSOMName(som));
} else {
errManager.infoSummaryReportOnly(si, null, "\nMemory Summary Report: " + Aadl2Util.getPrintableSOMName(som)
+ "\n No Memory with Memory_Size or RAMCapacity or ROMCapacity");
}
ForAllElement mal = new ForAllElement() {
@Override
protected void process(Element obj) {
if (canHaveMemory(obj)) {
checkMemoryLoad((ComponentInstance) obj, som);
}
}
};
mal.processPreOrderComponentInstance(si);
}
/**
* check the load from components bound to the given memory The components
* can be threads or higher level components.
*
* @param curMemory Component Instance of memory
*/
private void checkMemoryLoad(ComponentInstance curMemory, final SystemOperationMode som) {
EList<ComponentInstance> boundComponents = getMemoryBindings(curMemory);
final double MemoryCapacity = getMemorySize(curMemory);
final double ROMCapacity = getROMCapacityInKB(curMemory);
final double RAMCapacity = getRAMCapacityInKB(curMemory);
if (MemoryCapacity > 0.0) {
doMemoryLoad(curMemory, som, MemoryCapacity, boundComponents, ResourceKind.Memory); // Memory
}
if (RAMCapacity > 0.0) {
doMemoryLoad(curMemory, som, RAMCapacity, boundComponents, ResourceKind.RAM); // RAM
}
if (ROMCapacity > 0.0) {
doMemoryLoad(curMemory, som, ROMCapacity, boundComponents, ResourceKind.ROM); // ROM
}
}
/**
* check the load from components bound to the given memory The components
* can be threads or higher level components.
*
* @param curMemory Component Instance of memory
*/
private void doMemoryLoad(ComponentInstance curMemory, final SystemOperationMode som, double Memorycapacity,
EList<ComponentInstance> boundComponents, ResourceKind rk) {
double totalMemory = 0.0;
String somName = null;
String resourceName = rk.name();
boolean isROM = rk.equals(ResourceKind.ROM);
if (boundComponents.size() == 0 && Memorycapacity > 0) {
errManager.infoSummary(curMemory, somName,
" No application components bound to " + curMemory.getComponentInstancePath() + " with "
+ resourceName + " capacity " + toStringScaled(Memorycapacity, SizeUnits.KBYTE));
return;
}
if (Memorycapacity == 0) {
errManager.errorSummary(curMemory, somName, " Memory " + curMemory.getComponentInstancePath() + " has no "
+ resourceName + " capacity but has bound components.");
}
logHeader("\n\nDetailed Workload Report: " + Aadl2Util.getPrintableSOMName(som) + " for memory "
+ curMemory.getComponentInstancePath() + " with Capacity "
+ toStringScaled(Memorycapacity, SizeUnits.KBYTE) + "\n\nComponent,Budget,Actual");
Set<ComponentInstance> budgeted = new HashSet<>();
for (ComponentInstance bci : boundComponents) {
String notes = "";
double totalactual = sumMemoryActualPropertyValue(bci, isROM);
double budget = isROM ? getROMBudgetInKB(bci) : getRAMBudgetInKB(bci);
double actualsize = getMemoryUseActual(bci, resourceName, SizeUnits.KBYTE);
if (actualsize > 0) {
// only compare if there were actuals
if (budget > 0 && actualsize > budget) {
notes = ",Error: " + resourceName + " subtotal exceeds budget by " + (actualsize - budget) + " KB";
} else if (actualsize < budget) {
notes = ",Warning: " + resourceName + " Subtotal under budget by " + (budget - actualsize) + " KB";
}
if (totalactual > 0 && totalactual != actualsize) {
notes = notes + ",Warning: " + resourceName + " Data_Size differs from RAM/ROMActual";
}
}
if (totalactual > 0) {
// only compare if there were actuals
if (budget > 0 && totalactual > budget) {
notes = notes + ",Error: " + resourceName + " subtotal exceeds budget by " + (totalactual - budget)
+ " KB";
} else if (totalactual < budget) {
notes = notes + ",Warning: " + resourceName + " Subtotal under budget by " + (budget - totalactual)
+ " KB";
}
}
if (totalactual == 0.0 && actualsize == 0.0) {
// we use a budget number as there are no actuals
if (budget > 0 && !budgeted.contains(bci)) {
// only add it if no children budget numbers have been added
totalMemory += budget;
detailedLog(bci, budget, budget, "No actual. Added budget to total.");
// add ancestors to budgeted list so their budget does not get added later
while ((bci = bci.getContainingComponentInstance()) != null) {
budgeted.add(bci);
}
}
} else {
if (actualsize > 0) {
detailedLog(bci, budget, actualsize, notes);
totalMemory += actualsize;
} else {
// add only the current actual; the children actual have been added before
double currentActual = isROM ? getROMActualInKB(bci) : getRAMActualInKB(bci);
detailedLog(bci, budget, currentActual, notes);
totalMemory += currentActual;
}
}
}
detailedLogTotal2(null, totalMemory, SizeUnits.KBYTE);
if (Memorycapacity == 0) {
errManager.errorSummary(curMemory, somName,
" " + resourceName + curMemory.getComponentInstancePath() + " has no memory capacity specified");
}
if (totalMemory > Memorycapacity) {
errManager.errorSummary(curMemory, somName,
" Total Memory " + totalMemory + " KB of bounds tasks exceeds Memory capacity " + Memorycapacity
+ " KB of " + curMemory.getComponentInstancePath());
} else if (totalMemory == 0.0 && Memorycapacity == 0.0) {
errManager.warningSummary(curMemory, somName, " " + resourceName + curMemory.getComponentInstancePath()
+ " has no capacity. Bound app's have no memory budget.");
} else {
errManager.infoSummary(curMemory, somName,
" Total " + resourceName + " " + totalMemory + " KB of bound tasks within Memory capacity "
+ Memorycapacity + " KB of " + curMemory.getComponentInstancePath());
}
}
private double sumMemoryActualPropertyValue(ComponentInstance ci, boolean isROM) {
try {
double total = isROM ? getROMActualInKB(ci) : getRAMActualInKB(ci);
EList<ComponentInstance> subcis = ci.getComponentInstances();
for (Iterator<ComponentInstance> it = subcis.iterator(); it.hasNext();) {
ComponentInstance subci = it.next();
total += sumMemoryActualPropertyValue(subci, isROM);
}
return total;
} catch (PropertyDoesNotApplyToHolderException e) {
/*
* Callers are allowed to be sloppy and not care if the property
* actually applies to the category of the component instance 'ci'
*/
return 0.0;
}
}
/*
* Issue 2169: This is copied from InstanceModelUtil.getBoundSWComponents, but we had
* to specialize it because now processor/virtual processors are treated as things
* that have memory and the original method gets the wrong things from the processors.
*
* associatedObject is of category memory, system, processor, virtual processor, abstract
*/
private static EList<ComponentInstance> getMemoryBindings(final ComponentInstance associatedObject) {
EList<Element> boundComponents = null;
final SystemInstance root = associatedObject.getSystemInstance();
final ComponentCategory cc = associatedObject.getCategory();
if (cc == ComponentCategory.MEMORY || cc == ComponentCategory.SYSTEM || cc == ComponentCategory.PROCESSOR
|| cc == ComponentCategory.VIRTUAL_PROCESSOR || cc == ComponentCategory.ABSTRACT) {
boundComponents = new ForAllElement() {
@Override
protected boolean suchThat(Element obj) {
final List<InstanceObject> boundMemoryList = DeploymentProperties
.getActualMemoryBinding((ComponentInstance) obj).orElse(Collections.emptyList());
if (boundMemoryList.isEmpty()) {
return false;
}
return boundMemoryList.contains(associatedObject);
}
// process bottom up so we can check whether children had budgets
}.processPostOrderComponentInstance(root);
} else {
return new BasicEList<ComponentInstance>();
}
EList<ComponentInstance> topobjects = new BasicEList<ComponentInstance>();
for (Object componentInstance : boundComponents) {
InstanceModelUtil.addAsRoot(topobjects, (ComponentInstance) componentInstance);
}
return topobjects;
}
private static double getROMCapacityInKB(Element obj) {
return PropertyUtils.getScaled(Sei::getRomcapacity, (InstanceObject) obj, SizeUnits.KBYTE).orElse(0.0);
}
private static double getRAMCapacityInKB(Element obj) {
return PropertyUtils.getScaled(Sei::getRamcapacity, (InstanceObject) obj, SizeUnits.KBYTE).orElse(0.0);
}
private static double getROMBudgetInKB(Element obj) {
return PropertyUtils.getScaled(Sei::getRombudget, (InstanceObject) obj, SizeUnits.KBYTE).orElse(0.0);
}
private static double getRAMBudgetInKB(Element obj) {
return PropertyUtils.getScaled(Sei::getRambudget, (InstanceObject) obj, SizeUnits.KBYTE).orElse(0.0);
}
private static double getROMActualInKB(Element obj) {
return PropertyUtils.getScaled(Sei::getRomactual, (InstanceObject) obj, SizeUnits.KBYTE).orElse(0.0);
}
private static double getRAMActualInKB(Element obj) {
return PropertyUtils.getScaled(Sei::getRamactual, (InstanceObject) obj, SizeUnits.KBYTE).orElse(0.0);
}
private static double getMemorySize(Element obj) {
return PropertyUtils.getScaled(MemoryProperties::getMemorySize, (InstanceObject) obj, SizeUnits.KBYTE)
.orElse(0.0);
}
private static String toStringScaled(double d, Enum<?> u) {
return String.format("%.3f " + u.name(), d);
}
}