Monitoring execution times

Updated: May 06, 2022

QNX Neutrino includes some special CPU-time clocks that you can use to monitor the execution times for processes and threads.

This mechanism is implemented following the POSIX “Execution Time Monitoring” option, which defines special clock IDs that represent the execution time of a thread or a process:

To obtain: Call: Specifying: Classification
A process CPU-time clock ID clock_getcpuclockid() A process ID, or 0 to get the clock ID for the calling process POSIX
A thread CPU-time clock ID pthread_getcpuclockid() A thread ID in the calling process POSIX
Either of the above ClockId() A process ID and a thread ID QNX Neutrino

A process has permission to get the CPU-time clock ID of any process.

To get the execution time for the process or thread, call clock_gettime() or ClockTime(), passing a process or thread CPU-time clock ID. POSIX also defines the following, which you can use instead of calling clock_getcpuclockid() or pthread_getcpuclockid():

CLOCK_PROCESS_CPUTIME_ID
The CPU-time clock ID for the calling process.
CLOCK_THREAD_CPUTIME_ID
The CPU-time clock ID for the calling thread.

In QNX Neutrino 7.0.1 or later, the OS calculates the execution times on every clock tick and whenever an active thread is preempted.

Here's an example:

int process_clock_id, ret;
struct timespec process_time;

ret = clock_getcpuclockid(0, &process_clock_id);
if (ret != 0) {
    perror ("clock_getcpuid()");
    return (EXIT_FAILURE);
}

printf ("Process clock ID: %d\n", process_clock_id);
  
ret = clock_gettime (process_clock_id, &process_time);
if (ret != 0) {
    perror ("clock_gettime()");
    return (EXIT_FAILURE);
}
printf ("Process clock: %ld sec, %ld nsec\n", process_time.tv_sec,
        process_time.tv_nsec);

Given that we're getting the execution time for the calling process, we could use CLOCK_PROCESS_CPUTIME_ID instead of clock_getcpuclockid():

int process_clock_id, ret;
struct timespec process_time;

ret = clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &process_time);
if (ret != 0) {
    perror ("clock_gettime()");
    return (EXIT_FAILURE);
}
printf ("Process clock: %ld sec, %ld nsec\n", process_time.tv_sec,
        process_time.tv_nsec);
Note: You can't use clock_settime() or ClockTime() to set a process or thread CPU-time clock.

Timers using a thread CPU-time clock

In QNX Neutrino 7.0.1 or later, you can arrange to be notified when a thread uses more than a given amount of CPU time. You can't currently do this for a process.

Setting up this type of notification involves creating a timer with a thread CPU-time clock ID:

  1. Get a thread CPU-time clock ID, as described above.
  2. Set up a sigevent that specifies how you want to be notified.
  3. Call timer_create(), passing to it the thread CPU-time clock ID and the sigevent.
    Note:
    • At any moment, only one timer can be using a thread's CPU-time clock ID. If there's already a timer for the given thread CPU-time clock ID, timer_create() gives an error of EAGAIN.
    • If you try to create a timer for a process CPU-time clock, timer_create() gives an error of EINVAL.
  4. Set up the appropriate data structure (e.g., an itimerspec), specifying the execution time as the timeout.
  5. Call timer_settime(), passing to it the timer ID and the timeout.

Here's an example:

struct sigevent event;
timer_t timer_id;
struct itimerspec cpu_usage;

/* Set up the method of notification. */
SIGEV_SIGNAL_INIT(&event, SIGALRM);
signal( SIGALRM, sig_handler );

/* Create a timer. */
if (timer_create(CLOCK_THREAD_CPUTIME_ID, &event, &timer_id)) {
   perror( "timer_create" );
   return EXIT_FAILURE;
}

/* Set up the amount of CPU time and then set the timer. */
cpu_usage.it_value.tv_sec = 0;
cpu_usage.it_value.tv_nsec = nsec; 
cpu_usage.it_interval.tv_sec = 0;
cpu_usage.it_interval.tv_nsec = period_nsec; 

if (timer_settime(timer_id, 0, &cpu_usage, NULL)) {
   perror( "timer_settime" );
   exit(EXIT_FAILURE);
}

/* If the thread exceeds the specified CPU time, we'll get a SIGALRM,
   and our sig_handler() function will be called. */

The accuracy of this mechanism depends on the clock period; if the thread exceeds the given execution time, the actual amount of time used is at least the amount you specified but less than the same amount plus the clock period.

CPU time billing for threads

The exact points at which QNX Neutrino starts and stops measuring (or billing) execution time for a thread is affected by any preemption (context switching) or interrupt handling. As the diagram below shows:
  • Thread context switching time is billed partly to the preempted thread and partly to the preempter thread.
  • Interrupts are billed to the thread that gets interrupted. This includes normal hardware interrupts and “system” interrupts such as the system timer tick and interrupts.

Diagram of CPU timelines in which different threads run due to context switches and interrupts
Figure 1. CPU time billing for threads during context switching and interrupt handling