Tachyon

1 Introduction

To test software that depends on time, using the actual time is usually not feasable or even possible. To perform actons during a certain time period, a test can be speeded up if an accelerated time is used. Another example is software that acts on a specific date. To test such software the environment in which the program runs needs to set the date to that value instead of using the actual date. To facilitate testing of time dependent software, a virtual timebase is needed. This timebase will provide the virtual time to the system under test while the virtual time can be controlled by the test framework. When the time dependent software is not being tested but runs in actual operation the virtual time must be equal to the actual time.

Tachyon provides a virtual timebase with an interface that replaces system calls such as nanosleep(2) and time(2). Instead of using these system calls, a program would call Tachyon::nanosleep() and Tachyon::time().

Features of the Tachyon class include:

Functions to control the virtual timebase:

Example of performing an action every minute for 10 minutes:

Tachyon timebase;
struct timespec interval;

interval.tv_sec = 60;
interval.tv_nsec = 0;

std::cout << "Tachyon name is " << timebase.name() << "\n";
for (int i = 0; i < 10; i++)
{
   timebase.nanosleep(interval);
   std::cout << "One minute.\n"
}

Testing this in actual time would at least take 10 minutes. To speed up the test, the Tachyon object is instructed by a test program to accellerate time:


std::cin >> tachyon_name;

Tachyon testtime(tachyon_name);

testtime.accellerate(30);

while (std::cin >> report)
{
   std::cout << report;
}

image/svg+xml T0 Ta Tv Offset a

The virtual time is calculated by using an offset o and an acceleration factor a in a simple linear function:

Tv = T0 + a * (Ta - T0) + o

T0 is the time at the moment of changing the accelleration. By default, the acceleration is 1.0 and the offset is 0, rendering the virtual time equal to the actual time. When the acceleration is unequal to 1.0, the virtual time starts to deviate from the actual time, i.e. run slower or faster. The difference or 'offset' will gruadually change as time progresses. At any point in time, Ta, the offset can be calculated with: o' = Tv - Ta = (1 - a) * T0 - (1 - a) * Ta + o If the acceleration, a, is changed, the offset is recalculated to reflect the change in acceleration and T0 is set to the time at which the acceleration is changed.

The nanosleep method will sleep for an interval of time, ts, in virtual time. This is a period of ts / a in actual time, unless the the accelleration or offset are changed during the sleep. If either of these parameters is changed, the time to sleep is recalculated from the moment of change to the moment the sleep period is supposed to end in virtual time. Receiving a message to change the accelleration or the virtual time interrupts a sleeping process. At that moment, the sleep will resume with a recalculated period in actual time. This is the diffrence bewteen at which the sleep ends and the current virtual time divided by the accelleration.

The nanosleep method calculates the virtual time at which the sleep ends and call the sleep_until method to perform the actual sleep by suspending the process. Interruptions of the sleep are handled by recalculating the sleep period from the possibly changed parameters and resuming the sleep if needed. The nanosleep(2) system call is used to perform the actual sleep. This system call may return for either one of four reasons.

  1. The sleep period ended normally. Return value = 0.
  2. The sleep is interrupted by the reception of a message. Return value = -1 and errno = EINTR. Internally, message notification is handled by the signal SIGUSR1. The virtual time is recalculated and the sleep resumes if the virtual end time has not passed yet.
  3. The sleep is interrupted by a signal which is handled by a signal handler. Return value = -1 and errno = EINTR. This is not caused by receiving a message. The sleep in virtual time is interrupted and control is returned to the calling process.
  4. An error occured. Return value = -1 and errno = EFAULT or errnno = EINVAL.

To control the virtual time from an external process, a form of inter process communication (IPC) is used. The external process, usually a test program, can find the Tachyon object to control through a name which is assigned for that Tachyon object. Two or more Tachyon objects may exist, one of which holds the virtual time used by the time dependent software. The other Tachyon object(s) are used to control the virtual timebase and send the timebase parameters through a form of IPC.

2 The Tachyon class

2.1 Methods

Tachyon()
The default contructor creates a Tachyon object that can receive changes to its parameters. It opens an IPC resource (e.g. a message queue) and will make the name of that resource available with the name() method.
Tachyon(const char *name)
Creates a Tachyon object that will send the parameters to another Tachyon object which is referred to by name. Note that name is not the name of this object but of the object which is controlled by this object.
~Tachyon()
The destructor closes any open IPC resources.
const char * name(void)
Return the name of the IPC resource that can be used to control this object.
time_t time(void)
Return the virtual in seconds since the Epoch. See also time(2).
timespec gettime(void)
Return the virtual in seconds and nanoseconds since the Epoch. See also clock_gettime(2).
int nanosleep(struct timespec req)
Suspend execution until the time in req is passed. That time is divided by the accelleration set in the Tachyon object. So, if a waiting time of 60 seconds is requested and the accelleration is 10, execution will be actually be suspended for 6 seconds. The time is specified with nanosecond precision in the struct timespec. It is defined as follows:
           struct timespec
           {
               time_t tv_sec;        /* seconds */
               long   tv_nsec;       /* nanoseconds */
           };


int nanosleep(float req)
Suspend execution until the the number of seconds in req is passed. That time is divided by the accelleration set in the Tachyon object. So, if a waiting time of 60 seconds is requested and the accelleration is 10, execution will be actually be suspended for 6 seconds. The time in seconds is specified with nanosecond precision in a floating point number.
int sleep_until(timespec end_time)
Suspend execution until the virtual time in end_time has passed. If, during the sleep, the accelleration or the offset is changed, these changes are taken into account.
settime (time_t sec)
Set the time to sec seconds since the Epoch. The vitual time will continue from that point onwards.
accellerate(double factor)
Set the accelleration factor of the virtual time. A factor > 1.0 will speed things up and will make the actual sleep tine shorter then the time requested. A factor < 1.0 will slow the virtual time down.

2.2 Protocol

The parameters to control a Tachyon object are the accelleration factor and the time offset. These parameters are controlled by sending messages to the Tachyon object's message queue in ASCII format. A message starts with a single letter to select the parameter, followed by a number for the parameter's value. The letter can be 'A' for the accelleration or 'T' for the time. To set the accelleration the message is 'A', immediately followed by a number. This number can be a floating point number. For example, the message:

   A2.5
makes the virtaul time run 2.5 times faster. The virtual clock is set to a specific time by sending a 'T' followed by an integer number. The number is the number of seconds since the epoch (1970-01-01 00:00:00 +0000 (UTC)). For example:
   T1563942575
sets the time to Jul 24 06:29:35 CEST 2019.

2.3 tachyon control command

tachyon [options] tachyon-name.

Allowed options:
  -h [ --help ]              produce help message
  --tachyon-name arg         tachyon name
  -a [ --accelleration ] arg set time acceleration factor
  -t [ --time ] arg          set time value in seconds