Concept of time in the correlator

An understanding of how the correlator handles time is essential to writing Apama applications. See Understanding time in the correlator for more information. Note that the information provided there applies when developing Apama applications in EPL. Most of this, however, and especially the following topics also apply when developing Apama applications in Java:

The topics in this section are specific to Java. Similar topics are provided for developing Apama applications in EPL.

Getting the current time

In the correlator, the current time is the time indicated by the most recent clock tick. There are two exceptions to this:

The information in the remainder of this topic assumes that the current time is the time indicated by the most recent clock tick.

Use the static method double com.apama.jmon.Correlator.getCurrentTime() to obtain the current time. The value returned by the getCurrentTime() method is the current time represented as seconds since the epoch, January 1st, 1970 in UTC.

In the correlator, the current time is never the same as the current system time. In most circumstances it is a few milliseconds behind the system time. This difference increases when public context input queues grow.

When a listener triggers, it causes a call to the listener’s match() method. The correlator executes the entire method before the correlator starts to process another event. Consequently, while the listener is executing a method, time and the value returned by the getCurrentTime() method do not change.

Consider the following code snippet,

double a;
void checkTime() {
   a = Correlator.getCurrentTime();
}

// A listener calls the following method some time later
void logTime() {
   System.out.println("a: "+a );
      // The time when checkTime() was called

   System.out.println("current time: "+Correlator.getCurrentTime() );
   // The time now
}

In this code, a method sets double variable a to the value of getCurrentTime(), which is the time indicated by the most recent clock tick. Some time later, a different listener prints the value of a and the value of getCurrentTime(). The values logged might not be the same. This is because the first use of getCurrentTime() might return a value that is different from the second. If the two listeners have processed the same event, the logged values are the same. If the two listeners have processed different events, the logged values are different.

About timers and their trigger times

In an event expression, when you specify the within, wait, or at operator you are specifying a timer. Every timer has a trigger time. The trigger time is when you want the timer to fire.

  • When you use the within operator, the trigger time is when the specified length of time elapses. If a within timer fires, the listener fails. In the following listener, the trigger time is 30 seconds after A becomes true.

    A -> B within(30.0)
    

    If B becomes true within 30 seconds, the trigger time for the timer is not reached, the timer does not fire, the listener triggers, and the monitor calls any attached JMon listeners. If B does not become true within 30 seconds, the trigger time is reached, the timer fires, and the listener fails. The monitor does not call the MatchListener.

  • When you use the wait operator, the trigger time is when the specified pause during processing of the event expression has elaspsed. When a wait timer fires, processing continues. In the following expression, the trigger time is 20 seconds after A becomes true. When the trigger time is reached, the timer fires. The listener then starts watching for B. When B is true, the monitor calls any attached listeners.

    A -> wait(20.0) -> B
    
  • When you use the at operator, the trigger time is one or more specific times. An at timer fires at the specified times. In the following expression, the trigger time is five minutes past each hour every day. This timer fires 24 times each day. When the timer fires, the monitor calls any attached JMon listeners.

    all at(5, *, *, *, *)
    

At each clock tick, the correlator evaluates each timer to determine whether that timer’s trigger time has been reached. If a timer’s trigger time has been reached, the correlator fires that timer. When a timer’s trigger time is exactly at the same time as a clock tick, the timer fires at its exact trigger time. When a timer’s trigger time is not exactly at the same time as a clock tick, the timer fires at the next clock tick. This means that if a timer’s trigger time is.01 seconds after a clock tick, that timer does not fire until.09 seconds later.

When a timer fires, the current time is always the trigger time of the timer. This is regardless of whether the timer fired at its trigger time or at the first clock tick after its trigger time.

A single clock tick can make a repeating timer fire multiple times. For example, if you specify all wait(0.01), this timer fires 10 times every tenth of a second.

Because of rounding constraints,

  • A timer such as all wait(0.1) drifts away from firing every tenth of a second. The drift is of the order of milliseconds per century, but you can notice the drift if you convert the value of the current time to a string.

  • Two timers that you might expect to fire at the same instant might fire at different, though very close, times.

    The rounding constraint is that you cannot accurately express 0.1 seconds as a float because you cannot represent it in binary notation. For example, the on wait(0.1) listener waits for 0.10000000000000000555 seconds.

To specify a timer that fires exactly 10 times per second, calculate the length of time to wait by using a method that does not accumulate rounding errors. For example, calculate a whole part and a fractional part:

@Application(author="Tim Berners", company="Apama",
description="Demonstrate tenth of second timers", name="Tenth",
version="1.0")
@MonitorType
public class TenthOfSecond implements Monitor {

   private static final Logger LOGGER =
      Logger.getLogger(TenthOfSecond.class);
   private static final NumberFormat formatter =
      NumberFormat.getInstance();
   static { formatter.setGroupingUsed(false); }

   double startTime;
   double fraction;

   public void onLoad() {
      startTime = Math.ceil( Correlator.getCurrentTime() );
      fraction = Math.ceil(
         (Correlator.getCurrentTime() - startTime) * 10.0);
      setupTimeListener();
   }
   void setupTimeListener() {
      fraction++;
      if (10.0 <= fraction) {
         fraction = 0.0;
         startTime++;
      }
      EventExpression ee = new EventExpression("wait("+ ((startTime +
         (fraction / 10.0))-Correlator.getCurrentTime()) +")");
      ee.addMatchListener(new MatchListener() {
         public void match(MatchEvent evt) {
            LOGGER.info(formatter.format(Correlator.getCurrentTime()));
            // System.out.println(Correlator.getCurrentTime());
            // This would go to STDOUT, and isn't as pretty
            new TestEvent(Correlator.getCurrentTime()).emit();
            setupTimeListener();
         }
      });
   }
}
// TenthOfSecond

When a timer fires, the correlator processes items in the following order. The correlator

  1. Triggers all listeners that trigger at the same time.
  2. Routes any events, and routes any events that those events route, and so on.
  3. Fires any timers at the next trigger time.