This directory contains the implementation of PEOS - the Process Enactment Operating System - which is an enactment system for the PML process modeling language. The source code is the result of a number of student projects carried out at the University of Colorado, Denver, and Santa Clara University in the USA, and research projects at Lero, the Irish Software Engineering Research Centre. The code is released under the MIT license (see LICENSE file).
There are numerous subsystems in various states of maturity. At present, the most usable is a web-based interface using CGI. The source code for this is found in the following sub-directories:
pml - the PML language compiler and analysis tools.
pml/pml/libpml.a is required to build the PEOS "kernel"
os/kernel - the PEOS "kernel". This directory contains the core enactment engine that computes process state based on events submitted by users, or detected as a result of resource state changes.
os/kernl/libpeos.a is required to build the web ui.
ui/web2 - this is the original web interface shown in most of the PEOS research papers.
ui/ajax-cgi - this is a work in progress comprising a CGI program that recomputes process state in response to events, then returns the state of all process instances in XML format. A simple ajax front end shows how to use these data (under construction).
To build and install these subsystems, follow these steps:
Install library dependencies
If the header and libary files are not installed in the normal directory expected by the compiler on your platform (/usr/include and /usr/lib on Linux platforms), edit the following Makefiles to set CFLAGS and LDFLAGS to point to the include and library directories.
Execute make
in the root directory (where this README file is
found). This will invoke make
recursively in pml, os/kernel,
ui/web2, and ui/ajax-cgi.
Edit ui/web2/Makefile and/or ui/ajax-cgi/Makefile to set the installation directory for the CGI program(s), html, and javascript files. The default is _$(HOME)/publichtml/PEOS which is appropriate for most Linux installations.
Change directory to ui/web2/ and/or ui/ajax-cgi/ and type "make install"; this will install the CGI program(s), html, javascript, and other configuration files needed to run the PEOS web application.
The build has been tested on Ubuntu 12.04 with TCL v. 8.5. It may work on other systems as
A model of a process comprises a number of actions that represent tasks that should be performed to achieve some objective. Each action has two fields (requires and provides) that specify the inputs or pre-conditions required for the action to be performed, and the outputs or post-conditions that will be true when the action is completed.
Actions go through a set of states in response to events:
digraph pml_states {
None -> Ready {label = "requires true && predecessor DONE"};
None -> Blocked {label = "requires false && predecessor DONE"};
None -> Available {label = "requires true"};
Blocked -> Ready {label = "requires true"};
Ready -> Active {label = "event = START"};
Available -> Active {label = "event = START"};
Active -> Suspended {label = "event = SUSPEND"};
Suspended -> Active {label = "event = START"};
Active -> Pending {label = "event = DONE"};
Pending -> Done {label = "provides true"};
None -> Satisfied {label = "provides true"};
Available -> Satisfied {label = "provides true"};
Ready -> Satisfied {label = "provides true"};
Satisfied -> Done {label = "event = DONE"};
}
The data or objects that represent inputs and outputs are called resources; the requires and provides fields can have predicates that specify the state of resources necessary for the action to begin (for requires), and the new state of resources when the action completes.
For example, the following action specifies a simple "test" action:
action test {
requires { code.compiles == "true" }
provides { test_report }
}
This means that the "test" action requires code that compiles, and provides a new item "test_report" when the action is complete.
The kernel will examine the state of all required and provided resources of all actions in a process before updating their state. Actions where the resources are available in the state specified by their requires predicates will be marked "ready" or "available" depending on the state of the previous action. Actions where the resources are in the state specified in the provides predicate will be marked "done" or "satisfied" depending on whether the actor has submitted a "DONE" event on the action.
At the time a process is created, each resource name is bound to a value. Values are strings that uniquely identify a resource instance. Examples include filenames for resources that are files, urls for items in the World Wide Web, unique keys for relational database entries, or object ids for blobs in "nosql" stores.
The kernel evaluates each requires and requires predicate by querying the filesystem or database for the state of the resource. If the resource specification does not include a predicate on the resource's attributes, the kernel just verifies that it exists.
By default, resources are stored in the filesystem, and resource names are bound to filenames. So, in the example above, if _testreport is bound to "peos.tr" then
provides { test_report }
means, "check the filesystem for a file called 'peos.tr.'"
If there is a predicate included, the kernel will evaluate this predicate. For example, if code is bound to to "peos.c" in the example above, then
requires { code.compiles == "true" }
means, "determine whether 'peos.c' compiles without error."
To compute resource state, the kernel relies on a set of resource scripts defined in the file _peosinit.tcl. This file contains procedures written in the TCL scripting language.
The default _peosinit.tcl contains two procedures to check if a resource exists and evaluate a predicate:
proc exists { path } {
# Test whether 'path' is bound to a value.
if {[catch {set r $path}]} {
return 0
}
# Check if file specified by 'path' exists.
expr [file exists $path]
}
# Test whether the value specified in 'v' represents "truth" in TCL terms.
proc isTrue { v } {
return [expr {![string is false ${v}]}]
}
The 'exists' procedure would have to be redefined to work with resources stored in a database.
A resource specification such as
requires { code.compiles == "true" }
tells the kernel, "first verify that the resource bound to 'code' exists, then evaluate its 'compiles' attribute and compare it to the value 'true.'" How does the kernel do this?
Verifying that a resource exists is achieved via the exists procedure defined above. Evaluating resource attributes is similar.
From the kernel's point of view, all resource attributes are "computed": each attribute has a corresponding TCL procedure that returns the value of the attribute. So, for example, the compiles attribute of a file might be implemented with the following TCL procedure:
# Compute 'compiles' attribute by looking for an executable that
# is derived from, and newer than, the source file specified in 'path'.
proc compiles { path } {
# Get executable name ('file rootname' is like unix 'basename')
set executable [ file rootname $path ]
# Verify that (1) executable exists, and (2) that it is newer
# than source.
return [expr { [file exists $executable] && ([file mtime $executable] > [file mtime $path])}]
}
Every attribute found in a PML specification must have a corresponding TCL procedure in order for the kernel to successfully evaluate resource states. This means, for example, that if you want your kernel to use a database for resources, you must provide a new exists procedure, and a procedure for each attribute that is used in any of the models you intend to enact. Fortunately, TCL has many libraries for accessing repositories in addition to the filesystem, including relational databases, XML, and even HL7 (see also the TCL wiki entry on HL7).
Resource names in a PML specification are like variables in a program: they are convenient ways to refer to values stored in some location or repository. In the case of a program, the names refer to memory locations; in PML, the names refer to any object that can be stored in and retrieved from a repository, be it a database, Nosql store, filesystem, or the World Wide Web.
By default, PEOS expects resources to be stored in the filesystem, but this is just the default implementation and not a constraint or requirement.
Regardless of where resources are stored, for the PEOS kernel to be
able to assess resource state, resource names must be bound to
values. This is achieved by calling peos_set_resource_binding(),
which takes a process identifier, resource name, and value as
arguments, and binds the resource name to the supplied value within
the scope of the identified process. The same can be achieved from
the command line using the peos
command:
peos -r pid resource_name value
As an example, we'll expand the "test" action above into a small, complete PML specification:
process build_test {
action compile {
requires { code }
provides { code.compiles == "true" }
}
action test {
requires { code.compiles == "true" }
provides { test_report }
}
}
To create an instance of this process, run [^compiler_dir]
> peos -c build_test.pml
Executing build_test.pml:
Created pid = 0
which will create a new instance of build_test and print its process id.
The compile action will be immediately blocked because the code resource is unbound and therefore does not exist. We need to bind it to a value; suppose the pid of our process is '1'; then
> peos -r 1 code peos.c
will bind code to "peos.c", and
> peos -r 1 test_report peos.tr
will bind _testreport to "peos.tr".
Now, if the file peos.c exists, the "compile" action will become "ready." We can start the compile action:
> peos -n 1 compile start
Performing action compile
When we finish the compile action,
> peos -n 1 compile finish
Finishing action compile
If the peos executable exists, the test action will now be ready; if peos does not exist, the test action will be blocked.
[^compiler_dir]: To run these examples, you must set the COMPILER_DIR environment variable to point to the directory where your PML models are located, for instance peos/models.