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

nasa / trick / 20583394754

29 Dec 2025 09:46PM UTC coverage: 55.575% (-0.02%) from 55.591%
20583394754

Pull #2017

github

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

22 of 27 new or added lines in 1 file covered. (81.48%)

2 existing lines in 1 file now uncovered.

12505 of 22501 relevant lines covered (55.58%)

295011.64 hits per line

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

82.9
/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)
705✔
65
{
66
    long long curr_tic = exec_get_time_tics();
705✔
67
    rate_in_seconds = rate_in;
705✔
68
    long long cycle_tics = (long long)round(rate_in * Trick::JobData::time_tic_value);
705✔
69
    rate_in_tics = cycle_tics;
705✔
70
    if((curr_tic % cycle_tics) != 0)
705✔
71
    {
NEW
72
        next_cycle_in_tics = (curr_tic/cycle_tics) * cycle_tics + cycle_tics;
×
73
    } else 
74
    {
75
        next_cycle_in_tics = curr_tic;
705✔
76
    }
77
}
705✔
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(bool is_restart) {
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
    if(!is_restart)
55✔
444
    {
445
        long long curr_tics = exec_get_time_tics();
47✔
446
        int tic_value = exec_get_time_tic_value();
47✔
447

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

464
            /* Calculate the if the cycle_tics would be a whole number  */
465
            double test_rem = fmod(logging_rate * (double)tic_value, 1.0);
52✔
466

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

480
            // We've checked that the rate is achievable. Call set_rate again to make sure the latest time_tic_value
481
            // was utilized for calculated next tics
482
            logging_rates[ii].set_rate(logging_rate);
52✔
483
        }
484

485
        write_job->next_tics = curr_tics;
47✔
486

487
        long long next_next_tics = calculate_next_logging_tic(write_job->next_tics);
47✔
488
        write_job->cycle_tics = next_next_tics - curr_tics;
47✔
489
        write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
47✔
490
    }
491

492
    // set the inited flag to true when all initialization is done
493
    if ( ret == 0 ) {
55✔
494
        inited = true ;
55✔
495
    }
496

497
    return(0) ;
55✔
498

499
}
500

501
void Trick::DataRecordGroup::configure_jobs(DR_Type type) {
607✔
502
    switch(type) {
607✔
503
    default:
50✔
504
        // run the restart job in phase 60001
505
        add_job(0, 5, (char *)"restart", NULL, 1.0, (char *)"restart", (char *)"TRK", 60001) ;
50✔
506

507
    case DR_Type::DR_Type_FrameLogDataRecord:
607✔
508
        // add_jobs_to_queue will fill in job_id later
509
        // make the init job run after all other initialization jobs but before the post init checkpoint
510
        // job so users can allocate memory in initialization jobs and checkpointing data rec groups will work
511
        add_job(0, 1, (char *)"initialization", NULL, cycle, (char *)"init", (char *)"TRK", 65534) ;
607✔
512
        add_job(0, 2, (char *)"end_of_frame", NULL, 1.0, (char *)"write_data", (char *)"TRK") ;
607✔
513
        add_job(0, 3, (char *)"checkpoint", NULL, 1.0, (char *)"checkpoint", (char *)"TRK") ;
607✔
514
        add_job(0, 4, (char *)"post_checkpoint", NULL, 1.0, (char *)"clear_checkpoint_vars", (char *)"TRK") ;
607✔
515
        add_job(0, 6, (char *)"shutdown", NULL, 1.0, (char *)"shutdown", (char *)"TRK") ;
607✔
516

517
        write_job = add_job(0, 99, (char *)job_class.c_str(), NULL, cycle, (char *)"data_record" , (char *)"TRK") ;
607✔
518
        break ;
607✔
519
    }
520
    write_job->set_system_job_class(true);
607✔
521
}
607✔
522

523
int Trick::DataRecordGroup::checkpoint() {
12✔
524
    unsigned int jj ;
525

526
    /*
527
       Save the names of the variables and the aliases to the checkpoint,
528
       the rest of the DataRecordBuffer will be reconstructed during restart.
529
       The first variable is time which we do not have to save.
530
     */
531
    if ( rec_buffer.size() > 1 ) {
12✔
532
        num_variable_names = rec_buffer.size() - 1 ;
12✔
533
        variable_names = (char **)TMM_declare_var_1d("char *", (int)rec_buffer.size() - 1) ;
12✔
534
        variable_alias = (char **)TMM_declare_var_1d("char *", (int)rec_buffer.size() - 1) ;
12✔
535

536
        for (jj = 1; jj < rec_buffer.size() ; jj++) {
58✔
537
            Trick::DataRecordBuffer * drb = rec_buffer[jj] ;
46✔
538

539
            variable_names[jj-1] = TMM_strdup((char *)drb->name.c_str()) ;
46✔
540
            variable_alias[jj-1] = TMM_strdup((char *)drb->alias.c_str()) ;
46✔
541
        }
542
    }
543

544
    /*
545
       Save the names of the change variables and the aliases to the checkpoint,
546
       the rest of the DataRecordBuffer will be reconstructed during restart
547
     */
548
    if ( change_buffer.size() > 0 ) {
12✔
549
        num_change_variable_names = change_buffer.size() ;
2✔
550
        change_variable_names = (char **)TMM_declare_var_1d("char *", (int)change_buffer.size()) ;
2✔
551
        change_variable_alias = (char **)TMM_declare_var_1d("char *", (int)change_buffer.size()) ;
2✔
552

553
        for (jj = 0; jj < change_buffer.size() ; jj++) {
4✔
554
            Trick::DataRecordBuffer * drb = change_buffer[jj] ;
2✔
555

556
            change_variable_names[jj] = TMM_strdup((char *)drb->name.c_str()) ;
2✔
557
            change_variable_alias[jj] = TMM_strdup((char *)drb->alias.c_str()) ;
2✔
558
        }
559
    }
560

561
    return 0 ;
12✔
562
}
563

564
void Trick::DataRecordGroup::clear_checkpoint_vars() {
50✔
565
    
566
    if ( variable_names ) {
50✔
567
        for(unsigned int jj = 0; jj < num_variable_names; jj++) {
80✔
568
            TMM_delete_var_a(variable_names[jj]);
60✔
569
        }
570
        TMM_delete_var_a(variable_names) ;
20✔
571
    }
572

573
    if ( variable_alias ) {
50✔
574
        for(unsigned int jj = 0; jj < num_variable_names; jj++) {
80✔
575
            TMM_delete_var_a(variable_alias[jj]);
60✔
576
        }
577
        TMM_delete_var_a(variable_alias) ;
20✔
578
    }
579

580
    if ( change_variable_names ) {
50✔
581
        for(unsigned int jj = 0; jj < num_change_variable_names; jj++) {
8✔
582
            TMM_delete_var_a(change_variable_names[jj]);
4✔
583
        }
584
        TMM_delete_var_a(change_variable_names) ;
4✔
585
    }
586

587
    if ( change_variable_alias ) {
50✔
588
        for(unsigned int jj = 0; jj < num_change_variable_names; jj++) {
8✔
589
            TMM_delete_var_a(change_variable_alias[jj]);
4✔
590
        }
591
        TMM_delete_var_a(change_variable_alias) ;
4✔
592
    }
593

594
    variable_names = NULL ;
50✔
595
    variable_alias = NULL ;
50✔
596
    change_variable_names = NULL ;
50✔
597
    change_variable_alias = NULL ;
50✔
598
    num_variable_names = 0 ;
50✔
599
    num_change_variable_names = 0 ;
50✔
600
}
50✔
601

602
int Trick::DataRecordGroup::restart() {
8✔
603
    std::vector <Trick::DataRecordBuffer *>::iterator drb_it ;
8✔
604

605
    /* delete the current rec_buffer */
606
    for ( drb_it = rec_buffer.begin() ; drb_it != rec_buffer.end() ; ++drb_it ) {
16✔
607
        delete *drb_it ;
8✔
608
    }
609
    rec_buffer.clear() ;
8✔
610
    /* Add back the time variable */
611
    add_time_variable() ;
8✔
612

613
    /* delete the current change_buffer contents */
614
    for ( drb_it = change_buffer.begin() ; drb_it != change_buffer.end() ; ++drb_it ) {
8✔
615
        delete *drb_it ;
×
616
    }
617
    change_buffer.clear() ;
8✔
618

619
    unsigned int jj ;
620
    /* add the variable names listed in the checkpoint file */
621
    for ( jj = 0 ; jj < num_variable_names ; jj++ ) {
22✔
622
        add_variable( variable_names[jj] , variable_alias[jj] ) ;
14✔
623
    }
624
    for ( jj = 0 ; jj < num_change_variable_names ; jj++ ) {
10✔
625
        add_change_variable( change_variable_names[jj] ) ;
2✔
626
    }
627

628
    clear_checkpoint_vars() ;
8✔
629

630
    // set the write job class to what is in the checkpoint file.
631
    write_job->job_class_name = job_class ;
8✔
632

633
    // reset the sim_object name.
634
    name = std::string("data_record_group_") + group_name ;
8✔
635

636
    /* call init to open the recording file and look up variable name addresses */
637
    init(true) ;
8✔
638

639
    return 0 ;
8✔
640
}
641

642
int Trick::DataRecordGroup::write_header() {
55✔
643

644
    unsigned int jj ;
645
    std::string header_name ;
110✔
646
    std::fstream out_stream ;
110✔
647

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

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

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

659
    format_specific_header(out_stream) ;
55✔
660

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

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

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

685
    return(0) ;
55✔
686

687
}
688

689
int Trick::DataRecordGroup::data_record(double in_time) {
4,754,222✔
690

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

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

711
        }
712

713
        if ( freq == DR_Always || change_detected == true ) {
4,754,222✔
714

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

723
            curr_time = in_time ;
4,753,428✔
724

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

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

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

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

811
    return(0) ;
4,754,222✔
812
}
813

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

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

829
        if(logNextTic == min_tic)
22,034,435✔
830
        {
831
            logNextTic += logging_rates[cycleIndex].rate_in_tics;
52✔
832
        }
833

834
        if(logNextTic < ticOfCycleToProcess)
22,034,435✔
835
        {
836
            ticOfCycleToProcess = logNextTic;
15,511,103✔
837
        }
838
    }
839

840
    return ticOfCycleToProcess;
4,754,269✔
841
}
842

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

853
        while(logNextTic <= curr_tic_in)
31,286,839✔
854
        {
855
            logNextTic += logging_rates[cycleIndex].rate_in_tics;
9,252,456✔
856
        }
857
    }
858
}
4,754,222✔
859

860
int Trick::DataRecordGroup::write_data(bool must_write) {
402,620✔
861

862
    unsigned int local_buffer_num ;
863
    unsigned int num_to_write ;
864
    unsigned int writer_offset ;
865

866
    if ( record and inited and (buffer_type == DR_No_Buffer or must_write) and (total_bytes_written <= max_file_size)) {
402,620✔
867

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

872
        local_buffer_num = buffer_num ;
38,192✔
873
        if ( (local_buffer_num - writer_num) > max_num ) {
38,192✔
874
            num_to_write = max_num ;
×
875
        } else {
876
            num_to_write = (local_buffer_num - writer_num) ;
38,192✔
877
        }
878
        writer_num = local_buffer_num - num_to_write ;
38,192✔
879

880
        //! This loop pulls a "row" of time homogeneous data and writes it to the file
881
        while ( writer_num != local_buffer_num ) {
4,791,620✔
882

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

888
        }
889

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

897
        pthread_mutex_unlock(&buffer_mutex) ;
38,192✔
898

899
    }
900

901
    return 0 ;
402,628✔
902
}
903

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

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

914
int Trick::DataRecordGroup::shutdown() {
52✔
915

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

921
    remove_all_variables();
52✔
922

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

929
    if ( writer_buff ) {
52✔
930
        free(writer_buff) ;
52✔
931
        writer_buff = NULL ;
52✔
932
    }
933

934
    return 0 ;
52✔
935
}
936

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

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

© 2026 Coveralls, Inc