ha/service-mgmt/sm-common-1.0.0/src/sm_timer.c

647 lines
20 KiB
C

//
// Copyright (c) 2014 Wind River Systems, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
#include "sm_timer.h"
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <inttypes.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/timerfd.h>
#include "sm_limits.h"
#include "sm_types.h"
#include "sm_time.h"
#include "sm_debug.h"
#include "sm_selobj.h"
#define SM_TIMER_ENTRY_INUSE 0xFDFDFDFD
#define SM_MAX_TIMERS 512
#define SM_MAX_TIMERS_PER_TICK 8
#define SM_MIN_TICK_IN_MS 25
#define SM_MAX_TICK_INTERVALS 2
#define SM_SCHEDULING_ON_TIME_DEBOUNCE_IN_MS 1000
typedef struct
{
uint32_t inuse;
char timer_name[40];
long timer_run_time_in_ms;
} SmTimerHistoryEntryT;
typedef struct
{
uint32_t inuse;
uint64_t timer_instance;
char timer_name[40];
SmTimerIdT timer_id;
unsigned int ms_interval;
SmTimeT arm_timestamp;
SmTimerCallbackT callback;
int64_t user_data;
SmTimeT last_fired;
uint32_t total_fired;
} SmTimerEntryT;
typedef struct
{
bool inuse;
pthread_t thread_id;
char thread_name[SM_THREAD_NAME_MAX_CHAR];
int tick_timer_fd;
bool scheduling_on_time;
SmTimeT sched_timestamp;
SmTimeT delay_timestamp;
uint64_t timer_instance;
unsigned int tick_interval_in_ms;
unsigned int last_timer_dispatched;
SmTimerEntryT timers[SM_MAX_TIMERS];
} SmTimerThreadInfoT;
static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
static SmTimerThreadInfoT _threads[SM_THREADS_MAX];
// ****************************************************************************
// Timer - Find Thread Info
// ========================
static SmTimerThreadInfoT* sm_timer_find_thread_info( void )
{
pthread_t thread_id = pthread_self();
unsigned int thread_i;
for( thread_i=0; SM_THREADS_MAX > thread_i; ++thread_i )
{
if( !(_threads[thread_i].inuse) )
continue;
if( thread_id == _threads[thread_i].thread_id )
return( &(_threads[thread_i]) );
}
return( NULL );
}
// ****************************************************************************
// ****************************************************************************
// Timer - Register
// ================
SmErrorT sm_timer_register( const char name[], unsigned int ms,
SmTimerCallbackT callback, int64_t user_data, SmTimerIdT* timer_id )
{
SmTimerEntryT* timer_entry;
SmTimerThreadInfoT* thread_info;
*timer_id = SM_TIMER_ID_INVALID;
thread_info = sm_timer_find_thread_info();
if( NULL == thread_info )
{
DPRINTFE( "Failed to find thread information." );
return( SM_FAILED );
}
unsigned int timer_i;
for( timer_i=0; SM_MAX_TIMERS > timer_i; ++timer_i )
{
timer_entry = &(thread_info->timers[timer_i]);
if( SM_TIMER_ENTRY_INUSE == timer_entry->inuse )
continue;
memset( timer_entry, 0, sizeof(SmTimerEntryT) );
timer_entry->inuse = SM_TIMER_ENTRY_INUSE;
timer_entry->timer_instance = ++thread_info->timer_instance;
snprintf( timer_entry->timer_name, sizeof(timer_entry->timer_name),
"%s", name );
timer_entry->timer_id = timer_i;
timer_entry->ms_interval = ms;
sm_time_get( &timer_entry->arm_timestamp );
timer_entry->callback = callback;
timer_entry->user_data = user_data;
timer_entry->last_fired.tv_sec = timer_entry->last_fired.tv_nsec = 0;
timer_entry->total_fired = 0;
break;
}
if( SM_MAX_TIMERS <= timer_i )
{
DPRINTFE( "No space available to create timer (%s), exiting...",
name );
abort();
}
*timer_id = timer_i;
DPRINTFD( "Created timer (name=%s, id=%i).", timer_entry->timer_name,
timer_entry->timer_id );
return( SM_OKAY );
}
// ****************************************************************************
// ****************************************************************************
// Timer - Deregister
// ==================
SmErrorT sm_timer_deregister( SmTimerIdT timer_id )
{
SmTimerEntryT* timer_entry = NULL;
SmTimerThreadInfoT* thread_info;
if(( SM_TIMER_ID_INVALID == timer_id )||
( SM_MAX_TIMERS <= timer_id ))
{
return( SM_OKAY );
}
thread_info = sm_timer_find_thread_info();
if( NULL == thread_info )
{
DPRINTFE( "Failed to find thread information." );
return( SM_FAILED );
}
timer_entry = &(thread_info->timers[timer_id]);
timer_entry->inuse = 0;
timer_entry->timer_instance = 0;
timer_entry->user_data = 0;
DPRINTFD( "Cancelled timer (name=%s, id=%i).", timer_entry->timer_name,
timer_entry->timer_id );
return( SM_OKAY );
}
// ****************************************************************************
// ****************************************************************************
// Timer - Schedule
// ================
static SmErrorT sm_timer_schedule( SmTimerThreadInfoT* thread_info )
{
SmTimerEntryT* timer_entry;
struct itimerspec tick_time;
unsigned int interval_in_ms = thread_info->tick_interval_in_ms;
long ms_expired, ms_remaining;
int timerfd_rc;
unsigned int timer_i;
for( timer_i=0; SM_MAX_TIMERS > timer_i; ++timer_i )
{
timer_entry = &(thread_info->timers[timer_i]);
if( SM_TIMER_ENTRY_INUSE == timer_entry->inuse )
{
ms_expired = sm_time_get_elapsed_ms( &timer_entry->arm_timestamp );
if( ms_expired < timer_entry->ms_interval )
{
ms_remaining = timer_entry->ms_interval - ms_expired;
if( ms_remaining < interval_in_ms )
{
interval_in_ms = ms_remaining;
}
} else {
interval_in_ms = SM_MIN_TICK_IN_MS;
break;
}
}
}
if( SM_MIN_TICK_IN_MS > interval_in_ms )
{
interval_in_ms = SM_MIN_TICK_IN_MS;
}
DPRINTFD( "Scheduling tick timer in %d ms.", interval_in_ms );
tick_time.it_value.tv_sec = interval_in_ms / 1000;;
tick_time.it_value.tv_nsec = (interval_in_ms % 1000) * 1000000;
tick_time.it_interval.tv_sec = tick_time.it_value.tv_sec;
tick_time.it_interval.tv_nsec = tick_time.it_value.tv_nsec;
timerfd_rc = timerfd_settime( thread_info->tick_timer_fd, 0, &tick_time,
NULL );
if( 0 > timerfd_rc )
{
DPRINTFE( "Failed to arm timer, error=%s.", strerror( errno ) );
return( SM_FAILED );
}
return( SM_OKAY );
}
// ****************************************************************************
// ****************************************************************************
// Timer - Dispatch
// ================
static void sm_timer_dispatch( int selobj, int64_t user_data )
{
int retries;
long ms_expired;
SmTimeT time_prev, timer_prev;
unsigned int total_timers_fired = 0;
uint64_t num_expires = 0;
ssize_t s;
SmTimerEntryT* timer_entry;
SmTimerThreadInfoT* thread_info;
SmTimerHistoryEntryT history[SM_MAX_TIMERS_PER_TICK];
SmErrorT error;
memset( history, 0, sizeof(history) );
for( retries=0; 5 > retries; ++retries )
{
errno = 0;
s = read( selobj, &num_expires, sizeof(uint64_t) );
if(( -1 == s )&&( EINTR == errno ))
continue;
break;
}
if( sizeof(uint64_t) != s )
{
DPRINTFE( "Failed to read the number of timer expires." );
return;
}
thread_info = sm_timer_find_thread_info();
if( NULL == thread_info )
{
DPRINTFE( "Failed to find thread information." );
return;
}
ms_expired = sm_time_get_elapsed_ms( &thread_info->sched_timestamp );
if( ms_expired >= (thread_info->tick_interval_in_ms*SM_MAX_TICK_INTERVALS) )
{
if( thread_info->scheduling_on_time )
{
DPRINTFI( "Not scheduling on time, elapsed=%li ms.", ms_expired );
thread_info->scheduling_on_time = false;
}
sm_time_get( &thread_info->delay_timestamp );
} else {
if( !thread_info->scheduling_on_time )
{
ms_expired = sm_time_get_elapsed_ms( &thread_info->delay_timestamp );
if( SM_SCHEDULING_ON_TIME_DEBOUNCE_IN_MS < ms_expired )
{
DPRINTFI( "Now scheduling on time." );
thread_info->scheduling_on_time = true;
}
}
}
sm_time_get( &time_prev );
unsigned int timer_i;
for( timer_i=thread_info->last_timer_dispatched;
SM_MAX_TIMERS > timer_i; ++timer_i )
{
timer_entry = &(thread_info->timers[timer_i]);
if( SM_TIMER_ENTRY_INUSE == timer_entry->inuse )
{
ms_expired = sm_time_get_elapsed_ms( &timer_entry->arm_timestamp );
if( ms_expired >= timer_entry->ms_interval )
{
bool rearm;
uint64_t timer_instance = timer_entry->timer_instance;
SmTimerHistoryEntryT* history_entry;
history_entry = &(history[total_timers_fired]);
history_entry->inuse = SM_TIMER_ENTRY_INUSE;
snprintf( history_entry->timer_name,
sizeof(history_entry->timer_name),
"%s", timer_entry->timer_name );
DPRINTFD( "Timer %s fire, ms_interval=%d, ms_expired=%li.",
timer_entry->timer_name, timer_entry->ms_interval,
ms_expired );
sm_time_get( &timer_prev );
rearm = timer_entry->callback( timer_entry->timer_id,
timer_entry->user_data );
clock_gettime( CLOCK_REALTIME, &timer_entry->last_fired );
timer_entry->total_fired ++;
ms_expired = sm_time_get_elapsed_ms( &timer_prev );
history_entry->timer_run_time_in_ms = ms_expired;
DPRINTFD( "Timer %s took %li ms.", history_entry->timer_name,
history_entry->timer_run_time_in_ms );
if( timer_instance == timer_entry->timer_instance )
{
if( rearm )
{
sm_time_get( &timer_entry->arm_timestamp );
DPRINTFD( "Timer (%i) rearmed.", timer_entry->timer_id );
} else {
timer_entry->inuse = 0;
DPRINTFD( "Timer (%i) removed.", timer_entry->timer_id );
}
} else {
DPRINTFD( "Timer (%i) instance changed since callback, "
"rearm=%d.", timer_entry->timer_id, (int) rearm );
}
if( SM_MAX_TIMERS_PER_TICK <= ++total_timers_fired )
{
DPRINTFD( "Maximum timers per tick (%d) reached.",
SM_MAX_TIMERS_PER_TICK );
break;
}
}
}
}
if( SM_MAX_TIMERS <= timer_i )
{
thread_info->last_timer_dispatched = 0;
} else {
thread_info->last_timer_dispatched = timer_i;
}
ms_expired = sm_time_get_elapsed_ms( &time_prev );
if( ms_expired >= (thread_info->tick_interval_in_ms*SM_MAX_TICK_INTERVALS) )
{
unsigned int timer_i;
for( timer_i=0; SM_MAX_TIMERS_PER_TICK > timer_i; ++timer_i )
{
SmTimerHistoryEntryT* history_entry = &(history[timer_i]);
if( SM_TIMER_ENTRY_INUSE == history_entry->inuse )
{
DPRINTFI( "Timer %s took %li ms.", history_entry->timer_name,
history_entry->timer_run_time_in_ms );
}
}
thread_info->scheduling_on_time = false;
sm_time_get( &thread_info->delay_timestamp );
DPRINTFI( "Not scheduling on time, timer callbacks are taking too "
"long to execute, elapsed_time=%li ms.", ms_expired );
} else {
unsigned int timer_i;
for( timer_i=0; SM_MAX_TIMERS_PER_TICK > timer_i; ++timer_i )
{
SmTimerHistoryEntryT* history_entry = &(history[timer_i]);
if( SM_TIMER_ENTRY_INUSE == history_entry->inuse )
{
DPRINTFD( "Timer %s took %li ms.", history_entry->timer_name,
history_entry->timer_run_time_in_ms );
}
}
DPRINTFD( "Timer callbacks took %li ms.", ms_expired );
}
error = sm_timer_schedule( thread_info );
if( SM_OKAY != error )
{
DPRINTFE( "Failed to schedule timers, error=%s.",
sm_error_str( error ) );
}
sm_time_get( &thread_info->sched_timestamp );
}
// ****************************************************************************
// ****************************************************************************
// Timer - Scheduling On Time
// ==========================
bool sm_timer_scheduling_on_time( void )
{
SmTimerThreadInfoT* thread_info;
thread_info = sm_timer_find_thread_info();
if( NULL == thread_info )
{
DPRINTFE( "Failed to find thread information." );
return( true );
}
return( thread_info->scheduling_on_time );
}
// ****************************************************************************
// ****************************************************************************
// Timer - Scheduling On Time In Period
// ====================================
bool sm_timer_scheduling_on_time_in_period( unsigned int period_in_ms )
{
long ms_expired;
SmTimerThreadInfoT* thread_info;
thread_info = sm_timer_find_thread_info();
if( NULL == thread_info )
{
DPRINTFE( "Failed to find thread information." );
return( true );
}
ms_expired = sm_time_get_elapsed_ms( &thread_info->delay_timestamp );
return( period_in_ms < ms_expired );
}
// ****************************************************************************
// ****************************************************************************
// Timer - Dump Data
// =================
void sm_timer_dump_data( FILE* log )
{
SmTimerEntryT* timer_entry;
SmTimerThreadInfoT* thread_info;
char buffer[24];
fprintf( log, "--------------------------------------------------------------------\n" );
int thread_i;
for( thread_i=0; SM_THREADS_MAX > thread_i; ++thread_i )
{
thread_info = &(_threads[thread_i]);
if( !(thread_info->inuse) )
continue;
fprintf( log, "TIMER DATA for %s\n", thread_info->thread_name );
fprintf( log, " scheduling_on_time......%s\n", thread_info->scheduling_on_time ? "yes" : "no" );
fprintf( log, " tick_interval_in_ms.....%u\n", thread_info->tick_interval_in_ms );
unsigned int timer_i;
for( timer_i=0; SM_MAX_TIMERS > timer_i; ++timer_i )
{
timer_entry = &(thread_info->timers[timer_i]);
if( SM_TIMER_ENTRY_INUSE == timer_entry->inuse )
{
fprintf( log, " timer (name=%s, id=%i)\n", timer_entry->timer_name,
timer_entry->timer_id );
fprintf( log, " instance..........%" PRIu64 "\n", timer_entry->timer_instance );
fprintf( log, " ms_interval.......%i\n", timer_entry->ms_interval );
fprintf( log, " user_data.........%" PRIi64 "\n", timer_entry->user_data );
sm_time_format_monotonic_time(&timer_entry->arm_timestamp, buffer, sizeof(buffer));
fprintf( log, " timer created at .%s\n", buffer );
sm_time_format_realtime(&timer_entry->last_fired, buffer, sizeof(buffer));
fprintf( log, " last fired at ....%s\n", buffer );
fprintf( log, " total fired ......%d\n", timer_entry->total_fired );
}
}
fprintf( log, "\n" );
}
fprintf( log, "--------------------------------------------------------------------\n" );
}
// ****************************************************************************
// ****************************************************************************
// Timer - Initialize
// ==================
SmErrorT sm_timer_initialize( unsigned int tick_interval_in_ms )
{
SmTimerThreadInfoT* thread_info = NULL;
SmErrorT error;
if( 0 != pthread_mutex_lock( &_mutex ) )
{
DPRINTFE( "Failed to capture mutex." );
return( SM_FAILED );
}
unsigned int thread_i;
for( thread_i=0; SM_THREADS_MAX > thread_i; ++thread_i )
{
if( !(_threads[thread_i].inuse) )
{
thread_info = &(_threads[thread_i]);
break;
}
}
if( NULL == thread_info )
{
DPRINTFE( "Failed to allocate thread information." );
goto ERROR;
}
memset( thread_info, 0, sizeof(SmTimerThreadInfoT) );
thread_info->thread_id = pthread_self();
pthread_getname_np( pthread_self(), thread_info->thread_name,
sizeof(thread_info->thread_name) );
memset( thread_info->timers, 0 , sizeof(thread_info->timers) );
thread_info->tick_timer_fd = timerfd_create( CLOCK_MONOTONIC,
TFD_NONBLOCK | TFD_CLOEXEC );
if( 0 > thread_info->tick_timer_fd )
{
DPRINTFE( "Failed to create tick timer, error=%s.",
strerror( errno ) );
goto ERROR;
}
error = sm_selobj_register( thread_info->tick_timer_fd,
sm_timer_dispatch, 0 );
if( SM_OKAY != error )
{
DPRINTFE( "Failed to register selection object, error=%s.",
sm_error_str( error ) );
close( thread_info->tick_timer_fd );
thread_info->tick_timer_fd = -1;
goto ERROR;
}
thread_info->scheduling_on_time = true;
sm_time_get( &thread_info->sched_timestamp );
thread_info->timer_instance = 0;
thread_info->tick_interval_in_ms = tick_interval_in_ms;
thread_info->last_timer_dispatched = 0;
thread_info->inuse = true;
error = sm_timer_schedule( thread_info );
if( SM_OKAY != error )
{
DPRINTFE( "Failed to schedule timers, error=%s.",
sm_error_str( error ) );
close( thread_info->tick_timer_fd );
thread_info->tick_timer_fd = -1;
goto ERROR;
}
if( 0 != pthread_mutex_unlock( &_mutex ) )
{
DPRINTFE( "Failed to release mutex." );
return( SM_FAILED );
}
return( SM_OKAY );
ERROR:
if( 0 != pthread_mutex_unlock( &_mutex ) )
{
DPRINTFE( "Failed to release mutex." );
return( SM_FAILED );
}
return( SM_FAILED );
}
// ****************************************************************************
// ****************************************************************************
// Timer - Finalize
// ================
SmErrorT sm_timer_finalize( void )
{
SmTimerThreadInfoT* thread_info;
SmErrorT error;
if( 0 != pthread_mutex_lock( &_mutex ) )
{
DPRINTFE( "Failed to capture mutex." );
return( SM_FAILED );
}
thread_info = sm_timer_find_thread_info();
if( NULL == thread_info )
{
DPRINTFE( "Failed to find thread information." );
goto DONE;
}
memset( thread_info->timers, 0 , sizeof(thread_info->timers) );
if( 0 <= thread_info->tick_timer_fd )
{
error = sm_selobj_deregister( thread_info->tick_timer_fd );
if( SM_OKAY != error )
{
DPRINTFE( "Failed to deregister selection object, error=%s.",
sm_error_str( error ) );
}
close( thread_info->tick_timer_fd );
}
DONE:
if( 0 != pthread_mutex_unlock( &_mutex ) )
{
DPRINTFE( "Failed to release mutex." );
}
return( SM_OKAY );
}
// ****************************************************************************