mbgtools-lx  4.2.8
mbgcmptime.c
Go to the documentation of this file.
1 
2 /**************************************************************************
3  *
4  * $Id: mbgcmptime.c 1.4 2018/12/14 12:51:29 martin TRASH $
5  *
6  * Description:
7  * Main file for mbgcmptime program which compares the time on 2 devices
8  * by reading the time plus cycles from both devices immediately after
9  * each other, and compensates the execution delay based on the returned
10  * cycles counts.
11  *
12  * -----------------------------------------------------------------------
13  * $Log: mbgcmptime.c $
14  * Revision 1.4 2018/12/14 12:51:29 martin
15  * Fixed evaluation of device specification parameters.
16  * Improved error handling.
17  * Revision 1.3 2018/11/15 12:39:28 martin
18  * Individual MBG_MICRO_VERSION codes are now obsolete.
19  * Revision 1.2 2018/08/08 09:01:45 martin
20  * New option -a to compensate an initial offset automatically.
21  * New option -C which to append a marker '#' to the output if status changed.
22  * New option -L to specify a time offset limit. If the time offset change
23  * exceeds the specified limit then a marker '<<' is appended to the output.
24  * New option -q for quiet operation, i.e. output lines on the console
25  * are overwritten unless the previous line has a marker appended.
26  * New option -o to specify an output file that provides the full log
27  * even is -q is used, too.
28  * Options -s and -u also imply continuous mode.
29  * Fixed Windows build.
30  * Moved some common code to cmp_time_util module.
31  * Moved output unbuffering to toolutil.c::mbg_check_devices(), and account
32  * for additional parameter expected by the current version of that function.
33  * Use new, safe library functions.
34  * Close device handle when done.
35  * Proper return codes and exit codes.
36  * Revision 1.1 2013/01/25 10:26:13 martin
37  * Initial revision.
38  *
39  **************************************************************************/
40 
41 // include Meinberg headers
42 #include <cmp_time_util.h>
43 #include <mbgdevio.h>
44 #include <toolutil.h> // common utility functions
45 #include <str_util.h>
46 
47 // include system headers
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <math.h>
51 
52 #define MBG_FIRST_COPYRIGHT_YEAR 2013
53 #define MBG_LAST_COPYRIGHT_YEAR 0 // use default
54 
55 static const char *pname = "mbgcmptime";
56 
57 
58 static int loops;
59 static int read_fast;
60 static int read_raw;
61 static long sleep_secs;
62 static long sleep_usecs;
63 static int print_raw;
64 static double warn_limit;
65 static double chk_limit;
66 static int auto_offset;
67 static int check_status;
68 static int quiet;
69 
70 static const char *log_fn;
71 
72 MBG_PC_CYCLES_FREQUENCY cyc_freq; // must not be static!
73 
74 static const char *ref_dev_param;
77 
78 static const char term_clreol[] = "\x1B[0K"; // ANSI escape sequence for clreol
79 
80 
81 
82 static /*HDR*/
83 FILE *fopen_log( const char *mode )
84 {
85  FILE *fp = fopen( log_fn, mode );
86 
87  if ( fp == NULL )
88  {
89  fprintf( stderr, "Failed to open log file %s: %s. Giving up logging.\n", log_fn, strerror( errno ) );
90  log_fn = NULL;
91  }
92 
93  return fp;
94 
95 } // fopen_log
96 
97 
98 
99 static /*HDR*/
100 int do_mbgcmptime( MBG_DEV_HANDLE dh, const PCPS_DEV *p_dev )
101 {
102  PCPS_HR_TIME_CYCLES prv_htc1 = { 0 };
103  PCPS_HR_TIME_CYCLES prv_htc2 = { 0 };
104  PCPS_HR_TIME_CYCLES htc1 = { 0 };
105  PCPS_HR_TIME_CYCLES htc2 = { 0 };
106  double delta_ts;
107  double delta_cyc;
108  double delta_t;
109  double prv_delta_t = 0.0;
110  int prv_delta_t_avail = 0;
111  int this_loops = loops;
112  int rc;
113  size_t n;
114  char ws[256];
115  char ws1[128];
116  char ws2[128];
117  char ws3[64];
118  int add_marker;
119 
120  if ( read_fast )
121  {
122  rc = chk_fast_tstamp_supp( dh, p_dev, dh_ref, &dev_info_ref, NULL ); //### TODO err_msg_fnc ?
123 
124  if ( mbg_rc_is_error( rc ) )
125  {
126  if ( rc != MBG_ERR_NOT_SUPP_BY_DEV )
127  goto done;
128 
129  //##+++++++++ msg ....
130  read_fast = 0;
131  }
132  }
133 
135 
136  if ( mbg_cond_err_msg( rc, "mbg_get_default_cycles_frequency_from_dev" ) )
137  goto done;
138 
139 
140  for (;;)
141  {
142  rc = get_htc_timestamps( dh, &htc1, dh_ref, &htc2, read_fast );
143 
144  if ( rc != MBG_SUCCESS )
145  break;
146 
147  delta_t = get_htc_delta( &htc1, &htc2, &delta_ts, &delta_cyc );
148 
149  if ( auto_offset )
150  {
151  static double initial_offset;
152  static int initial_offset_set;
153 
154  if ( !initial_offset_set )
155  {
156  initial_offset = delta_t;
157  initial_offset_set = 1;
158  printf( "Initial offset: %.1f us\n", initial_offset * 1e6 );
159  }
160 
161  delta_t -= initial_offset;
162  }
163 
164  n = mbg_snprint_hr_tstamp_ext( ws1, sizeof( ws1 ), &htc1.t.tstamp, print_raw );
165 
166  if ( check_status )
167  snprintf_safe( ws1 + n, sizeof( ws1 ) - n, " st: %04X", htc1.t.status );
168 
169  n = mbg_snprint_hr_tstamp_ext( ws2, sizeof( ws2 ), &htc2.t.tstamp, print_raw );
170 
171  if ( check_status )
172  snprintf_safe( ws2 + n, sizeof( ws2 ) - n, " st: %04X", htc2.t.status );
173 
174  snprintf_safe( ws3, sizeof( ws3 ), "%+.1f us", delta_t * 1e6 );
175 
176  n = 0;
177 
178  n += snprintf_safe( &ws[n], sizeof( ws ) - n, "%s, %s", ws1, ws2 );
179 
180  n += snprintf_safe( &ws[n], sizeof( ws ) - n, " ts: %.1f us, cyc: %.1f us, delta: %s",
181  delta_ts * 1e6, delta_cyc * 1e6, ws3 );
182 
183  add_marker = 0;
184 
185  if ( ( warn_limit > 0.0 ) && ( fabs( delta_t ) > warn_limit ) )
186  {
187  n += sn_cpy_str_safe( &ws[n], sizeof( ws ) - n, " *" );
188  add_marker = 1;
189  }
190 
191  if ( prv_delta_t_avail )
192  {
193  double ddelta_t = delta_t - prv_delta_t;
194 
195  if ( ( chk_limit > 0.0 ) && ( fabs( ddelta_t ) > chk_limit ) )
196  {
197  n += sn_cpy_str_safe( &ws[n], sizeof( ws ) - n, " <<" );
198  add_marker = 1;
199  }
200 
201  if ( check_status )
202  {
203  if ( ( htc1.t.status != prv_htc1.t.status )
204  || ( htc2.t.status != prv_htc2.t.status ) )
205  {
206  n += sn_cpy_str_safe( &ws[n], sizeof( ws ) - n, " #" );
207  add_marker = 1;
208  }
209  }
210  }
211 
212  #if defined( DEBUG )
213  if ( htc2.t.tstamp.sec != htc1.t.tstamp.sec )
214  {
215  n += sn_cpy_str_safe( &ws[n], sizeof( ws ) - n, " X" );
216  add_marker = 1;
217  }
218  #endif
219 
220  if ( add_marker )
221  n += sn_cpy_str_safe( &ws[n], sizeof( ws ) - n, " !!" );
222 
223  if ( quiet )
224  {
225  // Overwrite last line, and only append new line if the
226  // current line has a marker appended, so it's visible
227  // on the console that something unexpected has happened.
228  printf( "\r%s%s%s", ws, term_clreol, ( add_marker ) ? "\n" : "" );
229  }
230  else
231  printf( "%s\n", ws );
232 
233  if ( log_fn )
234  {
235  FILE *fp = fopen_log( "a" );
236 
237  if ( fp )
238  {
239  fprintf( fp, "%s\n", ws );
240  fclose( fp );
241  }
242  }
243 
244  if ( this_loops > 0 )
245  this_loops--;
246 
247  if ( this_loops == 0 )
248  break;
249 
250  prv_htc1 = htc1;
251  prv_htc2 = htc2;
252  prv_delta_t = delta_t;
253  prv_delta_t_avail = 1;
254 
255  if ( sleep_secs )
256  sleep( sleep_secs );
257  else
258  if ( sleep_usecs )
259  usleep( sleep_usecs );
260 
261  //##++++++ printf( "\n" );
262  // if this_loops is < 0 then loop forever
263  }
264 
265 done:
266  return rc;
267 
268 } // do_mbgcmptime
269 
270 
271 
272 static /*HDR*/
273 void usage( void )
274 {
276  "Compare the time on two devices by reading the time stamps plus cycles from\n"
277  "both devices immediately after each other, and compensate the execution delay\n"
278  "based on the returned cycles counts.\n"
279  "The device to be used as reference has to be specified by parameter -i (see\n"
280  "below), whereas the device under test is specified as last parameter on the\n"
281  "command line."
282  );
284  mbg_print_opt_info( "-i dev", "reference device the test card's time shall be compared to" );
285  mbg_print_opt_info( "-l limit", "append a marker '*' to the output if delta exceeds limit [us]" );
286  mbg_print_opt_info( "-L limit", "append a marker '<<' to the output if delta changes [us]" );
287  mbg_print_opt_info( "-C", "append a marker '#' to the output if status changed" );
288  mbg_print_opt_info( "-f", "read fast (memory mapped) timestamps" );
289  mbg_print_opt_info( "-c", "run continuously" );
290  mbg_print_opt_info( "-n num", "run num loops" );
291  mbg_print_opt_info( "-r", "read raw time stamps, no cycles" );
292  mbg_print_opt_info( "-s num", "sleep num seconds between calls (implies -c)" );
293  mbg_print_opt_info( "-u num", "sleep num microseconds between calls (implies -c)" );
294  mbg_print_opt_info( "-o file", "also log output to <file>" );
295  mbg_print_opt_info( "-q", "quiet, only log errors to console (use -o to specify file for full log)" );
296  mbg_print_opt_info( "-a", "auto-compensate initial offset" );
298  puts( "" );
299 
300  printf( "\nExamples:\n\n" );
301  printf(
302  "Run once and compare the time from the 2 devices with indexes 1 and 2\n"
303  "to the time from reference device with index 0:\n\n"
304  " %s -i 0 1 2\n\n", pname
305  );
306  printf(
307  "Run continuously in 1 s intervals and compare the time from %s\n"
308  "to the time from reference device %s:\n\n"
309  " %s -s 1 -i %s %s\n\n", EXAMPLE_DEV_NAME_2, EXAMPLE_DEV_NAME_1,
311  );
312 
313 } // usage
314 
315 
316 
317 int main( int argc, char *argv[] )
318 {
319  MBG_DEV_FN ref_dev_fn;
320  int rc;
321  int c;
322 
324 
325  // check command line parameters
326  while ( ( c = getopt( argc, argv, "acCfi:l:L:n:o:qrs:u:h?" ) ) != -1 )
327  {
328  switch ( c )
329  {
330  case 'a':
331  auto_offset = 1;
332  break;
333 
334  case 'C':
335  check_status = 1;
336  break;
337 
338  case 'i':
339  ref_dev_param = optarg;
340  break;
341 
342  case 'l':
343  warn_limit = (double) atoi( optarg ) / 1e6;
344  break;
345 
346  case 'L':
347  chk_limit = (double) atoi( optarg ) / 1e6;
348  break;
349 
350  case 'f':
351  read_fast = 1;
352  break;
353 
354  case 'c':
355  loops = -1;
356  break;
357 
358  case 'n':
359  loops = atoi( optarg );
360  break;
361 
362  case 'o':
363  log_fn = optarg;
364  break;
365 
366  case 'q':
367  quiet = 1;
368  break;
369 
370  case 'r':
371  read_raw = 1;
372  break;
373 
374  case 's':
375  sleep_secs = atoi( optarg );
376  loops = -1;
377  break;
378 
379  case 'u':
380  sleep_usecs = atoi( optarg );
381  loops = -1;
382  break;
383 
384  case 'h':
385  case '?':
386  default:
387  must_print_usage = 1;
388  }
389  }
390 
391  if ( ref_dev_param == NULL )
392  {
393  // No ref device specified.
394  fprintf( stderr, "A reference device has to be specified using the -i parameter.\n\n" );
395  must_print_usage = 1;
396  }
397 
398  if ( must_print_usage )
399  {
400  usage();
401  return MBG_EXIT_CODE_USAGE;
402  }
403 
404  #if !MBG_TGT_SUPP_MEM_ACC
405  fprintf( stderr, "** Memory mapped access not supported on this target platform.\n\n" );
406  return MBG_EXIT_CODE_NOT_SUPP;
407  #endif
408 
409  #if !MBG_PC_CYCLES_SUPPORTED
410  fprintf( stderr, "** Warning: No cycles support to compute real latencies on this platform!\n" );
411  return MBG_EXIT_CODE_NOT_SUPP;
412  #endif
413 
414  if ( quiet ) // make stdout unbuffered
415  setvbuf( stdout, NULL, _IONBF, 0 );
416 
417  if ( log_fn )
418  {
419  // Create new or truncate existing log file.
420  FILE *fp = fopen_log( "w" );
421 
422  if ( fp )
423  fclose( fp );
424  }
425 
426  // We don't call mbg_open_device_by_param_chk() here since we
427  // want to print our own error message in case of failure.
428  rc = mbg_open_device_by_param( &dh_ref, ref_dev_param, 0,
429  ref_dev_fn, sizeof( ref_dev_fn ) );
430 
431  if ( mbg_rc_is_error( rc ) )
432  {
433  fprintf( stderr, "** Failed to open ref device %s: %s.\n",
434  ref_dev_param, mbg_strerror( rc ) );
435  goto out;
436  }
437 
438  #if defined( DEBUG )
439  fprintf( stderr, "Ref device %s (%s) opened successfully.\n",
440  ref_dev_param, ref_dev_fn );
441  #endif
442 
443  mbg_get_device_info( dh_ref, &dev_info_ref );
444 
445  if ( mbg_rc_is_error( rc ) )
446  {
447  fprintf( stderr, "** Failed to get device info for ref device %s: %s.\n",
448  ref_dev_param, mbg_strerror( rc ) );
449  goto out_close;
450  }
451 
452  // Handle each of the specified devices.
453  rc = mbg_handle_devices( argc, argv, optind, do_mbgcmptime, 0 );
454 
455 out_close:
456  mbg_close_device( &dh_ref );
457 
458 out:
460 }
static const char term_clreol[]
Definition: mbgcmptime.c:78
static void usage(void)
Definition: mbgcmptime.c:273
int mbg_handle_devices(int argc, char *argv[], int optind, MBG_DEV_HANDLER_FNC *fnc, int chk_dev_flags)
Main action handler that can be called by utility programs.
Definition: toolutil.c:655
int must_print_usage
PCPS_HR_TIME t
Definition: pcpsdev.h:1395
static double chk_limit
Definition: mbgcmptime.c:65
static int quiet
Definition: mbgcmptime.c:68
Requested action completed successfully.
Definition: mbgerror.h:631
int mbg_open_device_by_param(MBG_DEV_HANDLE *p_dh, const char *dev_param_str, int dev_idx, char *dev_name_buffer, size_t dev_name_buffer_size)
Try to open a device using a parameter string or device index.
Definition: toolutil.c:479
MBG_PC_CYCLES_FREQUENCY cyc_freq
Must be set up tby the application.
Definition: mbgcmptime.c:72
static int loops
Definition: mbgcmptime.c:58
#define EXAMPLE_DEV_NAME_1
Definition: toolutil.h:101
static double warn_limit
Definition: mbgcmptime.c:64
bool mbg_cond_err_msg(int rc, const char *what)
Check if a value is an error code and print an associated error message.
Definition: mbgerror.c:714
PCPS_SECONDS sec
seconds since 1970, usually UTC scale
Definition: pcpsdefs.h:974
static long sleep_secs
Definition: mbgcmptime.c:61
Unable to handle requested action, usage printed.
Definition: mbgerror.h:632
int sn_cpy_str_safe(char *dst, size_t max_len, const char *src)
A function to copy a string safely, returning the number of characters copied.
Definition: str_util.c:276
static int print_raw
Definition: mbgcmptime.c:63
#define mbg_rc_is_success(_rc)
Definition: mbgerror.h:618
#define EXAMPLE_DEV_NAME_2
Definition: toolutil.h:103
static const char * pname
Definition: mbgcmptime.c:55
static int read_raw
Definition: mbgcmptime.c:60
int get_htc_timestamps(MBG_DEV_HANDLE dh1, PCPS_HR_TIME_CYCLES *p_htc1, MBG_DEV_HANDLE dh2, PCPS_HR_TIME_CYCLES *p_htc2, int read_fast)
Read timestamps and cycles from both devices.
Definition: cmp_time_util.c:85
void mbg_print_device_options(void)
Print common info on how to specify devices on the command line.
Definition: toolutil.c:270
void mbg_print_usage_intro(const char *pname, const char *info)
Print usage intro to console.
Definition: toolutil.c:217
PCPS_TIME_STATUS_X status
status bits, see PCPS_TIME_STATUS_FLAGS
Definition: pcpsdefs.h:1089
_MBG_API_ATTR int _MBG_API mbg_get_default_cycles_frequency_from_dev(MBG_DEV_HANDLE dh, MBG_PC_CYCLES_FREQUENCY *p)
Retrieve the system&#39;s default cycles counter frequency from the kernel driver.
Definition: mbgdevio.c:9161
_MBG_API_ATTR int _MBG_API mbg_get_device_info(MBG_DEV_HANDLE dh, PCPS_DEV *p)
Read detailed device information.
Definition: mbgdevio.c:4242
static MBG_DEV_HANDLE dh_ref
Definition: mbgcmptime.c:75
static int auto_offset
Definition: mbgcmptime.c:66
static const char * log_fn
Definition: mbgcmptime.c:70
static long sleep_usecs
Definition: mbgcmptime.c:62
PCPS_TIME_STAMP tstamp
High resolution time stamp (UTC)
Definition: pcpsdefs.h:1087
int mbg_snprint_hr_tstamp_ext(char *s, int max_len, const PCPS_TIME_STAMP *p, int print_raw)
Print UTC date and time from a PCPS_TIME_STAMP structure to a string.
static int read_fast
Definition: mbgcmptime.c:59
static const char * ref_dev_param
Definition: mbgcmptime.c:74
#define MBG_SUCCESS
Error codes used with Meinberg devices and drivers.
Definition: mbgerror.h:259
Requested action not supported on the running OS.
Definition: mbgerror.h:633
_MBG_API_ATTR void _MBG_API mbg_close_device(MBG_DEV_HANDLE *dev_handle)
Close a device handle and set the handle value to MBG_INVALID_DEV_HANDLE.
Definition: mbgdevio.c:4190
#define MBG_LAST_COPYRIGHT_YEAR
Definition: mbgcmptime.c:53
Action failed for specified device.
Definition: mbgerror.h:634
#define MBG_FIRST_COPYRIGHT_YEAR
Definition: mbgcmptime.c:52
uint64_t MBG_PC_CYCLES_FREQUENCY
Definition: mbgpccyc.h:98
double get_htc_delta(const PCPS_HR_TIME_CYCLES *p_htc, const PCPS_HR_TIME_CYCLES *p_htc_ref, double *p_delta_ts, double *p_delta_cyc)
Compute the delta cycles and time difference.
static int do_mbgcmptime(MBG_DEV_HANDLE dh, const PCPS_DEV *p_dev)
Definition: mbgcmptime.c:100
static FILE * fopen_log(const char *mode)
Definition: mbgcmptime.c:83
High resolution time plus associated system cycles count.
Definition: pcpsdev.h:1392
void mbg_print_help_options(void)
Print info on common program help arguments.
Definition: toolutil.c:257
static int check_status
Definition: mbgcmptime.c:67
int snprintf_safe(char *s, size_t max_len, const char *fmt,...)
A portable, safe implementation of snprintf()
Definition: str_util.c:156
void mbg_print_opt_info(const char *opt_name, const char *opt_info)
Print info on a single program option / argument.
Definition: toolutil.c:236
int main(int argc, char *argv[])
Definition: mbgcmptime.c:317
#define MBG_ERR_NOT_SUPP_BY_DEV
Command or feature not supported by device.
Definition: mbgerror.h:286
#define mbg_rc_is_error(_rc)
Definition: mbgerror.h:617
void mbg_print_program_info(const char *pname, int first_year, int last_year)
Print program info to console.
Definition: toolutil.c:193
const char * mbg_strerror(int mbg_errno)
Return an error string associated with the MBG_ERROR_CODES.
Definition: mbgerror.c:685
static PCPS_DEV dev_info_ref
Definition: mbgcmptime.c:76
char MBG_DEV_FN[260]
A string that can take a device file name.
Definition: mbgdevio.h:387
int chk_fast_tstamp_supp(MBG_DEV_HANDLE dh1, const PCPS_DEV *p_dev_1, MBG_DEV_HANDLE dh2, const PCPS_DEV *p_dev_2, MBG_ERR_MSG_FNC err_msg_fnc)
Check if both devices support fast HR timestamps.
Definition: cmp_time_util.c:54
Device info structure.
Definition: pcpsdev.h:1043