• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nasa / trick / 20438126642

22 Dec 2025 04:41PM UTC coverage: 55.592% (+0.001%) from 55.591%
20438126642

Pull #2017

github

web-flow
Merge 8ca2c24a6 into bafbedd6e
Pull Request #2017: Fixed potential logging offset from restart

20 of 24 new or added lines in 1 file covered. (83.33%)

1 existing line in 1 file now uncovered.

12511 of 22505 relevant lines covered (55.59%)

297846.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.23
/trick_source/sim_services/DataRecord/DataRecordGroup.cpp
1

2
#include <algorithm>
3
#include <string>
4
#include <iostream>
5
#include <sstream>
6
#include <string.h>
7
#include <stdlib.h>
8
#include <iomanip>
9
#include <math.h>
10

11
#ifdef __GNUC__
12
#include <cxxabi.h>
13
#endif
14

15
#include "trick/DataRecordGroup.hh"
16
#include "trick/command_line_protos.h"
17
#include "trick/exec_proto.h"
18
#include "trick/reference.h"
19
#include "trick/memorymanager_c_intf.h"
20
#include "trick/message_proto.h"
21
#include "trick/message_type.h"
22

23
/**
24
@details
25
-# The recording group is enabled
26
-# The recording group is set to always record data (not changes only)
27
-# The recording group is set to allocate a maximum of 100,000 records in memory
28
-# The recording group is set to record at a cycle period of 0.1 seconds.
29
-# The first recording parameter is assigned to simulation time.  The name of
30
   the parmeter is named "sys.exec.out.time" to preserve backwards compatibility
31
   with older %Trick releases.
32
-# The call_function and call_function_double functions inherited from the SimObject
33
   are populated.  The data_record function is added as a job for this SimObject.
34

35
A sim object is created named @c data_record_group<n> where <n> is a unique group number. A
36
data_record class job within this sim object is also created that will write the group's
37
recorded data to disk. The job's name is <tt> data_record_group<n>.<in_name></tt>.
38

39
*/
40
Trick::DataRecordBuffer::DataRecordBuffer() {
5,607✔
41
    buffer = last_value = NULL ;
5,607✔
42
    ref = NULL ;
5,607✔
43
    ref_searched = false ;
5,607✔
44
}
5,607✔
45

46
Trick::DataRecordBuffer::~DataRecordBuffer() {
628✔
47
    if ( buffer ) {
628✔
48
        free(buffer) ;
614✔
49
    }
50
    if ( last_value ) {
628✔
51
        free(last_value) ;
616✔
52
    }
53

54
    ref_free(ref) ;
628✔
55
    free(ref) ;
628✔
56
}
628✔
57

58

59
Trick::LoggingCycle::LoggingCycle(double rate_in)
612✔
60
{
61
    set_rate(rate_in);
612✔
62
}
612✔
63

64
void Trick::LoggingCycle::set_rate(double rate_in)
714✔
65
{
66
    long long curr_tic = exec_get_time_tics();
714✔
67
    rate_in_seconds = rate_in;
714✔
68
    long long cycle_tics = (long long)round(rate_in * Trick::JobData::time_tic_value);
714✔
69
    rate_in_tics = cycle_tics;
714✔
70
    if((curr_tic % cycle_tics) != 0)
714✔
71
    {
72
        next_cycle_in_tics = (curr_tic/cycle_tics) * cycle_tics + cycle_tics;
2✔
73
    } else 
74
    {
75
        next_cycle_in_tics = curr_tic;
712✔
76
    }
77
}
714✔
78

79
Trick::DataRecordGroup::DataRecordGroup( std::string in_name, Trick::DR_Type dr_type ) :
607✔
80
 record(true) ,
81
 inited(false) ,
82
 group_name(in_name) ,
83
 freq(DR_Always),
84
 start(0.0) ,
85
 cycle(0.1) ,
86
 time_value_attr() ,
87
 num_variable_names(0),
88
 variable_names(NULL),
89
 variable_alias(NULL),
90
 num_change_variable_names(0),
91
 change_variable_names(NULL),
92
 change_variable_alias(NULL),
93
 max_num(100000),
94
 buffer_num(0),
95
 writer_num(0),
96
 max_file_size(1<<30), // 1 GB
97
 total_bytes_written(0),
98
 max_size_warning(false),
99
 writer_buff(NULL),
100
 single_prec_only(false),
101
 buffer_type(DR_Buffer),
102
 job_class("data_record"),
103
 curr_time(0.0)
607✔
104
{
105

106
    union {
107
        long l;
108
        char c[sizeof(long)];
109
    } byte_order_union;
110

111
    byte_order_union.l = 1;
607✔
112
    if (byte_order_union.c[sizeof(long) - 1] != 1) {
607✔
113
        byte_order = "little_endian" ;
607✔
114
    } else {
115
        byte_order = "big_endian" ;
×
116
    }
117

118
    // sim object name
119
    name = std::string("trick_data_record_group_") + in_name ;
607✔
120

121
    configure_jobs(dr_type) ;
607✔
122

123
    add_time_variable() ;
607✔
124

125
    logging_rates.emplace_back(cycle);
607✔
126
}
607✔
127

128
Trick::DataRecordGroup::~DataRecordGroup() {
42✔
129
    free((void *)time_value_attr.units) ;
42✔
130
}
42✔
131

132
int Trick::DataRecordGroup::call_function( Trick::JobData * curr_job ) {
5,119,390✔
133

134
    int ret = 0 ;
5,119,390✔
135

136
    switch (curr_job->id) {
5,119,390✔
137
        case 1:
44✔
138
            ret = init() ;
44✔
139
            break ;
44✔
140
        case 2:
365,048✔
141
            ret = write_data(false) ;
365,048✔
142
            break ;
365,048✔
143
        case 3:
12✔
144
            ret = checkpoint() ;
12✔
145
            break ;
12✔
146
        case 4:
12✔
147
            clear_checkpoint_vars() ;
12✔
148
            break ;
12✔
149
        case 5:
8✔
150
            ret = restart() ;
8✔
151
            break ;
8✔
152
        case 6:
44✔
153
            ret = shutdown() ;
44✔
154
            break ;
44✔
155
        default:
4,754,222✔
156
            ret = data_record(exec_get_sim_time()) ;
4,754,222✔
157
            break ;
4,754,222✔
158
    }
159

160
    return(ret) ;
5,119,390✔
161

162
}
163

164
double Trick::DataRecordGroup::call_function_double( Trick::JobData * curr_job ) {
×
165
    (void) curr_job ;
166
    return(0.0) ;
×
167
}
168

169
void Trick::DataRecordGroup::register_group_with_mm(void * address , const char * type) {
42✔
170
    // Only add to the memory manager if it has not already been added
171
    if ( TMM_var_exists(name.c_str()) == 0 ) {
42✔
172
        // Declare this to the memory manager.  Must be done here to get the correct type name
173
        TMM_declare_ext_var(address , TRICK_STRUCTURED, type , 0 , name.c_str() , 0 , NULL ) ;
42✔
174
        ALLOC_INFO * alloc_info = get_alloc_info_at(address) ;
42✔
175
        alloc_info->stcl = TRICK_LOCAL ;
42✔
176
        alloc_info->alloc_type = TRICK_ALLOC_NEW ;
42✔
177
    }
178
}
42✔
179

180
const std::string & Trick::DataRecordGroup::get_group_name() {
24✔
181
    return group_name ;
24✔
182
}
183

184
int Trick::DataRecordGroup::set_cycle( double in_cycle ) {
41✔
185
    logging_rates[0].set_rate(in_cycle);
41✔
186
    write_job->set_cycle(in_cycle) ;
41✔
187
    return(0) ;
41✔
188
}
189

190
int Trick::DataRecordGroup::add_cycle(double in_cycle)
5✔
191
{
192
    logging_rates.emplace_back(in_cycle);
5✔
193
    return(0);
5✔
194
}
195

196
int Trick::DataRecordGroup::set_phase( unsigned short in_phase ) {
587✔
197
    write_job->phase = in_phase ;
587✔
198
    return(0) ;
587✔
199
}
200

201
int Trick::DataRecordGroup::set_freq( DR_Freq in_freq ) {
36✔
202
    freq = in_freq ;
36✔
203
    return(0) ;
36✔
204
}
205

206
int Trick::DataRecordGroup::set_max_buffer_size( int num ) {
×
207
    max_num = num ;
×
208
    return(0) ;
×
209
}
210

211
int Trick::DataRecordGroup::set_buffer_type( int in_buffer_type ) {
44✔
212
    buffer_type = (DR_Buffering)in_buffer_type ;
44✔
213
    return(0) ;
44✔
214
}
215

216
int Trick::DataRecordGroup::set_max_file_size( uint64_t bytes ) {
16✔
217
    if(bytes == 0) {
16✔
218
        max_file_size = UINT64_MAX ;
×
219
    } else {
220
    max_file_size = bytes ; 
16✔
221
    }
222
    return(0) ;
16✔
223
}
224

225
int Trick::DataRecordGroup::set_single_prec_only( bool in_single_prec_only ) {
24✔
226
    single_prec_only = in_single_prec_only ;
24✔
227
    return(0) ;
24✔
228
}
229

230
int Trick::DataRecordGroup::set_thread( unsigned int in_thread_id ) {
199✔
231

232
    unsigned int jj ;
233
    Trick::JobData * temp_job  ;
234

235
    /* make all data_record_groups have same sim object id as data_record */
236
    for ( jj = 0 ; jj < jobs.size() ; jj++ ) {
1,801✔
237
        temp_job = jobs[jj] ;
1,602✔
238
        temp_job->thread = in_thread_id ;
1,602✔
239
    }
240
    return 0 ;
199✔
241
}
242

243
int Trick::DataRecordGroup::set_job_class( std::string in_class ) {
587✔
244
    write_job->job_class_name = job_class = in_class ;
587✔
245
    return(0) ;
587✔
246
}
247

248
int Trick::DataRecordGroup::add_time_variable() {
645✔
249
    REF2 * new_ref ;
250

251
    // Create attributes for time recorded as a double
252
    time_value_attr.type = TRICK_DOUBLE ;
645✔
253
    time_value_attr.size = sizeof(double) ;
645✔
254
    time_value_attr.units = strdup("s") ;
645✔
255

256
    // Create a reference that records time as sys.exec.out.time
257
    new_ref = (REF2 *)calloc( 1 , sizeof(REF2));
645✔
258
    new_ref->reference = strdup("sys.exec.out.time") ;
645✔
259
    new_ref->address = &curr_time ;
645✔
260
    new_ref->attr = &time_value_attr ;
645✔
261
    add_variable(new_ref) ;
645✔
262

263
    return 0 ;
645✔
264
}
265

266
int Trick::DataRecordGroup::add_variable( std::string in_name , std::string alias ) {
954✔
267

268
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
954✔
269
    // Trim leading spaces
270
    in_name.erase( 0, in_name.find_first_not_of( " \t" ) );
954✔
271
    // Trim trailing spaces
272
    in_name.erase( in_name.find_last_not_of( " \t" ) + 1);
954✔
273
    new_var->name = in_name ;
954✔
274
    new_var->alias = alias ;
954✔
275
    rec_buffer.push_back(new_var) ;
954✔
276
    return 0 ;
954✔
277
}
278

279
void Trick::DataRecordGroup::remove_variable( std::string in_name ) {
×
280
    // Trim leading spaces++ 
281
    in_name.erase( 0, in_name.find_first_not_of( " \t" ) );
×
282
    // Trim trailing spaces
283
    in_name.erase( in_name.find_last_not_of( " \t" ) + 1);
×
284

285
    if (!in_name.compare("sys.exec.out.time")) {
×
286
        // This class assumes sim time is always the first variable.
287
        // Removing it results in errors.
288
        return;
×
289
    }
290

291
    auto remove_from = [&](std::vector<DataRecordBuffer*>& buffer) {
×
292
        for (auto i = buffer.begin(); i != buffer.end(); ++i) {
×
293
            if (!(*i)->name.compare(in_name)) {
×
294
                delete *i;
×
295
                buffer.erase(i);
×
296
                break;
×
297
            }
298
        }
299
    };
×
300

301
    remove_from(rec_buffer);
×
302
    remove_from(change_buffer);
×
303
}
304

305
void Trick::DataRecordGroup::remove_all_variables() {
52✔
306
    // remove all but the first variable, which is sim time
307
    if(!rec_buffer.empty()) {
52✔
308
        for (auto i = rec_buffer.begin() + 1; i != rec_buffer.end(); ++i) {
608✔
309
            delete *i;
556✔
310
        }
311
        rec_buffer.erase(rec_buffer.begin() + 1, rec_buffer.end());
52✔
312
    }
313

314
    // remove everything
315
    for (auto variable : change_buffer) {
58✔
316
        delete variable;
6✔
317
    }
318

319
    change_buffer.clear();
52✔
320
}
52✔
321

322
int Trick::DataRecordGroup::add_variable( REF2 * ref2 ) {
4,647✔
323

324
    if ( ref2 == NULL || ref2->attr == NULL ) {
4,647✔
325
        return(-1) ;
×
326
    }
327

328
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
4,647✔
329
    new_var->name = std::string(ref2->reference) ;
4,647✔
330
    new_var->ref_searched = true ;
4,647✔
331
    new_var->ref = ref2 ;
4,647✔
332
    new_var->last_value = (char *)calloc(1 , new_var->ref->attr->size) ;
4,647✔
333
    // Don't allocate space for the temp storage buffer until "init"
334
    rec_buffer.push_back(new_var) ;
4,647✔
335

336
    return(0) ;
4,647✔
337

338
}
339

340
int Trick::DataRecordGroup::add_change_variable( std::string in_name ) {
6✔
341

342
    REF2 * ref2 ;
343

344
    ref2 = ref_attributes(in_name.c_str()) ;
6✔
345

346
    if ( ref2 == NULL || ref2->attr == NULL ) {
6✔
347
        message_publish(MSG_WARNING, "Could not find Data Record change variable %s.\n", in_name.c_str()) ;
×
348
        return(-1) ;
×
349
    }
350

351
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
6✔
352
    new_var->ref = ref2 ;
6✔
353
    new_var->name = in_name;
6✔
354
    new_var->buffer = (char *)malloc(ref2->attr->size) ;
6✔
355
    new_var->last_value =  NULL ;
6✔
356
    memcpy(new_var->buffer , ref2->address , ref2->attr->size) ;
6✔
357
    change_buffer.push_back(new_var) ;
6✔
358

359
    return(0) ;
6✔
360

361
}
362

363
bool Trick::DataRecordGroup::isSupportedType(REF2 * ref2, std::string& message) {
544✔
364
    if (ref2->attr->type == TRICK_STRING || ref2->attr->type == TRICK_STL || ref2->attr->type == TRICK_STRUCTURED) {
544✔
365
        message = "Cannot Data Record variable " + std::string(ref2->reference) + " of unsupported type " + std::to_string(ref2->attr->type);
2✔
366
        return false;
2✔
367
    }
368
    
369
    // If this is an array and not a single value, don't record it
370
    if (ref2->num_index != ref2->attr->num_index) {
542✔
371
        message = "Cannot Data Record arrayed variable " + std::string(ref2->reference);
4✔
372
        return false;
4✔
373
    }
374

375
    return true;
538✔
376
}
377

378
/**
379
@details
380
-# The simulation output directory is retrieved from the CommandLineArguments
381
-# The log header file is created
382
   -# The endianness of the log file is written to the log header.
383
   -# The names of the parameters contained in the log file are written to the header.
384
-# Memory buffers are allocated to store simulation data
385
-# The DataRecordGroupObject (a derived SimObject) is added to the Scheduler.
386
*/
387
int Trick::DataRecordGroup::init() {
55✔
388

389
    unsigned int jj ;
390
    int ret ;
391

392
    // reset counter here so we can "re-init" our recording
393
    buffer_num = writer_num = total_bytes_written = 0 ;
55✔
394

395
    output_dir = command_line_args_get_output_dir() ;
55✔
396
    /* this is the common part of the record file name, the format specific will add the correct suffix */
397
    file_name = output_dir + "/log_" + group_name ;
55✔
398

399
    pthread_mutex_init(&buffer_mutex, NULL);
55✔
400

401
    // Allocate recording space for time.
402
    rec_buffer[0]->buffer = (char *)calloc(max_num , rec_buffer[0]->ref->attr->size) ;
55✔
403
    rec_buffer[0]->last_value = (char *)calloc(1 , rec_buffer[0]->ref->attr->size) ;
55✔
404

405
    /* Loop through all variables looking up names.  Allocate recording space
406
       according to size of the variable */
407
    for (jj = 1; jj < rec_buffer.size() ; jj++) {
637✔
408
        Trick::DataRecordBuffer * drb = rec_buffer[jj] ;
582✔
409
        if ( drb->ref_searched == false ) {
582✔
410
            REF2 * ref2 ;
411

412
            ref2 = ref_attributes(drb->name.c_str()) ;
544✔
413
            if ( ref2 == NULL || ref2->attr == NULL ) {
544✔
414
                message_publish(MSG_WARNING, "Could not find Data Record variable %s.\n", drb->name.c_str()) ;
×
415
                rec_buffer.erase(rec_buffer.begin() + jj--) ;
×
416
                delete drb ;
×
417
                continue ;
×
418
            } else {
419
                std::string message;
544✔
420
                if (!isSupportedType(ref2, message)) {
544✔
421
                    message_publish(MSG_WARNING, "%s\n", message.c_str()) ;
6✔
422
                    rec_buffer.erase(rec_buffer.begin() + jj--) ;
6✔
423
                    delete drb ;
6✔
424
                    continue ;
6✔
425
                } else {
426
                    drb->ref = ref2 ;
538✔
427
                }
428
            }
429
        }
430
        if ( drb->alias.compare("") ) {
576✔
431
            drb->ref->reference = strdup(drb->alias.c_str()) ;
16✔
432
        }
433
        drb->last_value = (char *)calloc(1 , drb->ref->attr->size) ;
576✔
434
        drb->buffer = (char *)calloc(max_num , drb->ref->attr->size) ;
576✔
435
        drb->ref_searched = true ;
576✔
436
    }
437

438
    write_header() ;
55✔
439

440
    // call format specific initialization to open destination and write header
441
    ret = format_specific_init() ;
55✔
442

443
    long long curr_tics = exec_get_time_tics();
55✔
444
    int tic_value = exec_get_time_tic_value();
55✔
445

446
    for(size_t ii = 0; ii < logging_rates.size(); ++ii)
116✔
447
    {
448
        double logging_rate = logging_rates[ii].rate_in_seconds;
61✔
449
        if(logging_rate < (1.0 / tic_value))
61✔
450
        {
NEW
451
            message_publish(MSG_ERROR,
×
452
                            "DataRecordGroup ERROR: Cycle for %lu logging rate idx is less than time tic value. cycle = "
453
                            "%16.12f, time_tic = %16.12f\n",
454
                            ii,
455
                            logging_rate,
456
                            tic_value);
NEW
457
            ret = -1;
×
458
        }
459
        long long cycle_tics = (long long)round(logging_rate * Trick::JobData::time_tic_value);
61✔
460

461
        /* Calculate the if the cycle_tics would be a whole number  */
462
        double test_rem = fmod(logging_rate * (double)tic_value , 1.0 ) ;
61✔
463

464
        if ( test_rem > 0.001 ) {
61✔
NEW
465
            message_publish(MSG_WARNING,"DataRecordGroup ERROR: Cycle for %lu logging rate idx cannot be exactly scheduled with time tic value. "
×
466
             "cycle = %16.12f, cycle_tics = %lld , time_tic = %16.12f\n",
467
             ii , logging_rate, cycle_tics , 1.0 / tic_value ) ;
NEW
468
            ret = -1 ;
×
469
        }
470

471
        // We've checked that the rate is achievable. Call set_rate again to make sure the latest time_tic_value
472
        // was utilized for calculated next tics
473
        logging_rates[ii].set_rate(logging_rate);
61✔
474
    }
475

476
    write_job->next_tics = curr_tics;
55✔
477

478
    long long next_next_tics = calculate_next_logging_tic(write_job->next_tics);
55✔
479
    write_job->cycle_tics = next_next_tics - curr_tics;
55✔
480
    write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
55✔
481

482
    // set the inited flag to true when all initialization is done
483
    if ( ret == 0 ) {
55✔
484
        inited = true ;
55✔
485
    }
486

487
    return(0) ;
55✔
488

489
}
490

491
void Trick::DataRecordGroup::configure_jobs(DR_Type type) {
607✔
492
    switch(type) {
607✔
493
    default:
50✔
494
        // run the restart job in phase 60001
495
        add_job(0, 5, (char *)"restart", NULL, 1.0, (char *)"restart", (char *)"TRK", 60001) ;
50✔
496

497
    case DR_Type::DR_Type_FrameLogDataRecord:
607✔
498
        // add_jobs_to_queue will fill in job_id later
499
        // make the init job run after all other initialization jobs but before the post init checkpoint
500
        // job so users can allocate memory in initialization jobs and checkpointing data rec groups will work
501
        add_job(0, 1, (char *)"initialization", NULL, cycle, (char *)"init", (char *)"TRK", 65534) ;
607✔
502
        add_job(0, 2, (char *)"end_of_frame", NULL, 1.0, (char *)"write_data", (char *)"TRK") ;
607✔
503
        add_job(0, 3, (char *)"checkpoint", NULL, 1.0, (char *)"checkpoint", (char *)"TRK") ;
607✔
504
        add_job(0, 4, (char *)"post_checkpoint", NULL, 1.0, (char *)"clear_checkpoint_vars", (char *)"TRK") ;
607✔
505
        add_job(0, 6, (char *)"shutdown", NULL, 1.0, (char *)"shutdown", (char *)"TRK") ;
607✔
506

507
        write_job = add_job(0, 99, (char *)job_class.c_str(), NULL, cycle, (char *)"data_record" , (char *)"TRK") ;
607✔
508
        break ;
607✔
509
    }
510
    write_job->set_system_job_class(true);
607✔
511
}
607✔
512

513
int Trick::DataRecordGroup::checkpoint() {
12✔
514
    unsigned int jj ;
515

516
    /*
517
       Save the names of the variables and the aliases to the checkpoint,
518
       the rest of the DataRecordBuffer will be reconstructed during restart.
519
       The first variable is time which we do not have to save.
520
     */
521
    if ( rec_buffer.size() > 1 ) {
12✔
522
        num_variable_names = rec_buffer.size() - 1 ;
12✔
523
        variable_names = (char **)TMM_declare_var_1d("char *", (int)rec_buffer.size() - 1) ;
12✔
524
        variable_alias = (char **)TMM_declare_var_1d("char *", (int)rec_buffer.size() - 1) ;
12✔
525

526
        for (jj = 1; jj < rec_buffer.size() ; jj++) {
58✔
527
            Trick::DataRecordBuffer * drb = rec_buffer[jj] ;
46✔
528

529
            variable_names[jj-1] = TMM_strdup((char *)drb->name.c_str()) ;
46✔
530
            variable_alias[jj-1] = TMM_strdup((char *)drb->alias.c_str()) ;
46✔
531
        }
532
    }
533

534
    /*
535
       Save the names of the change variables and the aliases to the checkpoint,
536
       the rest of the DataRecordBuffer will be reconstructed during restart
537
     */
538
    if ( change_buffer.size() > 0 ) {
12✔
539
        num_change_variable_names = change_buffer.size() ;
2✔
540
        change_variable_names = (char **)TMM_declare_var_1d("char *", (int)change_buffer.size()) ;
2✔
541
        change_variable_alias = (char **)TMM_declare_var_1d("char *", (int)change_buffer.size()) ;
2✔
542

543
        for (jj = 0; jj < change_buffer.size() ; jj++) {
4✔
544
            Trick::DataRecordBuffer * drb = change_buffer[jj] ;
2✔
545

546
            change_variable_names[jj] = TMM_strdup((char *)drb->name.c_str()) ;
2✔
547
            change_variable_alias[jj] = TMM_strdup((char *)drb->alias.c_str()) ;
2✔
548
        }
549
    }
550

551
    return 0 ;
12✔
552
}
553

554
void Trick::DataRecordGroup::clear_checkpoint_vars() {
50✔
555
    
556
    if ( variable_names ) {
50✔
557
        for(unsigned int jj = 0; jj < num_variable_names; jj++) {
80✔
558
            TMM_delete_var_a(variable_names[jj]);
60✔
559
        }
560
        TMM_delete_var_a(variable_names) ;
20✔
561
    }
562

563
    if ( variable_alias ) {
50✔
564
        for(unsigned int jj = 0; jj < num_variable_names; jj++) {
80✔
565
            TMM_delete_var_a(variable_alias[jj]);
60✔
566
        }
567
        TMM_delete_var_a(variable_alias) ;
20✔
568
    }
569

570
    if ( change_variable_names ) {
50✔
571
        for(unsigned int jj = 0; jj < num_change_variable_names; jj++) {
8✔
572
            TMM_delete_var_a(change_variable_names[jj]);
4✔
573
        }
574
        TMM_delete_var_a(change_variable_names) ;
4✔
575
    }
576

577
    if ( change_variable_alias ) {
50✔
578
        for(unsigned int jj = 0; jj < num_change_variable_names; jj++) {
8✔
579
            TMM_delete_var_a(change_variable_alias[jj]);
4✔
580
        }
581
        TMM_delete_var_a(change_variable_alias) ;
4✔
582
    }
583

584
    variable_names = NULL ;
50✔
585
    variable_alias = NULL ;
50✔
586
    change_variable_names = NULL ;
50✔
587
    change_variable_alias = NULL ;
50✔
588
    num_variable_names = 0 ;
50✔
589
    num_change_variable_names = 0 ;
50✔
590
}
50✔
591

592
int Trick::DataRecordGroup::restart() {
8✔
593
    std::vector <Trick::DataRecordBuffer *>::iterator drb_it ;
8✔
594

595
    /* delete the current rec_buffer */
596
    for ( drb_it = rec_buffer.begin() ; drb_it != rec_buffer.end() ; ++drb_it ) {
16✔
597
        delete *drb_it ;
8✔
598
    }
599
    rec_buffer.clear() ;
8✔
600
    /* Add back the time variable */
601
    add_time_variable() ;
8✔
602

603
    /* delete the current change_buffer contents */
604
    for ( drb_it = change_buffer.begin() ; drb_it != change_buffer.end() ; ++drb_it ) {
8✔
605
        delete *drb_it ;
×
606
    }
607
    change_buffer.clear() ;
8✔
608

609
    unsigned int jj ;
610
    /* add the variable names listed in the checkpoint file */
611
    for ( jj = 0 ; jj < num_variable_names ; jj++ ) {
22✔
612
        add_variable( variable_names[jj] , variable_alias[jj] ) ;
14✔
613
    }
614
    for ( jj = 0 ; jj < num_change_variable_names ; jj++ ) {
10✔
615
        add_change_variable( change_variable_names[jj] ) ;
2✔
616
    }
617

618
    clear_checkpoint_vars() ;
8✔
619

620
    // set the write job class to what is in the checkpoint file.
621
    write_job->job_class_name = job_class ;
8✔
622

623
    // reset the sim_object name.
624
    name = std::string("data_record_group_") + group_name ;
8✔
625

626
    /* call init to open the recording file and look up variable name addresses */
627
    init() ;
8✔
628

629
    // Account for the fact that the current time tics is actually already passed for a checkpoint.
630
    long long curr_tics = exec_get_time_tics();
8✔
631
    advance_log_tics_given_curr_tic(curr_tics);
8✔
632

633
    write_job->next_tics = calculate_next_logging_tic(curr_tics);
8✔
634
    write_job->cycle_tics = write_job->next_tics - curr_tics;
8✔
635
    write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
8✔
636

637
    return 0 ;
8✔
638
}
639

640
int Trick::DataRecordGroup::write_header() {
55✔
641

642
    unsigned int jj ;
643
    std::string header_name ;
110✔
644
    std::fstream out_stream ;
110✔
645

646
    /*! create the header file used by the GUIs */
647
    header_name = output_dir + "/log_" + group_name + ".header" ;
55✔
648

649
    out_stream.open(header_name.c_str(), std::fstream::out ) ;
55✔
650
    if ( ! out_stream  ||  ! out_stream.good() ) {
55✔
651
        return -1;
×
652
    }
653

654
    /* Header file first line is created in format specific header */
655
    out_stream << "log_" << group_name ;
55✔
656

657
    format_specific_header(out_stream) ;
55✔
658

659
    /* Output the file name, variable size, units, and variable name
660
     * to the rest of recorded data header file.
661
     * (e.g. file_name  C_type  units  sim_name)
662
     * Note: "sys.exec.out.time" should be the first variable in the buffer.
663
     */
664
    for (jj = 0; jj < rec_buffer.size() ; jj++) {
686✔
665
        /*! recording single data item */
666
        out_stream << "log_" << group_name << "\t"
631✔
667
            << type_string(rec_buffer[jj]->ref->attr->type,
1,262✔
668
                           rec_buffer[jj]->ref->attr->size) << "\t"
631✔
669
            << std::setw(6) ;
1,262✔
670

671
        if ( rec_buffer[jj]->ref->attr->mods & TRICK_MODS_UNITSDASHDASH ) {
631✔
672
            out_stream << "--" ;
4✔
673
        } else {
674
            out_stream << rec_buffer[jj]->ref->attr->units ;
627✔
675
        }
676
        out_stream << "\t" << rec_buffer[jj]->ref->reference << std::endl ;
631✔
677
    }
678

679
    // Send all unwritten characters in the buffer to its output/file.
680
    out_stream.flush() ;
55✔
681
    out_stream.close() ;
55✔
682

683
    return(0) ;
55✔
684

685
}
686

687
int Trick::DataRecordGroup::data_record(double in_time) {
4,754,222✔
688

689
    unsigned int jj ;
690
    unsigned int buffer_offset ;
691
    Trick::DataRecordBuffer * drb ;
692
    bool change_detected = false ;
4,754,222✔
693

694
    //TODO: does not handle bitfields correctly!
695
    if ( record == true ) {
4,754,222✔
696
        if ( freq != DR_Always ) {
4,754,222✔
697
            for (jj = 0; jj < change_buffer.size() ; jj++) {
1,644✔
698
                drb = change_buffer[jj] ;
822✔
699
                REF2 * ref = drb->ref ;
822✔
700
                if ( ref->pointer_present == 1 ) {
822✔
701
                    ref->address = follow_address_path(ref) ;
×
702
                }
703
                if ( memcmp( drb->buffer , drb->ref->address , drb->ref->attr->size) ) {
822✔
704
                    change_detected = true ;
28✔
705
                    memcpy( drb->buffer , drb->ref->address , drb->ref->attr->size) ;
28✔
706
                }
707
            }
708

709
        }
710

711
        if ( freq == DR_Always || change_detected == true ) {
4,754,222✔
712

713
            // If this is not the ring buffer and
714
            // we are going to have trouble fitting 2 data sets then write the data now.
715
            if ( buffer_type != DR_Ring_Buffer ) {
4,753,428✔
716
                if ( buffer_num - writer_num >= (max_num - 2) ) {
4,494,228✔
717
                    write_data(true) ;
×
718
                }
719
            }
720

721
            curr_time = in_time ;
4,753,428✔
722

723
            if ( freq == DR_Changes_Step ) {
4,753,428✔
724
                buffer_offset = buffer_num % max_num ;
×
725
                *((double *)(rec_buffer[0]->last_value)) = in_time ;
×
726
                for (jj = 0; jj < rec_buffer.size() ; jj++) {
×
727
                    drb = rec_buffer[jj] ;
×
728
                    REF2 * ref = drb->ref ;
×
729
                    int param_size = ref->attr->size ;
×
730
                    if ( buffer_offset == 0 ) {
×
731
                       drb->curr_buffer = drb->buffer ;
×
732
                    } else {
733
                       drb->curr_buffer += param_size ;
×
734
                    }
735
                    switch ( param_size ) {
×
736
                        case 8:
×
737
                            *(int64_t *)drb->curr_buffer = *(int64_t *)drb->last_value ;
×
738
                            break ;
×
739
                        case 4:
×
740
                            *(int32_t *)drb->curr_buffer = *(int32_t *)drb->last_value ;
×
741
                            break ;
×
742
                        case 2:
×
743
                            *(int16_t *)drb->curr_buffer = *(int16_t *)drb->last_value ;
×
744
                            break ;
×
745
                        case 1:
×
746
                            *(int8_t *)drb->curr_buffer = *(int8_t *)drb->last_value ;
×
747
                            break ;
×
748
                        default:
×
749
                            memcpy( drb->curr_buffer , drb->last_value , param_size ) ;
×
750
                            break ;
×
751
                    }
752
                }
753
                buffer_num++ ;
×
754
            }
755

756
            buffer_offset = buffer_num % max_num ;
4,753,428✔
757
            for (jj = 0; jj < rec_buffer.size() ; jj++) {
39,653,547✔
758
                drb = rec_buffer[jj] ;
34,900,119✔
759
                REF2 * ref = drb->ref ;
34,900,119✔
760
                if ( ref->pointer_present == 1 ) {
34,900,119✔
761
                    ref->address = follow_address_path(ref) ;
×
762
                }
763
                int param_size = ref->attr->size ;
34,900,119✔
764
                if ( buffer_offset == 0 ) {
34,900,119✔
765
                   drb->curr_buffer = drb->buffer ;
909✔
766
                } else {
767
                   drb->curr_buffer += param_size ;
34,899,210✔
768
                }
769
                /**
770
                 * While the typical idiom is something like:
771
                 * 1. previous_value = current_value
772
                 * 2. current_value = new_value
773
                 * That is incorrect here, as curr_buffer is a pointer that has already been
774
                 * incremented to the next value's location. We therefore set *curr_buffer and
775
                 * *last_value to the same value, which results in the DR_Changes_Step loop above
776
                 * correctly using this value as the first point of the step change on the next
777
                 * call to this function.
778
                 */
779
                switch ( param_size ) {
34,900,119✔
780
                    case 8:
33,945,133✔
781
                        *(int64_t *)drb->last_value = *(int64_t *)drb->curr_buffer = *(int64_t *)ref->address ;
33,945,133✔
782
                        break ;
33,945,133✔
783
                    case 4:
954,266✔
784
                        *(int32_t *)drb->last_value = *(int32_t *)drb->curr_buffer = *(int32_t *)ref->address ;
954,266✔
785
                        break ;
954,266✔
786
                    case 2:
650✔
787
                        *(int16_t *)drb->last_value = *(int16_t *)drb->curr_buffer = *(int16_t *)ref->address ;
650✔
788
                        break ;
650✔
789
                    case 1:
70✔
790
                        *(int8_t *)drb->last_value = *(int8_t *)drb->curr_buffer = *(int8_t *)ref->address ;
70✔
791
                        break ;
70✔
792
                    default:
×
793
                        memcpy( drb->curr_buffer , ref->address , param_size ) ;
×
794
                        memcpy( drb->last_value , drb->curr_buffer , param_size ) ;
×
795
                        break ;
×
796
                }
797
            }
798
            buffer_num++ ;
4,753,428✔
799
        }
800
    }
801

802
    long long curr_tics = (long long)round(in_time * Trick::JobData::time_tic_value);
4,754,222✔
803
    advance_log_tics_given_curr_tic(curr_tics);
4,754,222✔
804

805
    write_job->next_tics = calculate_next_logging_tic(curr_tics);
4,754,222✔
806
    write_job->cycle_tics = write_job->next_tics - curr_tics;
4,754,222✔
807
    write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
4,754,222✔
808

809
    return(0) ;
4,754,222✔
810
}
811

812
/**
813
 * Loop through the required logging rates and calculate the
814
 * next logging time in tics.
815
 * @return Next logging time in tics,
816
 */
817
long long Trick::DataRecordGroup::calculate_next_logging_tic(long long min_tic)
4,754,285✔
818
{
819
    long long ticOfCycleToProcess = std::numeric_limits<long long>::max();
4,754,285✔
820

821
    // Loop over all the logging rates. If the logging rate's next tic is equal to the min_tic, test against
822
    // that rate's next cycle from min. Find the smallest next tic 
823
    for(size_t cycleIndex = 0; cycleIndex < logging_rates.size(); ++cycleIndex)
26,788,738✔
824
    {
825
        long long logNextTic = logging_rates[cycleIndex].next_cycle_in_tics;
22,034,453✔
826

827
        if(logNextTic == min_tic)
22,034,453✔
828
        {
829
            logNextTic += logging_rates[cycleIndex].rate_in_tics;
59✔
830
        }
831

832
        if(logNextTic < ticOfCycleToProcess)
22,034,453✔
833
        {
834
            ticOfCycleToProcess = logNextTic;
15,511,119✔
835
        }
836
    }
837

838
    return ticOfCycleToProcess;
4,754,285✔
839
}
840

841
/**
842
 * Loop through the required logging rates and advance the next cycle tics of matching rates
843
 * @param curr_tic_in - time in tics to match and advance the next cycle tic
844
 */
845
void Trick::DataRecordGroup::advance_log_tics_given_curr_tic(long long curr_tic_in)
4,754,230✔
846
{
847
    for(size_t cycleIndex = 0; cycleIndex < logging_rates.size(); ++cycleIndex)
26,788,622✔
848
    {
849
        long long & logNextTic = logging_rates[cycleIndex].next_cycle_in_tics;
22,034,392✔
850

851
        while(logNextTic <= curr_tic_in)
31,286,855✔
852
        {
853
            logNextTic += logging_rates[cycleIndex].rate_in_tics;
9,252,463✔
854
        }
855
    }
856
}
4,754,230✔
857

858
int Trick::DataRecordGroup::write_data(bool must_write) {
407,828✔
859

860
    unsigned int local_buffer_num ;
861
    unsigned int num_to_write ;
862
    unsigned int writer_offset ;
863

864
    if ( record and inited and (buffer_type == DR_No_Buffer or must_write) and (total_bytes_written <= max_file_size)) {
407,828✔
865

866
        // buffer_mutex is used in this one place to prevent forced calls of write_data
867
        // to not overwrite data being written by the asynchronous thread.
868
        pthread_mutex_lock(&buffer_mutex) ;
43,389✔
869

870
        local_buffer_num = buffer_num ;
43,389✔
871
        if ( (local_buffer_num - writer_num) > max_num ) {
43,389✔
872
            num_to_write = max_num ;
×
873
        } else {
874
            num_to_write = (local_buffer_num - writer_num) ;
43,389✔
875
        }
876
        writer_num = local_buffer_num - num_to_write ;
43,389✔
877

878
        //! This loop pulls a "row" of time homogeneous data and writes it to the file
879
        while ( writer_num != local_buffer_num ) {
4,796,817✔
880

881
            writer_offset = writer_num % max_num ;
4,753,428✔
882
            //! keep record of bytes written to file. Default max is 1GB
883
            total_bytes_written += format_specific_write_data(writer_offset) ;
4,753,428✔
884
            writer_num++ ;
4,753,428✔
885

886
        }
887

888
        if(!max_size_warning && (total_bytes_written > max_file_size)) {
43,389✔
889
            std::cerr << "WARNING: Data record max file size " << (static_cast<double>(max_file_size))/(1<<20) << "MB reached.\n"
×
890
            "https://nasa.github.io/trick/documentation/simulation_capabilities/Data-Record#changing-the-max-file-size-of-a-data-record-group-ascii-and-binary-only" 
×
891
            << std::endl;
×
892
            max_size_warning = true;
×
893
        }
894

895
        pthread_mutex_unlock(&buffer_mutex) ;
43,389✔
896

897
    }
898

899
    return 0 ;
407,826✔
900
}
901

902
int Trick::DataRecordGroup::enable() {
36✔
903
    record = true ;
36✔
904
    return(0) ;
36✔
905
}
906

907
int Trick::DataRecordGroup::disable() {
×
908
    record = false ;
×
909
    return(0) ;
×
910
}
911

912
int Trick::DataRecordGroup::shutdown() {
52✔
913

914
    // Force write out all data
915
    record = true ; // If user disabled group, make sure any recorded data gets written out
52✔
916
    write_data(true) ;
52✔
917
    format_specific_shutdown() ;
52✔
918

919
    remove_all_variables();
52✔
920

921
    // remove_all_variables does not remove sim time
922
    if(!rec_buffer.empty()){
52✔
923
        delete rec_buffer[0];
52✔
924
        rec_buffer.clear();
52✔
925
    }
926

927
    if ( writer_buff ) {
52✔
928
        free(writer_buff) ;
52✔
929
        writer_buff = NULL ;
52✔
930
    }
931

932
    return 0 ;
52✔
933
}
934

935
std::string Trick::DataRecordGroup::type_string( int item_type, int item_size ) {
631✔
936
    switch (item_type) {
631✔
937
        case TRICK_CHARACTER:
2✔
938
                return "char";
2✔
939
                break;
940
        case TRICK_UNSIGNED_CHARACTER:
2✔
941
                return "unsigned_char";
2✔
942
                break;
943
        case TRICK_STRING:
×
944
                return "string";
×
945
                break;
946
        case TRICK_SHORT:
5✔
947
                return "short";
5✔
948
                break;
949
        case TRICK_UNSIGNED_SHORT:
5✔
950
                return "unsigned_short";
5✔
951
                break;
952
        case TRICK_ENUMERATED:
73✔
953
        case TRICK_INTEGER:
954
                return "int";
73✔
955
                break;
956
        case TRICK_UNSIGNED_INTEGER:
5✔
957
                return "unsigned_int";
5✔
958
                break;
959
        case TRICK_LONG:
5✔
960
                return "long";
5✔
961
                break;
962
        case TRICK_UNSIGNED_LONG:
5✔
963
                return "unsigned_long";
5✔
964
                break;
965
        case TRICK_FLOAT:
5✔
966
                return "float";
5✔
967
                break;
968
        case TRICK_DOUBLE:
457✔
969
                if ( single_prec_only ) {
457✔
970
                        return "float";
×
971
                }
972
                else {
973
                        return "double";
457✔
974
                }
975
                break;
976
        case TRICK_BITFIELD:
28✔
977
                if (item_size == sizeof(int)) {
28✔
978
                        return "int";
28✔
979
                } else if (item_size == sizeof(short)) {
×
980
                        return "short";
×
981
                } else {
982
                        return "char";
×
983
                }
984
                break;
985
        case TRICK_UNSIGNED_BITFIELD:
28✔
986
                if (item_size == sizeof(int)) {
28✔
987
                        return "unsigned_int";
28✔
988
                } else if (item_size == sizeof(short)) {
×
989
                        return "unsigned_short";
×
990
                } else {
991
                        return "unsigned_char";
×
992
                }
993
                break;
994
        case TRICK_LONG_LONG:
5✔
995
                return "long_long";
5✔
996
                break;
997
        case TRICK_UNSIGNED_LONG_LONG:
2✔
998
                return "unsigned_long_long";
2✔
999
                break;
1000
        case TRICK_BOOLEAN:
4✔
1001
#if ( __sun | __APPLE__ )
1002
                return "int";
1003
#else
1004
                return "unsigned_char";
4✔
1005
#endif
1006
                break;
1007
    }
1008
    return "";
×
1009
}
1010

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc