/************************************************************************** * * $Id: mbgxhrtime.c 1.6 2017/07/05 18:38:18 martin REL_M $ * * Description: * Main file for mbgxhrtime program which demonstrates how to retrieve * fast and accurate timestamps. * * This program starts a polling thread which reads a high resolution * time stamp and associated CPU cycles counter value once per second * and saves that data pair. * * Current time stamps are then computed by taking the current CPU * cycles value and extrapolating the time from the last data pair. * * This is very much faster than accessing the PCI card for every * single time stamp. * * Notes: * * 1.) This approach works / makes sense only with cards which support * high resolution time stamps (HR time). If a card doesn't support * that then this program prints a warning. * * 2.) Under Linux extrapolation is done using the time stamp counter * (TSC) registers provided by Pentium CPUs and newer/compatible * types as the cycles counter. On SMP / multicore CPUs those * counters may not be synchronized, so this works only correctly * if all cycles counter values are taken from the same CPU. * To achieve this the process CPU affinity is by default set to * the first CPU at program start, which means all threads of this * process are executed only on that CPU. * * 3.) Under Linux there's no easy way to find the accurate clock * frequency of the cycles counter, so the polling thread computes * the frequency from the time differences of 2 subsequent polls * of the PCI card. If the time extrapolation function is called * before the cycles clock frequency has been determined the * returned time stamp is always 0. * * ----------------------------------------------------------------------- * $Log: mbgxhrtime.c $ * Revision 1.6 2017/07/05 18:38:18 martin * New way to maintain version information. * Support build under Windows. * Use more functions from common library modules. * Use codes and inline functions from mbgerror.h. * Proper return codes and exit codes. * Revision 1.5 2009/09/29 14:25:07 martin * Display measured and default PC cycles frequency. * Updated version number to 3.4.0. * Revision 1.4 2009/07/24 09:50:09 martin * Updated version number to 3.3.0. * Revision 1.3 2009/06/19 12:38:52 martin * Updated version number to 3.2.0. * Revision 1.2 2009/03/19 17:04:26 martin * Updated version number to 3.1.0. * Updated copyright year to include 2009. * Revision 1.1 2008/12/22 11:05:24 martin * Initial revision. * **************************************************************************/ // include Meinberg headers #include #include // common utility functions // include system headers #include #include #include #if !defined( MBG_TGT_WIN32 ) #include #include #include #endif #if !defined( MBGDEVIO_USE_THREAD_API ) #error Symbol MBGDEVIO_USE_THREAD_API needs to be defined, see the Makefile. #endif #if !defined( USE_PROCESS_AFFINITY ) #define USE_PROCESS_AFFINITY 1 #endif #define MBG_MICRO_VERSION 0 #define MBG_FIRST_COPYRIGHT_YEAR 2008 #define MBG_LAST_COPYRIGHT_YEAR 0 // use default static const char *pname = "mbgxhrtime"; static int loops; #if USE_PROCESS_AFFINITY static /*HDR*/ void print_cpu_set( const char *info, MBG_CPU_SET *p_cpu_set ) { int min_cpu = MBG_CPU_SET_SIZE + 1; int max_cpu = 0; int i; for ( i = 0; i < MBG_CPU_SET_SIZE; i++ ) { if ( !_mbg_cpu_isset( i, p_cpu_set ) ) continue; if ( i < min_cpu ) min_cpu = i; if ( i > max_cpu ) max_cpu = i; } printf( "%s: CPU%i", info, min_cpu ); if ( max_cpu == min_cpu ) printf( " only" ); else printf( "...CPU%i", max_cpu ); printf( "\n" ); } // print_cpu_set /*HDR*/ void check_set_process_affinity_mask( MBG_PROCESS_ID pid, int cpu_num ) { MBG_CPU_SET cpu_set; int rc = mbg_get_process_affinity( pid, &cpu_set ); if ( rc < 0 ) { perror( "Failed to get process affinity mask" ); exit( 1 ); } print_cpu_set( "Initial process affinity mask", &cpu_set ); _mbg_cpu_clear( &cpu_set ); _mbg_cpu_set( cpu_num, &cpu_set ); rc = mbg_set_process_affinity( pid, &cpu_set ); if ( rc < 0 ) { perror( "Failed to set process affinity mask" ); exit( 1 ); } printf( "Process affinity mask set for CPU%i only\n", cpu_num ); } // check_set_process_affinity_mask #endif static /*HDR*/ int do_mbgxhrtime( MBG_DEV_HANDLE dh, const PCPS_DEV *p_dev ) { MBG_PC_CYCLES_FREQUENCY freq_hz = 0; MBG_PC_CYCLES_FREQUENCY default_freq_hz = 0; int this_loops = loops; MBG_POLL_THREAD_INFO poll_thread_info = { { { { 0 } } } }; int rc; if ( !_pcps_has_hr_time( p_dev ) ) { printf( "High resolution time not supported by this device.\n" ); return 0; } mbg_get_default_cycles_frequency_from_dev( dh, &default_freq_hz ); rc = mbg_xhrt_poll_thread_create( &poll_thread_info, dh, 0, 0 ); if ( rc != MBG_SUCCESS ) return rc; for (;;) { static int has_printed_msg = 0; PCPS_HR_TIME hrt; char ws[80]; MBG_PC_CYCLES cyc_1; MBG_PC_CYCLES cyc_2; double latency; rc = mbg_get_xhrt_cycles_frequency( &poll_thread_info.xhrt_info, &freq_hz ); if ( rc != MBG_SUCCESS ) goto fail; if ( freq_hz == 0 ) { if ( !has_printed_msg ) { printf( "Waiting until PC cycles frequency has been computed ... " ); has_printed_msg = 1; } usleep( 50000 ); continue; } if ( has_printed_msg ) { puts( "" ); printf( "PC cycles freq: %.6f MHz", ( (double) (int64_t) freq_hz ) / 1E6 ); if ( default_freq_hz ) printf( ", default: %.6f MHz", ( (double) (int64_t) default_freq_hz ) / 1E6 ); printf( "\n" ); has_printed_msg = 0; } // get an extrapolated time stamp bracketed by // mbg_get_pc_cycles() calls to compute the latency mbg_get_pc_cycles( &cyc_1 ); rc = mbg_get_xhrt_time_as_pcps_hr_time( &poll_thread_info.xhrt_info, &hrt ); mbg_get_pc_cycles( &cyc_2 ); if ( rc != MBG_SUCCESS ) goto fail; // compute the latency latency = ( (double) cyc_2 - (double) cyc_1 ) / (double) (int64_t) freq_hz * 1E6; // convert to human readable date and time mbg_snprint_hr_time( ws, sizeof( ws ), &hrt, 0 ); // raw timestamp? printf( "t: %s (%.3f us)\n", ws, latency ); if ( this_loops > 0 ) this_loops--; if ( this_loops == 0 ) break; // if this_loops is < 0 then loop forever } goto done; fail: fprintf( stderr, "** Aborting: xhrt function returned %i\n", rc ); done: mbg_xhrt_poll_thread_stop( &poll_thread_info ); return rc; } // do_mbgxhrtime static MBG_DEV_HANDLER_FNC do_mbgxhrtime; static /*HDR*/ void usage( void ) { mbg_print_usage_intro( pname, "This example program reads fast extrapolated high resolution time stamps.\n" "\n" "The program starts an extra polling thread which reads a high resolution\n" "time stamp plus associated PC cycles counter at regular intervals.\n" "The returned time stamps are extrapolated using the current PC cycles\n" "count value plus the last time stamp/cycles pair read by the polling thread.\n" "\n" "This is very much faster than accessing the card every time a time stamp\n" "needs to be retrieved.\n" "This works only for devices which support high resolution time (HR time)." ); mbg_print_help_options(); mbg_print_opt_info( "-c", "run continuously" ); mbg_print_opt_info( "-n num", "run num loops" ); mbg_print_device_options(); puts( "" ); } // usage int main( int argc, char *argv[] ) { int rc; int c; mbg_print_program_info( pname, MBG_MICRO_VERSION, MBG_FIRST_COPYRIGHT_YEAR, MBG_LAST_COPYRIGHT_YEAR ); // check command line parameters while ( ( c = getopt( argc, argv, "cn:h?" ) ) != -1 ) { switch ( c ) { case 'c': loops = -1; break; case 'n': loops = atoi( optarg ); break; case 'h': case '?': default: must_print_usage = 1; } } if ( must_print_usage ) { usage(); return MBG_EXIT_CODE_USAGE; } #if USE_PROCESS_AFFINITY check_set_process_affinity_mask( _mbg_get_current_process(), 0 ); puts( "" ); #endif // Handle each of the specified devices. rc = mbg_handle_devices( argc, argv, optind, do_mbgxhrtime, 0 ); return mbg_rc_is_success( rc ) ? MBG_EXIT_CODE_SUCCESS : MBG_EXIT_CODE_FAIL; }