Closed reteprelief closed 4 years ago
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.
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:
Dispatch_Protocol
property. This property doesn't officially have a default value. 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
? 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
).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
.
I believe I just need to change the lines
double executionTimeLower = GetProperties.getScaledMinComputeExecutionTimeinMilliSec(componentInstance);
double executionTimeHigher = GetProperties.getScaledMaxComputeExecutionTimeinMilliSec(componentInstance);
in mapComponentInstance()
.
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.
Regarding computation of latency, In the worst-case, sporadic and hybrid are periodic, worst case is likely to be period + WCET
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);
}
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.
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;
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.
@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
@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.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()
.
Added unit tests.
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.