The Computation Monitor Project

Step-by-Step Tutorial

Before reading this tutorial, be sure you have installed the package, as explained in the page Installation instructions for Microsoft Windows users or in the page Installation instructions for Unix users. In addition, it is better to have run the demo, as explained in the page Using the Demo.

1. A Trivial Computation

Create a new file named TrivialComp.java, containing the following code:

import computation.*;
class TrivialComp extends Computation {
    private static final long limit = (long)200e6;
    private volatile long i;
    public void run() {
        for (i = 0; i < limit; ++i) {
            if (isCanceled()) break;
        }
    }
    public int getLevelCount() { return 1; }
    public double getProgress(int level) {
        return (double)i / limit;
    }
    public String getStatus(int level) {
        return "" + i * 10000 / limit;
    }
}

The run method is the body of the computation. It calls often (here even too often) the isCanceled method, and exits from the computation if that call returns true.

The getLevelCount method always returns 1, for simple computations.

The getProgress method returns a fractional number between 0 and 1, that represents the fraction of the work already done. When the computation starts, i equals 0, therefore getProgress returns 0; when the computation ends, i equals limit, therefore getProgress returns 1.

The getStatus method returns, as a description of the condition of the computation, the decimal representation of the fraction of the work done times 10000.

The value of the constant limit determines the duration of the computation, depending on the computing power of the computer and the efficiency of the JVM. Tune it to your needs.

Create a new file named TrivialApp.java, containing the following code:

import computation.*;
public class TrivialApp {
    public static void main(String[] args) {
        new ComputationMonitor(null, new TrivialComp()).run();
        System.exit(0);
    }
}

The application creates a new computation object implementing the algorithm, creates a new computation monitor object that allows the user to control the execution of the computation, and tells this object to start the computation, by calling its run method.

Compile and run TrivialApp.java.

Of course, this computation is useless, as it has doesn't communicate with the object that runs it. It needs a means to get parameters and a means to return results.

2. A Computation with a Parameter and a Result

From here, the changes from a previous version of the code will be emphasized in bold blue text.

Copy the file TrivialComp.java as the file ParamComp.java, and then apply to this new file the changes shown in the following code:

import computation.*;
class ParamComp extends Computation {
    private long limit;
    private int squareRootCount;
    private volatile long i;
    public void setLimit(long limit) {
        this.limit = limit;
    }
    public int getSquareRootCount() {
        return squareRootCount;
    }
    public void run() {
        squareRootCount = 0;
        for (i = 0; i < limit; ++i) {
            if (isCanceled()) break;
            double squareRoot = Math.sqrt(i);
            if (Math.floor(squareRoot) == squareRoot) {
                ++squareRootCount;
            }
        }
    }
    public int getLevelCount() { return 1; }
    public double getProgress(int level) {
        return (double)i / limit;
    }
    public String getStatus(int level) {
        return "" + i * 10000 / limit;
    }
}

The limit constant has been replaced by a variable of the same type.

The new setLimit method allows to set a parameter for the computation.

The new getSquareRootCount method allows to get a result of the computation.

The run method is changed. Now, for every value of i that is a perfect square number, the private counter squareRootCount is incremented.

Copy the file TrivialApp.java as the file ParamApp.java, and then apply to this new file the changes shown in the following code:

import computation.*;
public class ParamApp {
    public static void main(String[] args) {
        ParamComp comp = new ParamComp();
        comp.setLimit((long)20e6);
        new ComputationMonitor(null, comp).run();
        System.out.println(comp.getSquareRootCount());
        System.exit(0);
    }
}

Before starting the computation, its setLimit method is called to pass a parameter to it.

After the computation monitor has finished to run the computation, the getSquareRootCount method of the computation is called to get the results of the execution, that are afterwords printed on the console.

Compile and run ParamApp.java.

This computation is a linear, one-stage operation, but, often, long-lasting computations have several stages, and some of these stages could themselves be composed of stages.

3. A Multi-Level Computation

Copy the file ParamComp.java as the file MultiStageComp.java, and then apply to this new file the changes shown in the following code:

import computation.*;
class MultiStageComp extends Computation {
    private long limit;
    private int squareRootCount;
    private static final int stageCount = 6;
    private int currStage;
    private volatile long i;
    public void setLimit(long limit) {
        this.limit = limit;
    }
    public int getSquareRootCount() {
        return squareRootCount;
    }
    public void run() {
        squareRootCount = 0;
        for (currStage = 0; currStage < stageCount; ++currStage) {
            for (i = 0; i < limit; ++i) {
                if (isCanceled()) break;
                double squareRoot = Math.sqrt(i);
                if (Math.floor(squareRoot) == squareRoot) {
                    ++squareRootCount;
                }
            }
        }
    }
    public int getLevelCount() { return 2; }
    public double getProgress(int level) {
        if (level == 0) return (double)currStage / stageCount;
        return (double)i / limit;
    }
    public String getStatus(int level) {
        if (level == 0) return "" + (currStage + 1);
        return "" + i * 10000 / limit;
    }
}

The new stageCount constant indicates that the computation will be composed of 6 consecutive stages.

The new currStage variable tracks the current stage of the computation, from 1 to 6.

The run method is changed. Now, the previous computation is just the body of a loop through the six stages.

The getLevelCount method now returns 2, instead of 1.

The getProgress and the getStatus methods are changed too. Now, if the parameter level is zero, both methods indicates which is the current stage of the computation; otherwise (i.e. when level is 1), the behavior is the previous one.

Copy the file ParamApp.java as the file MultiStageApp.java, and then apply to this new file the changes shown in the following code:

import computation.*;
public class MultiStageApp {
    public static void main(String[] args) {
        MultiStageComp comp = new MultiStageComp();
        comp.setLimit((long)5e6);
        new ComputationMonitor(null, comp).run();
        System.out.println(comp.getSquareRootCount());
        System.exit(0);
    }
}

The limit has been shortened, as there are several stages.

Compile and run MultiStageApp.java.

This computation monitor has default features, appropriate in many cases, but not always. A more customizable computation monitor is often desirable, if not necessary.

4. A Customized Monitor

Copy the file MultiStageApp.java as the file CustomCompApp.java, and then apply to this new file the changes shown in the following code:

import computation.*;
public class CustomCompApp {
    public static void main(String[] args) {
        MultiStageComp comp = new MultiStageComp();
        comp.setLimit((long)5e6);
        ComputationMonitor monitor = new ComputationMonitor(null, comp);
        monitor.setUpdateInterval(80);
        monitor.setSuspendedCaption("Freeze");
        monitor.run();
        System.out.println(comp.getSquareRootCount());
        System.exit(0);
    }
}

Between the creation and the execution of the computation monitor object, two of its public methods are called.

Calling the setUpdateInterval method, the refresh rate of the view the user has of the computation has been accelerated.

Calling the setSuspendedCaption method, the caption near the check box for suspending the execution has been set to "Freeze".

Compile and run CustomCompApp.java.

This computation monitor has been customized with respect to the default features, but if a program or a suite of programs need a consistent customization, some code is replicated for every computation.

5. A Customized Monitor Class

Create a new file named CustomMon.java, containing the following code:

import java.awt.*;
import computation.*;
public class CustomMon extends ComputationMonitor {
    public CustomMon(Frame base, CompInterface comp) {
        super(base, comp);
    }
    protected final String getDefaultSuspendedCaption() { return "Pause"; }
    protected final String getDefaultCancelCaption() { return "Stop"; }
}

This class, in addition to the constructor, redefines two of the protected methods of the ComputationMonitor class.

The redefined getDefaultSuspendedCaption methods causes the caption of the check box to be "Pause".

The redefined getDefaultCancelCaption methods causes the caption of the check box to be "Stop".

Copy the file CustomCompApp.java as the file CustomMonApp.java, and then apply to this new file the changes shown in the following code:

import computation.*;
public class CustomMonApp {
    public static void main(String[] args) {
        MultiStageComp comp = new MultiStageComp();
        comp.setLimit((long)5e6);
        CustomMon monitor = new CustomMon(null, comp);
        monitor.setUpdateInterval(80);
        monitor.setSuspendedCaption("Freeze");
        monitor.run();
        System.out.println(comp.getSquareRootCount());
        System.exit(0);
    }
}

Instead of the standard computation monitor class, the customized one is used to create a computation monitor object. This object has a specific further customization, so the suspension label, set to "Pause" at the class level, has been set to "Freeze" at the object level.

Compile and run CustomMonApp.java.

All of the computation algorithm, the computation monitor that controls it, and the object that launched the computation monitor run in the same process, and consequently on the same host.

6. A Client/Server Computation

This section is only for those people who has some knowledge of RMI (i.e. Remote Method Invocation) techniques.

Create a new file named RemoteCompI.java, containing the following code:

import java.rmi.*;
import computation.*;
interface RemoteCompI extends RemoteCompInterface {
    void setLimit(long limit) throws RemoteException;
    int getSquareRootCount() throws RemoteException;
}

This is the RMI interface, providing only the application-specific methods. It is an extension of the RMI interface RemoteCompInterface, used by the computation monitor.

Create a new file named RemoteComp.java, containing the following code:

import java.rmi.*;
import computation.*;
public class RemoteComp extends RemoteComputation implements RemoteCompI {
    private MultiStageComp localComp = new MultiStageComp();
    public RemoteComp() throws RemoteException {
        setComputation(localComp);
    }
    public void setLimit(long limit) {
        localComp.setLimit(limit);
    }
    public int getSquareRootCount() {
        return localComp.getSquareRootCount();
    }
}

This is the class of the remote object that implements the RMI interface. It creates a local computation object and delegates to it every operation.

In this way, the algorithm is just in one place, both for local or remote clients.

Create a new file named ServerApp.java, containing the following code:

import java.rmi.*;
import java.net.*;
public class ServerApp {
    public static void main(String[] args) {
        try {
            Naming.rebind("//localhost/MyObject", new RemoteComp());
            System.out.println("MyObject is ready.");
        }
        catch (Exception exc) { System.err.println(exc); }
    }
}

This is the root of the remote process, that publishes the remote object, naming it "MyObject".

Copy the file CustomMonApp.java as the file ClientApp.java, and then apply to this new file the changes shown in the following code:

import java.rmi.*;
import computation.*;
public class ClientApp {
    public static void main(String[] args) {
        try {
            RemoteCompI comp = (RemoteCompI)
                Naming.lookup("//" + args[0] +"/MyObject");
            comp.setLimit((long)5e6);
            CustomMon monitor = new CustomMon(null,
                new RemoteCompProxy(comp));
            monitor.setUpdateInterval(80);
            monitor.setSuspendedCaption("Freeze");
            monitor.run();
            System.out.println(comp.getSquareRootCount());
            System.exit(0);
        } catch (Exception exc) { System.err.println(exc); }
    }
}

Instead of creating an object of class MultiStageComp, this application tries to create an RMI connection with the remote object named "MyObject". This remote object is searched on the host having DNS name or IP address specified as the single command-line parameter. This object is accessed by its RMI interface RemoteCompI. This interface is quite similar to the interface of MultiStageComp, with the only difference that its methods throw the RemoteException exception if there are problems with the communication.

A ComputationMonitor, to be optimal for local computations, doesn't know about remote objects, so it cannot be constructed with a remote object, as our RemoteCompI. Instead, a proxy object must be constructed to make the ComputationMonitor communicates with the remote computation object. The class of the proxy object is RemoteCompProxy.

Compile ServerApp.java and ClientApp.java.

Run the following command:

rmic RemoteComp

It creates the files RemoteComp_Stub.class and RemoteComp_Skel.class, used by RMI.

Be sure the program rmiregistry is running on the server host.

To launch it from a Microsoft Windows console, use the following command:

start /min rmiregistry

Afterwords, you should see a new icon in the task bar with the caption "rmiregistry".

To launch it from a Unix console, use the following command:

rmiregistry &

On the server host, launch in background the java program ServerApp.

To do so from a Microsoft Windows console, use the following command:

start java ServerApp

Afterwords, you should see a new console window. Server status messages are output in this window. When you see the message "MyObject is ready.", the server object is listening.

To start the server from a Unix console, use the following command:

java ServerApp &

On the client host, run the java program ClientApp, passing as argument the DNS name or the IP address of the server host. If both client and server run on the same host, you can use "localhost" or "127.0.0.1". For instance, type the following command:

java ClientApp localhost

The user interface is the same as for the local case, but now the real work is done elsewhere.

If both client and server run on the same host, and the server has normal priority, the user interface is sluggish. To make it snappy, you could launch the server with a low priority, at the expense of some running time.

You can do this under Microsoft Windows with the following command:

start /belownormal java ServerApp

and under Unix with the following command:

nice java ServerApp &
Using the Demo The Computation Monitor Project Design Schema
© Carlo Milanesi - Last update: 30 September 2002