Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Latency analysis does not interpret execution time on feature (entrypoint) of thread #1940

Closed
reteprelief opened this issue Aug 8, 2019 · 13 comments · Fixed by #2127
Closed
Assignees
Labels
Milestone

Comments

@reteprelief
Copy link
Contributor

AADL allows users to specify compute execution time for specific features, e.g., specific ports.
Similarly the execution time of a subprogram should be used as value for remote subprogram calls.

@reteprelief
Copy link
Contributor Author

reteprelief commented Dec 9, 2019

The compute execution time property on a port indicates the compute execution time for a thread when dispatched on a particular port. In other words, a (aperiodic, sporadic, hybrid) thread could be dispatched through different ports and each dispatch could result in a different compute execution time. Note that for periodic threads only the compute execution time of the thread should be used.

For latency analysis we have a flow through ports. If an incoming port of the flow has a compute execution time property then its value should be used instead of the value associated with the thread.

For an example just use an existing latency test example and add the compute execution time property to a port involved in a flow when the thread is not periodic.

@lwrage lwrage assigned AaronGreenhouse and unassigned lwrage Dec 9, 2019
@AaronGreenhouse
Copy link
Contributor

This change is conceptually simple: when getting the execution time of thread, if the thread is periodic, use the period property value. Otherwise, try to get the compute_execution_time on the port.

Some issues though:

  • The examples are in general not using the Dispatch_Protocol property. This property doesn't officially have a default value.
  • The period property is also used when the dispatch is sporadic, timed, and hybrid dispatches.

If I understand the standard correctly,

  • sporadic means execution is triggered by an event, and the thread cannot run again afterwards for period amount of time. This means the worst case execution time could actually be period + compute_execution_time; best case would be compute_execution_time?
  • A timed dispatch is triggered by an event, and is cut off after period amount of time. Worse case is period, best case is min(compute_execution_time, period).
  • And hybrid runs periodically and is triggered by the event. Best case is compute_execution_time; worst case is also compute_execution_time?

Going through the code in FlowLatencyAnalysisSwitch it looks like mapComponentInstance() takes care of these side issues. In particular, it assumes that if the period property is set but the dispatch_protocol is not set, then the dispatch type is periodic.

@AaronGreenhouse
Copy link
Contributor

I believe I just need to change the lines

		double executionTimeLower = GetProperties.getScaledMinComputeExecutionTimeinMilliSec(componentInstance);
		double executionTimeHigher = GetProperties.getScaledMaxComputeExecutionTimeinMilliSec(componentInstance);

in mapComponentInstance().

@AaronGreenhouse
Copy link
Contributor

I'm wondering if getScaledMinComputeExecutionTimeinMilliSec() and the max version are correct for ports:

	public static double getScaledMinComputeExecutionTimeinMilliSec(final NamedElement ne) {
		Property computeExecutionTime = lookupPropertyDefinition(ne, TimingProperties._NAME,
				TimingProperties.COMPUTE_EXECUTION_TIME);
		UnitLiteral milliSecond = findUnitLiteral(computeExecutionTime, AadlProject.MS_LITERAL);
		double time = PropertyUtils.getScaledRangeMinimum(ne, computeExecutionTime, milliSecond, 0.0);
		if (ne instanceof ComponentInstance) {
			double scale = getProcessorScalingFactor((ComponentInstance) ne);
			return time * scale;
		}
		return time;
	}

It only scales the result if the named element is a ComponentInstance. I think it needs to check if the named element is a feature, and if so, use the containing ComponentInstance to get the scaling factor.

@jjhugues
Copy link
Contributor

Regarding computation of latency,
In the worst-case, sporadic and hybrid are periodic, worst case is likely to be period + WCET

@AaronGreenhouse
Copy link
Contributor

AaronGreenhouse commented Dec 17, 2019

Changed

		double executionTimeLower = GetProperties.getScaledMinComputeExecutionTimeinMilliSec(componentInstance);
		double executionTimeHigher = GetProperties.getScaledMaxComputeExecutionTimeinMilliSec(componentInstance);

to

GetProperties.getScaledMaxComputeExecutionTimeinMilliSec(componentInstance);
		double executionTimeLower = getMinExecutionTimeInMilliSec(flowElementInstance, componentInstance, isPeriodic);
		double executionTimeHigher = getMaxExecutionTimeInMilliSec(flowElementInstance, componentInstance, isPeriodic);

where

	private double getExecutionTimeInMilliSec(final FlowElementInstance fei, final ComponentInstance ci,
			final boolean isPeriodic, final Function<NamedElement, Double> getExecTime) {
		/*
		 * If the flow element is a component instance or if the thread is periodic, we use the thread's
		 * computation time. Otherwise we try to use the compute execution time from the flow's input feature.
		 */
		if (isPeriodic || fei == ci) { // the flow element is a component instance
			return getExecTime.apply(ci);
		} else { // the flow element is a FlowSpecificationInstance
			final FlowSpecificationInstance fsi = (FlowSpecificationInstance) fei;
			final FlowEnd allInEnd = fsi.getFlowSpecification().getAllInEnd();
			if (allInEnd != null) { // we have an input feature
				final FeatureInstance fi = ci.findFeatureInstance(allInEnd.getFeature());
				if (GetProperties.hasComputeExecutionTime(fi)) {
					return getExecTime.apply(fi);
				}
			}
			return getExecTime.apply(ci);
		}
	}

	private double getMinExecutionTimeInMilliSec(final FlowElementInstance fei, final ComponentInstance ci,
			final boolean isPeriodic) {
		return getExecutionTimeInMilliSec(fei, ci, isPeriodic,
				GetProperties::getScaledMinComputeExecutionTimeinMilliSec);
	}

	private double getMaxExecutionTimeInMilliSec(final FlowElementInstance fei, final ComponentInstance ci,
			final boolean isPeriodic) {
		return getExecutionTimeInMilliSec(fei, ci, isPeriodic,
				GetProperties::getScaledMaxComputeExecutionTimeinMilliSec);
	}

@AaronGreenhouse
Copy link
Contributor

Created test cases Periodic.aadl and Aperiodic.aadl

package Periodic
public
	
	system s
	end s;
	
	system implementation s.impl
		subcomponents
			p: process p.impl;
	end s.impl;
	
	process p
	end p;
	
	process implementation p.impl
		subcomponents
			t1_sample: thread t_out {Period => 3 ms;};
			t2: thread t_inout {Period => 15 ms; Deadline => 5ms;};
			t3: thread t_in {Period => 15ms;Compute_Execution_Time => 5ms..10ms;};
		connections
			c1: port t1_sample.p -> t2.p_i;
			x1: port t2.p_o -> t3.p1;
			x2: port t2.p_o -> t3.p2;
			x3: port t2.p_o -> t3.p3;
			x4: port t2.p_o -> t3.p4;
		flows
			e2e_sample1: end to end flow t1_sample.s -> c1 -> t2.p -> x1 -> t3.s1;
			e2e_sample2: end to end flow t1_sample.s -> c1 -> t2.p -> x2 -> t3.s2;
			e2e_sample3: end to end flow t1_sample.s -> c1 -> t2.p -> x3 -> t3.s3;
			e2e_sample4: end to end flow t1_sample.s -> c1 -> t2.p -> x4 -> t3.s4;
		properties
			Latency => 0 ms .. 100 ms applies to e2e_sample1;
			Latency => 0 ms .. 100 ms applies to e2e_sample2;
			Latency => 0 ms .. 100 ms applies to e2e_sample3;
			Latency => 0 ms .. 100 ms applies to e2e_sample4;
	end p.impl;
	
	thread t_out
		features
			p: out event port;
		flows
			s: flow source p;
	end t_out;
	
	thread t_in
		features
			p1: in event port;
			p2: in event port {Compute_Execution_Time => 15ms..20ms;};
			p3: in event port {Compute_Execution_Time => 3ms..6ms;};
			p4: in event port {Compute_Execution_Time => 20ms..23ms;};
		flows
			s1: flow sink p1;
			s2: flow sink p2;
			s3: flow sink p3;
			s4: flow sink p4;
	end t_in;
	
	thread t_inout
		features
			p_i: in event port;
			p_o: out event port;
		flows
			p: flow path p_i -> p_o;
	end t_inout;
	
end Periodic;
package Aperiodic
public
	
	system s
	end s;
	
	system implementation s.impl
		subcomponents
			p: process p.impl;
	end s.impl;
	
	process p
	end p;
	
	process implementation p.impl
		subcomponents
			t1_sample: thread t_out {Period => 3 ms;};
			t2: thread t_inout {Period => 15 ms; Deadline => 5ms;};
			t3: thread t_in {Compute_Execution_Time => 5ms..10ms;};
		connections
			c1: port t1_sample.p -> t2.p_i;
			x1: port t2.p_o -> t3.p1;
			x2: port t2.p_o -> t3.p2;
			x3: port t2.p_o -> t3.p3;
			x4: port t2.p_o -> t3.p4;
		flows
			e2e_sample1: end to end flow t1_sample.s -> c1 -> t2.p -> x1 -> t3.s1;
			e2e_sample2: end to end flow t1_sample.s -> c1 -> t2.p -> x2 -> t3.s2;
			e2e_sample3: end to end flow t1_sample.s -> c1 -> t2.p -> x3 -> t3.s3;
			e2e_sample4: end to end flow t1_sample.s -> c1 -> t2.p -> x4 -> t3.s4;
		properties
			Latency => 0 ms .. 100 ms applies to e2e_sample1;
			Latency => 0 ms .. 100 ms applies to e2e_sample2;
			Latency => 0 ms .. 100 ms applies to e2e_sample3;
			Latency => 0 ms .. 100 ms applies to e2e_sample4;
	end p.impl;
	
	thread t_out
		features
			p: out event port;
		flows
			s: flow source p;
	end t_out;
	
	thread t_in
		features
			p1: in event port; -- use the thread's property
			p2: in event port {Compute_Execution_Time => 15ms..20ms;};
			p3: in event port {Compute_Execution_Time => 3ms..6ms;};
			p4: in event port {Compute_Execution_Time => 20ms..23ms;};
		flows
			s1: flow sink p1;
			s2: flow sink p2;
			s3: flow sink p3;
			s4: flow sink p4;
	end t_in;
	
	thread t_inout
		features
			p_i: in event port;
			p_o: out event port;
		flows
			p: flow path p_i -> p_o;
	end t_inout;
	
end Aperiodic;

They are the same except that Periodic specifies a period for the thread t3. In this case, the compute execution times are not relevant.

The Aperodic example has 4 flows through 4 different in ports, one that uses the compute execution time of the thread, and three that get the compute execution time from the the ports.

My changes seems to work.

I'm still pretty sure the processing scaling factor stuff is not going to work correctly for ports. I have to test this and then probably fix it.

@AaronGreenhouse
Copy link
Contributor

Execution times from the features are not scaled when there is a bound processor. This needs to be fixed.

I have the additional examples

package Aperiodic_Refproc
public
	processor cpu
		properties
			Processor_Capacity => 100.0 MIPS;
	end cpu;

	processor cpu_faster
		properties
			Processor_Capacity => 400.0 MIPS;
	end cpu_faster;
	
	system s
	end s;
	
	system implementation s.impl
		subcomponents
			p: process p.impl;
			myCPU: processor cpu_faster;
		properties
			Actual_Processor_Binding => (reference (myCPU)) applies to p;			
	end s.impl;
	
	process p
	end p;
	
	process implementation p.impl
		subcomponents
			t1_sample: thread t_out {Period => 3 ms;};
			t2: thread t_inout {Period => 15 ms; Deadline => 5ms;};
			t3: thread t_in {Compute_Execution_Time => 5ms..10ms;};
		connections
			c1: port t1_sample.p -> t2.p_i;
			x1: port t2.p_o -> t3.p1;
			x2: port t2.p_o -> t3.p2;
			x3: port t2.p_o -> t3.p3;
			x4: port t2.p_o -> t3.p4;
		flows
			e2e_sample1: end to end flow t1_sample.s -> c1 -> t2.p -> x1 -> t3.s1;
			e2e_sample2: end to end flow t1_sample.s -> c1 -> t2.p -> x2 -> t3.s2;
			e2e_sample3: end to end flow t1_sample.s -> c1 -> t2.p -> x3 -> t3.s3;
			e2e_sample4: end to end flow t1_sample.s -> c1 -> t2.p -> x4 -> t3.s4;
		properties
			Reference_Processor => classifier (cpu);

			Latency => 0 ms .. 100 ms applies to e2e_sample1;
			Latency => 0 ms .. 100 ms applies to e2e_sample2;
			Latency => 0 ms .. 100 ms applies to e2e_sample3;
			Latency => 0 ms .. 100 ms applies to e2e_sample4;
	end p.impl;
	
	thread t_out
		features
			p: out event port;
		flows
			s: flow source p;
	end t_out;
	
	thread t_in
		features
			p1: in event port; -- use the thread's property
			p2: in event port {Compute_Execution_Time => 15ms..20ms;};
			p3: in event port {Compute_Execution_Time => 3ms..6ms;};
			p4: in event port {Compute_Execution_Time => 20ms..23ms;};
		flows
			s1: flow sink p1;
			s2: flow sink p2;
			s3: flow sink p3;
			s4: flow sink p4;
	end t_in;
	
	thread t_inout
		features
			p_i: in event port;
			p_o: out event port;
		flows
			p: flow path p_i -> p_o;
	end t_inout;
	
end Aperiodic_Refproc;

and

package Periodic_Refproc
public
	processor cpu
		properties
			Processor_Capacity => 100.0 MIPS;
	end cpu;

	processor cpu_faster
		properties
			Processor_Capacity => 400.0 MIPS;
	end cpu_faster;
	
	system s
	end s;
	
	system implementation s.impl
		subcomponents
			p: process p.impl;
			myCPU: processor cpu_faster;
		properties
			Actual_Processor_Binding => (reference (myCPU)) applies to p;			
	end s.impl;
	
	process p
	end p;
	
	process implementation p.impl
		subcomponents
			t1_sample: thread t_out {Period => 3 ms;};
			t2: thread t_inout {Period => 15 ms; Deadline => 5ms;};
			t3: thread t_in {Period => 15ms;Compute_Execution_Time => 5ms..10ms;};
		connections
			c1: port t1_sample.p -> t2.p_i;
			x1: port t2.p_o -> t3.p1;
			x2: port t2.p_o -> t3.p2;
			x3: port t2.p_o -> t3.p3;
			x4: port t2.p_o -> t3.p4;
		flows
			e2e_sample1: end to end flow t1_sample.s -> c1 -> t2.p -> x1 -> t3.s1;
			e2e_sample2: end to end flow t1_sample.s -> c1 -> t2.p -> x2 -> t3.s2;
			e2e_sample3: end to end flow t1_sample.s -> c1 -> t2.p -> x3 -> t3.s3;
			e2e_sample4: end to end flow t1_sample.s -> c1 -> t2.p -> x4 -> t3.s4;
		properties
			Reference_Processor => classifier (cpu);

			Latency => 0 ms .. 100 ms applies to e2e_sample1;
			Latency => 0 ms .. 100 ms applies to e2e_sample2;
			Latency => 0 ms .. 100 ms applies to e2e_sample3;
			Latency => 0 ms .. 100 ms applies to e2e_sample4;
	end p.impl;
	
	thread t_out
		features
			p: out event port;
		flows
			s: flow source p;
	end t_out;
	
	thread t_in
		features
			p1: in event port;
			p2: in event port {Compute_Execution_Time => 15ms..20ms;};
			p3: in event port {Compute_Execution_Time => 3ms..6ms;};
			p4: in event port {Compute_Execution_Time => 20ms..23ms;};
		flows
			s1: flow sink p1;
			s2: flow sink p2;
			s3: flow sink p3;
			s4: flow sink p4;
	end t_in;
	
	thread t_inout
		features
			p_i: in event port;
			p_o: out event port;
		flows
			p: flow path p_i -> p_o;
	end t_inout;
	
end Periodic_Refproc;

@Etienne13
Copy link
Contributor

Regarding computation of latency,
In the worst-case, sporadic and hybrid are periodic, worst case is likely to be period + WCET

In case the port on which the WCET is given dispatches the thread for each received data/event/eventdata, I agree with this assertion. This seems to be the assumption on examples given hereabove.

Otherwise, I think it requires to pay more attention. What if the dispatch occurs when the thread has received at least two items (e.g. Input_rate says two items per dispatch) ? What if the port does not dispatch the execution of the thread (e.g. not referenced in the Dispatch_Trigger property)?

Last remark, I still think it is odd to use the compute execution time (i.e. WCET) as if it was the response time of the task. I already filed an issue about this some time ago. I think it is on the former repo of analysis plugins.

Hope this helps ;)

Etienne.

@jjhugues
Copy link
Contributor

@Etienne13 , you are right. I was making the assumption the system was locally (per process) schedulable. If we want instead to use response time, we must revisit the whole machinery and address osate/osate2-plugins#81

@lwrage
Copy link
Contributor

lwrage commented Dec 18, 2019

@Etienne13 Some comments after talking to Peter about this:

  • Input_Rate: The latency analysis does not process this property at all. It could be an enhancement request.
  • Dispatch_Trigger: Good point, the latency analysis ignores this case. This should be a separate issue.
  • Execution time vs. response time: I'm looking into getting the best and worst case response time from Cheddar.

@AaronGreenhouse
Copy link
Contributor

Fixed getScaledMaxComputeExecutionTimeinMilliSec() to work with features:

	private static double scaleTime(final double time, final NamedElement ne) {
		ComponentInstance ci = null;
		if (ne instanceof FeatureInstance) {
			ci = ((FeatureInstance) ne).getComponentInstance();
		} else if (ne instanceof ComponentInstance) {
			ci = (ComponentInstance) ne;
		}

		if (ci != null) {
			double scale = getProcessorScalingFactor(ci);
			return time * scale;
		} else {
			return time;
		}
	}

	/**
	 * get max execution time scaled in terms of the processor the thread is
	 * bound to If it is not bound then return the specified execution time
	 *
	 * @param ne
	 *            thread component instance
	 * @return scaled time or 0.0
	 */
	public static double getScaledMaxComputeExecutionTimeinMilliSec(final NamedElement ne) {
		Property computeExecutionTime = lookupPropertyDefinition(ne, TimingProperties._NAME,
				TimingProperties.COMPUTE_EXECUTION_TIME);
		UnitLiteral milliSecond = findUnitLiteral(computeExecutionTime, AadlProject.MS_LITERAL);
		double time = PropertyUtils.getScaledRangeMaximum(ne, computeExecutionTime, milliSecond, 0.0);
		return scaleTime(time, ne);
	}

Also fixed getScaledMinComputeExecutionTimeinMilliSec() and getScaledComputeExecutionTimeinSec().

@AaronGreenhouse
Copy link
Contributor

Added unit tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants