Resource Modeling Tutorial
Core ABS
ABS is a modeling language which combines functional and imperative
programming styles to develop high-level executable models. Concurrent object
groups execute in parallel and communicate through asynchronous method calls.
To intuitively capture internal computation inside a method, we use a simple
functional language based on user-defined algebraic data types and functions.
Thus, the modeler may abstract from the details of low-level imperative
implementations of data structures, and still maintain an overall
object-oriented design which is close to the target system. At a high level
of abstraction, concurrent object groups typically consist of a single
concurrent object; other objects may be introduced into a group as required to
give some of the algebraic data structures an explicit imperative
representation in the model. In this tutorial, we aim at high-level models
and the groups will consist of single concurrent objects. The functional
sublanguage of ABS consists of a library of algebraic data types such as the
empty type Unit
, booleans Bool
, integers Int
, parametric data types such
as sets Set<A>
and maps Map<A>
(given a value for the type variable A
),
and (parametric) functions over values of these data types. For example, we
can define polymorphic sets using a type variable A
and two constructors
EmptySet
and Insert
, and a function contains which checks whether an
element el
is in a set ss
recursively by pattern matching over ss
:
data Set<A> = EmptySet | Insert(A, Set<A>);
def Bool contains<A>(Set<A> ss, A el) =
case ss {
EmptySet => False ;
Insert(el, _) => True;
Insert(_, xs) => contains(xs, el);
};
Here, the cases p=>exp
are evaluated in the listed order, underscore works
as a wild card in the pattern p
, and variables in p
are bound in the
expression exp
. The imperative sublanguage of ABS addresses concurrency,
communication, and synchronization at the concurrent object level in the
system design, and defines interfaces and methods with a Java-like syntax.
ABS objects are active; i.e., their run
method, if defined, gets called upon
creation. Statements are standard for sequential composition s1;s2
,
assignments x=rhs
, and for the skip
, if
, while
, and return
constructs. The statement suspend
unconditionally suspends the execution of
the active process of an object by adding this process to the queue, from
which an enabled process is then selected for execution. In await g
, the
guard g
controls the suspension of the active process and consists of
Boolean conditions b
and return tests x?
(see below). Just like
functional expressions e
, guards g
are side-effect free. If g
evaluates
to False
, the active process is suspended, i.e., added to the queue, and
some other process from the queue may execute. Expressions rhs
include the
creation of an object group new C(e)
, object creation in the group of the
creator new local C(e)
, method calls o!m(e)
and o.m(e)
, future
dereferencing x.get
, and pure expressions e
apply functions from the
functional sublanguage to state variables. Communication and synchronization
are decoupled in ABS. Communication is based on asynchronous method calls,
denoted by assignments f=o!m(e)
to future variables f
. Here, o
is an
object expression and e
are (data value or object) expressions providing
actual parameter values for the method invocation. (Local calls are written
this!m(e)
.) After calling f=o!m(e)
, the future variable f
refers to the
return value of the call and the caller may proceed with its execution without
blocking on the method reply. There are two operations on future variables,
which control synchronization in ABS. First, the guard await f?
suspends
the active process unless a return to the call associated with f
has
arrived, allowing other processes in the object group to execute. Second, the
return value is retrieved by the expression f.get
, which blocks all
execution in the object until the return value is available. The statement
sequence x=o!m(e);v=x.get
encodes commonly used blocking calls, abbreviated
v=o.m(e)
(often referred to as synchronous calls). If the return value of a
call is without interest, the call may occur directly as a statement o!m(e)
with no associated future variable. This corresponds to message passing in
the sense that there is no synchronization associated with the call.
Real-Time ABS
Real-Time ABS is an extension of ABS which captures the timed behavior of ABS models. An ABS model is a model in Real-Time ABS in which execution takes zero time; thus, standard statements in ABS are assumed to execute in zero time. Timing aspects may be added incrementally to an untimed behavioral model. Our approach extends the distributed concurrent object groups in ABS with an integration of both explicit and implicit time.
Deadlines
The object-oriented perspective on timed behavior is captured by deadlines to
method calls. Every method activation in Real-Time ABS has an associated
deadline, which decrements with the passage of time. This deadline can be
accessed inside the method body with the expression deadline()
. Deadlines
are soft; i.e., deadline()
may become negative but this does not in itself
block the execution of the method. By default the deadline associated with a
method activation is infinite, so in an untimed model deadlines will never be
missed. Other deadlines may be introduced by means of call-site annotations.
Real-Time ABS introduces two new data types into the functional sublanguage of
ABS: Time
, which has the constructor Time(r)
, and Duration
, which has
the constructors InfDuration
and Duration(r)
, where r
is a value of the
type Rat
of rational numbers. The accessor functions timeVal
and
durationValue
return r
for time and duration values Time(r)
and
Duration(r)
, respectively. Let o
be an object which implements a method
m
. Below, we define a method n
which calls m
on o
and specifies a
deadline for this call, given as an annotation and expressed in terms of its
own deadline. Remark that if its own deadline is InfDuration
, then the
deadline to m
will also be unlimited. The function scale(d,r)
multiplies
a duration d
by a rational number r
(the definition of scale
is
straightforward).
Int n (T x){ [Deadline: scale(deadline(),0.9)] return o.m(x); }
Explicit Time
In the explicit time model of Real-Time ABS, the execution time of
computations is modeled using duration statements duration(e1,e2)
with best-
and worst-case execution times e1
and e2
. These statements are inserted
into the model, and capture execution time which does not depend on the
system’s deployment architecture. Let f
be a function defined in the
functional sublanguage of ABS, which recurses through some data structure x
of type T
, and let size(x)
be a measure of the size of this data structure
x
. Consider a method m
which takes as input such a value x
and returns
the result of applying f
to x
. Let us assume that the time needed for
this computation depends on the size of x
; e.g., the computation time is
between a duration 0.5*size(x)
and a duration 4*size(x)
. An interface I
which provides the method m
and a class C
which implements I
, including the
execution time for m
using the explicit time model, are specified as follows:
interface I {
Int m(T x)
}
class C implements I {
Int m (T x){
duration(0.5*size(x), 4*size(x)); return f(x);
}
}
Implicit Time
In the implicit time model of Real-Time ABS, the execution time is not
specified explicitly in terms of durations, but rather observed on the
executing model. This is done by comparing clock values from a global clock,
which can be read by an expression now()
of type Time
. We specify an
interface J
with a method p
which, given a value of type T
, returns a
value of type Duration
, and implement p
in a class D
such that p
measures the time needed to call the method m
above, as follows:
interface J {
Duration p (T x)
}
class D implements J (I o) {
Duration p (T x){
Time start; Int y;
start = now(); y=o.m(x);
return timeDifference(now(),start);
}
}
Observe that by using the implicit time model, no assumptions about execution times are specified in the model above. The execution time depends on how quickly the method call is effectuated by the called object. The execution time is simply measured during execution by comparing the time before and after making the call. As a consequence, the time needed to execute a statement with the implicit time model depends on the capacity of the chosen deployment architecture and on synchronization with (slower) objects.
Modeling Deployment Architectures in ABS
Deployment Components
A deployment component in Real-Time ABS captures the execution capacity
associated with a number of concurrent object groups. Deployment components
are first-class citizens in Real-Time ABS, and provide a given amount of
resources which are shared by their allocated objects. Deployment components
may be dynamically created depending on the control flow of the ABS model or
statically created in the main block of the model. We assume a deployment
component environment with unlimited resources, to which the root object of a
model is allocated. When objects are created, they are by default allocated
to the same deployment component as their creator, but they may also be
allocated to a different component. Thus, a model without explicit deployment
components runs in environment, which does not impose any restrictions on the
execution capacity of the model. A model may be extended with other
deployment components with different processing capacities. Given the
interfaces I
and J
and classes C
and D
defined in above, we can for
example specify a deployment architecture in which two C
objects are
deployed on different deployment components server1
and server2
, and
interact with the D
objects deployed on a deployment component
clientServer
. Deployment components in Real-Time ABS have the type DC
and
are instances of the class DeploymentComponent
. This class takes as
parameters a name, given as a string, and a set of restrictions on resources.
The name is mainly used for monitoring purposes. Here we focus on resources
reflecting the components’ processing capacity, which are specified by the
constructor CPUCapacity(r)
, where r
represents the amount of abstract
processing resources available between observable points in time. Below, we
create three deployment components Server1
, Server2
, and ClientServer
,
with the processing capacities 6, 3, and unlimited (i.e., ClientServer
has
no resource restrictions). The local variables server1
, server2
, and
clientServer
refer to these three deployment components, respectively.
Objects are explicitly allocated to the servers by annotations; below,
object1
is allocated to Server1
, etc.
{ // This main block initializes a static deployment architecture:
DC server1 = new DeploymentComponent("Server1",set[CPUCapacity(6)]);
DC server2 = new DeploymentComponent("Server2",set[CPUCapacity(3)]);
DC clientServer = new DeploymentComponent("ClientServer", EmptySet);
[DC: server1] I object1 = new cog C;
[DC: server2] I object2 = new cog C;
[DC: clientServer] J client1monitor = new cog D(object1);
[DC: clientServer] J client2monitor = new cog D(object2);
}
The figure depicts this deployment architecture and the artefacts introduced
into the modeling language. Since all objects are allocated to a deployment
component (which is environment unless overridden by an annotation), we let
the expression thisDC()
evaluate to the deployment component of an object.
For convenience, a call to the method total("CPU")
of a deployment component
returns its total amount of allocated CPU resources.
Resource Costs
The available resource capacity of a deployment component determines how much computation may occur in the objects allocated to that component. Objects allocated to the component compete for the shared resources in order to execute, and they may execute until the component runs out of resources or they are otherwise blocked. For the case of CPU resources, the resources of the component define its processing capacity between observable (discrete) points in time, after which the resources are renewed.
Cost models
The cost of executing statements in the ABS model is determined by a default
value which is set as a compiler option (e.g., defaultcost=10
). However,
the default cost does not discriminate between statements and we may want to
introduce a more refined cost model. For example, if e
is a complex
expression, then the statement x=e
should have a significantly higher cost
than skip
in a realistic model. For this reason, more fine-grained costs
can be inserted into Real-Time ABS models by means of annotations. For
example, let us assume that the cost of computing the function f(x)
defined
in the section on Real-Time ABS may be given as a function g
which depends
on the size of the input value x
. In the context of deployment components,
we may redefine the implementation of interface I
above to be
resource-sensitive instead of having a predefined duration as in the explicit
time model. The resulting class C2
can be defined as follows:
class C2 implements I {
Int m (T x){ [Cost: g(size(x))] return f(x);
}
}
It is the responsibility of the modeler to specify an appropriate cost model.
A behavioral model with default costs may be gradually refined to provide more
realistic resource-sensitive behavior. For the computation of the cost
functions such as g
in our example above, the modeler may be assisted by the
COSTABS tool, which computes a worst-case approximation of the cost for f
in
terms of the input value x
based on static analysis techniques, when given
the ABS definition of the expression f
. However, the modeler may also want to
capture resource consumption at a more abstract level during the early stages
of system design, for example to make resource limitations explicit before a
further refinement of a behavioral model. Therefore, cost annotations may be
used by the modeler to abstractly represent the cost of some computation which
remains to be fully specified. For example, the class C3
below represents a
draft version of our method m
in which the worst-case cost of the computation
is specified although the function f
has yet to be introduced:
class C3 implements I {
Int m (T x){ [Cost: size(x)*size(x)] return 0;
}
}