RuntimeProcessWalker.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.scheduling;

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

import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.osate.aadl2.ComponentCategory;
import org.osate.aadl2.Element;
import org.osate.aadl2.contrib.aadlproject.TimeUnits;
import org.osate.aadl2.contrib.timing.TimingProperties;
import org.osate.aadl2.instance.ComponentInstance;
import org.osate.aadl2.properties.PropertyNotPresentException;
import org.osate.analysis.resource.management.handlers.Schedule;
import org.osate.pluginsupport.properties.PropertyUtils;
import org.osate.xtext.aadl2.properties.util.GetProperties;
import org.osate.xtext.aadl2.properties.util.InstanceModelUtil;

public class RuntimeProcessWalker {
	// to record the invariants visisted and put back to the system tree.
	private static List<RuntimeProcess> runTimeComponents = new ArrayList<RuntimeProcess>();
	private static int ARCID = 0;

	// since the current timing schedulability analysis is based on per-processor.
	// I need to construct the runtimeComponents based on the identical processor
	// binding.
	private static ComponentInstance currentProcessor;

	final Schedule scheduleAction;

	/**
	 * Schedule Action is the action extended from AbstractAaxlAction.
	 * It makes the reporting methods available.
	 * @param scheduleAction
	 */
	public RuntimeProcessWalker(final Schedule scheduleAction) {
		this.scheduleAction = scheduleAction;
	}

	public void setCurrentProcessor(ComponentInstance processor) {
		currentProcessor = processor;
	}

	public ComponentInstance getCurrentProcessor() {
		return currentProcessor;
	}

	// since the run time component holder is a static vector, you have to clear it regularly
	// for every new processor analysis.
	public void cleanProcessHolder() {
		runTimeComponents.clear();
	}

	/**
	 * @since 2.0
	 */
	public List<RuntimeProcess> getRunTimeComponents() {
		return runTimeComponents;
	};

	public void initWalker() {
		Element root = currentProcessor.getSystemInstance();
		TreeIterator<Element> ciit = EcoreUtil.getAllContents(Collections.singleton(root));
		while (ciit.hasNext()) {
			Object o = ciit.next();
			if (o instanceof ComponentInstance && ((ComponentInstance) o).getCategory() == ComponentCategory.THREAD) {
				addThread((ComponentInstance) o);
			}
		}
	}

	/**
	 * add thread if it is bound to the processor set in processorName
	 * @param elt
	 */
	public void addThread(ComponentInstance elt) {
		double exectimeval;
		try {
			exectimeval = GetProperties.getThreadExecutioninMilliSec(elt);
		} catch (PropertyNotPresentException e) {
			scheduleAction.error(elt, elt.getComponentInstancePath() + ": Execution time is not set");
			return;
		}
		if (!InstanceModelUtil.isBoundToProcessor(elt, currentProcessor)) {
			return;
		}

		double val;
		try {
			val = GetProperties.getPeriodinMS(elt);
		} catch (PropertyNotPresentException e) {
			scheduleAction.error(elt, elt.getComponentInstancePath() + ": Period is not set");
			return;
		}

		double deadlineval = PropertyUtils.getScaled(TimingProperties::getDeadline, elt, TimeUnits.MS).orElse(0.0);
		RuntimeProcess curComponent = new RuntimeProcess();
		curComponent.setProcessorName(currentProcessor.getInstanceObjectPath());
		// convert time into MicroSeconds so it does not get rounded down
		curComponent.setPeriod((int) (val * 1000));
		curComponent.setDeadline((int) (deadlineval * 1000));
		curComponent.setExecutionTime((int) (exectimeval * 1000));

		curComponent.setPhaseOffset(0);

		/* There is no standard Priority property */
		long priority = GetProperties.getPriority(elt, 0);
		curComponent.setPriority((int) priority);

		curComponent.setComponentName(elt.getInstanceObjectPath());

		// up to this point, all timing properties are all set!
		curComponent.setAssociatedComponent(elt);
		runTimeComponents.add(curComponent);

		return;
	}

	public void componentsSortByPeriod() {
		if (runTimeComponents.size() == 0) {
			return;
		}
		Collections.sort(runTimeComponents, (p1, p2) -> p1.getPeriod() - p2.getPeriod());
	}

	public void assignPriority() {
		if (runTimeComponents.size() == 0) {
			return;
		}
		int prior = runTimeComponents.size();
		for (RuntimeProcess curComponent : runTimeComponents) {
			if (curComponent.getPriority() == 0) {
				curComponent.setPriority(prior--);
			}
		}
	}

	private int getARCID(RuntimeProcess comp) {
		int result = -1;
		for (int i = 0; i < runTimeComponents.size(); i++) {
			RuntimeProcess curComponent = runTimeComponents.get(i);
			if (curComponent.getARCName() != null && comp.getARCName() != null) {
				if (curComponent.getARCName().equals(comp.getARCName())) {
					if (curComponent.getARCID() >= 0) {
						result = curComponent.getARCID();
						break;
					}
				}
			}
		}
		return result;

	}

	/**generate the analysis result. and at the same time, report the analysis result
	 * into a file.
	 */
	public boolean timingSchedualabilityAnalysis() {
		// no process bounded to this process, so it is true. Of course.
		if (runTimeComponents.size() == 0) {
			return true;
		}

		// numbering the ARC ID for all the schedulable component in the system.
		for (int i = 0; i < runTimeComponents.size(); i++) {
			RuntimeProcess curComponent = runTimeComponents.get(i);
			// construct ARCID.
			if (curComponent.getARCName() == null) {
				// no ARC name associated, it is a normal component.
				curComponent.setARCID(ARCID++);
			} else {
				// it is associated with ARC component
				int id = getARCID(curComponent);
				if (id >= 0) {
					curComponent.setARCID(id);
				} else {
					curComponent.setARCID(ARCID++);
				}
			}
		}

		// the timing analysis is resided in SimplexTiming class
		SimplexTiming analysis = new SimplexTiming();
		// only the tractable analysis is useful in terms of computation complexity.
		analysis.setExactOrTractable(true);

		for (int i = 0; i < runTimeComponents.size(); i++) {
			RuntimeProcess curComponent = runTimeComponents.get(i);
			analysis.addProcessToList(curComponent, curComponent.getARCID());
		}

		boolean result = analysis.schedulabilityAnalysis();

		double totaltime = 0;
		for (int i = 0; i < runTimeComponents.size(); i++) {
			RuntimeProcess curComponent = runTimeComponents.get(i);
			totaltime += curComponent.getExecutionTime() * 1000 / curComponent.getPeriod();
		}

		scheduleAction.logInfo("Schedulability Results");
		if (result) {
			scheduleAction.info(getCurrentProcessor(), "Processor " + getCurrentProcessor().getInstanceObjectPath()
					+ " is schedulable with utilization " + totaltime / 10 + "%");
		} else {
			scheduleAction.error(getCurrentProcessor(), "Processor " + getCurrentProcessor().getInstanceObjectPath()
					+ " is not schedulable with utilization " + totaltime / 10 + "%");
		}
		scheduleAction.logInfo(
				"thread name, period, deadline, execution time, phase offset, priority, max response time, schedulability ");

		for (int i = 0; i < runTimeComponents.size(); i++) {
			RuntimeProcess curComponent = runTimeComponents.get(i);
			scheduleAction.logInfo(curComponent.getComponentName() + ", " + curComponent.getPeriod() + ", "
					+ curComponent.getDeadline() + ", " + curComponent.getExecutionTime() + ", "
					+ curComponent.getPhaseOffset() + ", " + curComponent.getPriority() + ", "
					+ curComponent.getMaxResponseTime() + ", " + curComponent.getSchedulability());
		}
		scheduleAction.logInfo("");
		scheduleAction.logInfo("");

		// clean the ARC vector holder.
		analysis.cleanARCList();

		return result;

	}

}