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

nasa / trick / 28043519565

23 Jun 2026 05:13PM UTC coverage: 56.232% (+0.1%) from 56.126%
28043519565

Pull #2154

github

web-flow
Merge 87d8bac31 into 4a137dd0c
Pull Request #2154: Add data recording support for STL (vector, deque, and array) of supported data types.

120 of 145 new or added lines in 5 files covered. (82.76%)

2 existing lines in 1 file now uncovered.

14722 of 26181 relevant lines covered (56.23%)

481357.37 hits per line

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

83.4
/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/ReferenceUtils.hh"
17
#include "trick/command_line_protos.h"
18
#include "trick/exec_proto.h"
19
#include "trick/memorymanager_c_intf.h"
20
#include "trick/message_proto.h"
21
#include "trick/message_type.h"
22
#include "trick/reference.h"
23

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

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

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

47
Trick::DataRecordBuffer::~DataRecordBuffer() {
672✔
48
    if ( buffer ) {
672✔
49
        free(buffer) ;
657✔
50
    }
51
    if ( last_value ) {
672✔
52
        free(last_value) ;
660✔
53
    }
54

55
    ref_free(ref) ;
672✔
56
    free(ref) ;
672✔
57
}
672✔
58

59

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

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

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

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

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

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

122
    configure_jobs(dr_type) ;
624✔
123

124
    add_time_variable() ;
624✔
125

126
    logging_rates.emplace_back(cycle);
624✔
127
}
624✔
128

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

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

135
    int ret = 0 ;
5,119,596✔
136

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

161
    return(ret) ;
5,119,596✔
162

163
}
164

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

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

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

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

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

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

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

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

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

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

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

231
int Trick::DataRecordGroup::set_thread( unsigned int in_thread_id ) {
204✔
232

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

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

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

249
int Trick::DataRecordGroup::add_time_variable() {
666✔
250
    REF2 * new_ref ;
251

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

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

264
    return 0 ;
666✔
265
}
266

267
int Trick::DataRecordGroup::add_variable( std::string in_name , std::string alias ) {
1,002✔
268

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

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

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

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

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

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

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

320
    change_buffer.clear();
57✔
321
}
57✔
322

323
int Trick::DataRecordGroup::add_variable( REF2 * ref2 ) {
4,770✔
324

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

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

337
    return(0) ;
4,770✔
338

339
}
340

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

343
    REF2 * ref2 ;
344

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

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

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

360
    return(0) ;
6✔
361

362
}
363

364
bool Trick::DataRecordGroup::isSupportedType(REF2 * ref2, std::string& message) {
582✔
365
    // For STL-indexed paths the effective type is the element/member type, not TRICK_STL.
366
    TRICK_TYPE eff_type = Trick::ReferenceUtils::effective_trick_type(ref2);
582✔
367

368
    // Reject bare (unindexed) STL containers — user must index, e.g. vec[0]
369
    if (ref2->attr->type == TRICK_STL && !Trick::ReferenceUtils::is_stl_ref(ref2))
582✔
370
    {
NEW
371
        message = "Cannot Data Record bare STL container " + std::string(ref2->reference)
×
NEW
372
            + " (use indexed access, e.g. " + std::string(ref2->reference) + "[0])";
×
UNCOV
373
        return false;
×
374
    }
375

376
    if (eff_type == TRICK_STRING || eff_type == TRICK_WSTRING || eff_type == TRICK_STL || eff_type == TRICK_STRUCTURED
582✔
377
        || eff_type == TRICK_NUMBER_OF_TYPES)
580✔
378
    {
379
        message = "Cannot Data Record variable " + std::string(ref2->reference) + " of unsupported type "
4✔
380
            + std::to_string(eff_type);
6✔
381
        return false;
2✔
382
    }
383

384
    // For STL-present paths skip the array dimension check — num_index/attr->num_index
385
    // semantics do not apply when an STL container was indexed anywhere in the path.
386
    if (!Trick::ReferenceUtils::is_stl_ref(ref2) && ref2->num_index != ref2->attr->num_index)
580✔
387
    {
388
        message = "Cannot Data Record arrayed variable " + std::string(ref2->reference);
4✔
389
        return false;
4✔
390
    }
391

392
    return true;
576✔
393
}
394

395
/**
396
@details
397
-# The simulation output directory is retrieved from the CommandLineArguments
398
-# The log header file is created
399
   -# The endianness of the log file is written to the log header.
400
   -# The names of the parameters contained in the log file are written to the header.
401
-# Memory buffers are allocated to store simulation data
402
-# The DataRecordGroupObject (a derived SimObject) is added to the Scheduler.
403
*/
404
int Trick::DataRecordGroup::init(bool is_restart) {
60✔
405

406
    unsigned int jj ;
407
    int ret ;
408

409
    // reset counter here so we can "re-init" our recording
410
    buffer_num = writer_num = total_bytes_written = 0 ;
60✔
411

412
    output_dir = command_line_args_get_output_dir() ;
60✔
413
    /* this is the common part of the record file name, the format specific will add the correct suffix */
414
    file_name = output_dir + "/log_" + group_name ;
60✔
415

416
    pthread_mutex_init(&buffer_mutex, NULL);
60✔
417

418
    // Allocate recording space for time.
419
    rec_buffer[0]->buffer = (char *)calloc(max_num , rec_buffer[0]->ref->attr->size) ;
60✔
420
    rec_buffer[0]->last_value = (char *)calloc(1 , rec_buffer[0]->ref->attr->size) ;
60✔
421

422
    /* Loop through all variables looking up names.  Allocate recording space
423
       according to size of the variable */
424
    for (jj = 1; jj < rec_buffer.size() ; jj++) {
680✔
425
        Trick::DataRecordBuffer * drb = rec_buffer[jj] ;
620✔
426
        if ( drb->ref_searched == false ) {
620✔
427
            REF2 * ref2 ;
428

429
            ref2 = ref_attributes(drb->name.c_str()) ;
582✔
430
            if ( ref2 == NULL || ref2->attr == NULL ) {
582✔
431
                message_publish(MSG_WARNING, "Could not find Data Record variable %s.\n", drb->name.c_str()) ;
×
432
                rec_buffer.erase(rec_buffer.begin() + jj--) ;
×
433
                delete drb ;
×
434
                continue ;
×
435
            } else {
436
                std::string message;
582✔
437
                if (!isSupportedType(ref2, message)) {
582✔
438
                    message_publish(MSG_WARNING, "%s\n", message.c_str()) ;
6✔
439
                    rec_buffer.erase(rec_buffer.begin() + jj--) ;
6✔
440
                    delete drb ;
6✔
441
                    continue ;
6✔
442
                } else {
443
                    drb->ref = ref2 ;
576✔
444
                }
445
            }
582✔
446
        }
447
        if ( drb->alias.compare("") ) {
614✔
448
            drb->ref->reference = strdup(drb->alias.c_str()) ;
16✔
449
        }
450
        size_t elem_size  = Trick::ReferenceUtils::effective_trick_size(drb->ref);
614✔
451
        drb->last_value   = (char*)calloc(1, elem_size);
614✔
452
        drb->buffer       = (char*)calloc(max_num, elem_size);
614✔
453
        drb->ref_searched = true ;
614✔
454
    }
455

456
    write_header() ;
60✔
457

458
    // call format specific initialization to open destination and write header
459
    ret = format_specific_init() ;
60✔
460

461
    if(!is_restart)
60✔
462
    {
463
        long long curr_tics = exec_get_time_tics();
51✔
464
        int tic_value = exec_get_time_tic_value();
51✔
465

466
        for(size_t ii = 0; ii < logging_rates.size(); ++ii)
107✔
467
        {
468
            double logging_rate = logging_rates[ii].rate_in_seconds;
56✔
469
            if(logging_rate < (1.0 / tic_value))
56✔
470
            {
471
                message_publish(
×
472
                    MSG_ERROR,
473
                    "DataRecordGroup ERROR: Cycle for %lu logging rate idx is less than time tic value. cycle = "
474
                    "%16.12f, time_tic = %16.12f\n",
475
                    ii,
476
                    logging_rate,
477
                    tic_value);
478
                ret = -1;
×
479
            }
480
            long long cycle_tics = (long long)round(logging_rate * Trick::JobData::time_tic_value);
56✔
481

482
            /* Calculate the if the cycle_tics would be a whole number  */
483
            double test_rem = fmod(logging_rate * (double)tic_value, 1.0);
56✔
484

485
            if(test_rem > 0.001)
56✔
486
            {
487
                message_publish(MSG_WARNING,
×
488
                                "DataRecordGroup ERROR: Cycle for %lu logging rate idx cannot be exactly scheduled "
489
                                "with time tic value. "
490
                                "cycle = %16.12f, cycle_tics = %lld , time_tic = %16.12f\n",
491
                                ii,
492
                                logging_rate,
493
                                cycle_tics,
494
                                1.0 / tic_value);
495
                ret = -1;
×
496
            }
497

498
            // We've checked that the rate is achievable. Call set_rate again to make sure the latest time_tic_value
499
            // was utilized for calculated next tics
500
            logging_rates[ii].set_rate(logging_rate);
56✔
501
        }
502

503
        write_job->next_tics = curr_tics;
51✔
504

505
        long long next_next_tics = calculate_next_logging_tic(write_job->next_tics);
51✔
506
        write_job->cycle_tics = next_next_tics - curr_tics;
51✔
507
        write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
51✔
508
    }
509

510
    // set the inited flag to true when all initialization is done
511
    if ( ret == 0 ) {
60✔
512
        inited = true ;
60✔
513
    }
514

515
    return(0) ;
60✔
516

517
}
518

519
void Trick::DataRecordGroup::configure_jobs(DR_Type type) {
624✔
520
    switch(type) {
624✔
521
    default:
55✔
522
        // run the restart job in phase 60001
523
        add_job(0, 5, (char *)"restart", NULL, 1.0, (char *)"restart", (char *)"TRK", 60001) ;
55✔
524

525
    case DR_Type::DR_Type_FrameLogDataRecord:
624✔
526
        // add_jobs_to_queue will fill in job_id later
527
        // make the init job run after all other initialization jobs but before the post init checkpoint
528
        // job so users can allocate memory in initialization jobs and checkpointing data rec groups will work
529
        add_job(0, 1, (char *)"initialization", NULL, cycle, (char *)"init", (char *)"TRK", 65534) ;
624✔
530
        add_job(0, 2, (char *)"end_of_frame", NULL, 1.0, (char *)"write_data", (char *)"TRK") ;
624✔
531
        add_job(0, 3, (char *)"checkpoint", NULL, 1.0, (char *)"checkpoint", (char *)"TRK") ;
624✔
532
        add_job(0, 4, (char *)"post_checkpoint", NULL, 1.0, (char *)"clear_checkpoint_vars", (char *)"TRK") ;
624✔
533
        add_job(0, 6, (char *)"shutdown", NULL, 1.0, (char *)"shutdown", (char *)"TRK") ;
624✔
534

535
        write_job = add_job(0, 99, (char *)job_class.c_str(), NULL, cycle, (char *)"data_record" , (char *)"TRK") ;
624✔
536
        break ;
624✔
537
    }
538
    write_job->set_system_job_class(true);
624✔
539
}
624✔
540

541
int Trick::DataRecordGroup::checkpoint() {
13✔
542
    unsigned int jj ;
543

544
    /*
545
       Save the names of the variables and the aliases to the checkpoint,
546
       the rest of the DataRecordBuffer will be reconstructed during restart.
547
       The first variable is time which we do not have to save.
548
     */
549
    if ( rec_buffer.size() > 1 ) {
13✔
550
        num_variable_names = rec_buffer.size() - 1 ;
13✔
551
        variable_names = (char **)TMM_declare_var_1d("char *", (int)rec_buffer.size() - 1) ;
13✔
552
        variable_alias = (char **)TMM_declare_var_1d("char *", (int)rec_buffer.size() - 1) ;
13✔
553

554
        for (jj = 1; jj < rec_buffer.size() ; jj++) {
61✔
555
            Trick::DataRecordBuffer * drb = rec_buffer[jj] ;
48✔
556

557
            variable_names[jj-1] = TMM_strdup((char *)drb->name.c_str()) ;
48✔
558
            variable_alias[jj-1] = TMM_strdup((char *)drb->alias.c_str()) ;
48✔
559
        }
560
    }
561

562
    /*
563
       Save the names of the change variables and the aliases to the checkpoint,
564
       the rest of the DataRecordBuffer will be reconstructed during restart
565
     */
566
    if ( change_buffer.size() > 0 ) {
13✔
567
        num_change_variable_names = change_buffer.size() ;
2✔
568
        change_variable_names = (char **)TMM_declare_var_1d("char *", (int)change_buffer.size()) ;
2✔
569
        change_variable_alias = (char **)TMM_declare_var_1d("char *", (int)change_buffer.size()) ;
2✔
570

571
        for (jj = 0; jj < change_buffer.size() ; jj++) {
4✔
572
            Trick::DataRecordBuffer * drb = change_buffer[jj] ;
2✔
573

574
            change_variable_names[jj] = TMM_strdup((char *)drb->name.c_str()) ;
2✔
575
            change_variable_alias[jj] = TMM_strdup((char *)drb->alias.c_str()) ;
2✔
576
        }
577
    }
578

579
    return 0 ;
13✔
580
}
581

582
void Trick::DataRecordGroup::clear_checkpoint_vars() {
55✔
583
    
584
    if ( variable_names ) {
55✔
585
        for(unsigned int jj = 0; jj < num_variable_names; jj++) {
86✔
586
            TMM_delete_var_a(variable_names[jj]);
64✔
587
        }
588
        TMM_delete_var_a(variable_names) ;
22✔
589
    }
590

591
    if ( variable_alias ) {
55✔
592
        for(unsigned int jj = 0; jj < num_variable_names; jj++) {
86✔
593
            TMM_delete_var_a(variable_alias[jj]);
64✔
594
        }
595
        TMM_delete_var_a(variable_alias) ;
22✔
596
    }
597

598
    if ( change_variable_names ) {
55✔
599
        for(unsigned int jj = 0; jj < num_change_variable_names; jj++) {
8✔
600
            TMM_delete_var_a(change_variable_names[jj]);
4✔
601
        }
602
        TMM_delete_var_a(change_variable_names) ;
4✔
603
    }
604

605
    if ( change_variable_alias ) {
55✔
606
        for(unsigned int jj = 0; jj < num_change_variable_names; jj++) {
8✔
607
            TMM_delete_var_a(change_variable_alias[jj]);
4✔
608
        }
609
        TMM_delete_var_a(change_variable_alias) ;
4✔
610
    }
611

612
    variable_names = NULL ;
55✔
613
    variable_alias = NULL ;
55✔
614
    change_variable_names = NULL ;
55✔
615
    change_variable_alias = NULL ;
55✔
616
    num_variable_names = 0 ;
55✔
617
    num_change_variable_names = 0 ;
55✔
618
}
55✔
619

620
int Trick::DataRecordGroup::restart() {
9✔
621
    std::vector <Trick::DataRecordBuffer *>::iterator drb_it ;
9✔
622

623
    /* delete the current rec_buffer */
624
    for ( drb_it = rec_buffer.begin() ; drb_it != rec_buffer.end() ; ++drb_it ) {
18✔
625
        delete *drb_it ;
9✔
626
    }
627
    rec_buffer.clear() ;
9✔
628
    /* Add back the time variable */
629
    add_time_variable() ;
9✔
630

631
    /* delete the current change_buffer contents */
632
    for ( drb_it = change_buffer.begin() ; drb_it != change_buffer.end() ; ++drb_it ) {
9✔
633
        delete *drb_it ;
×
634
    }
635
    change_buffer.clear() ;
9✔
636

637
    unsigned int jj ;
638
    /* add the variable names listed in the checkpoint file */
639
    for ( jj = 0 ; jj < num_variable_names ; jj++ ) {
25✔
640
        add_variable( variable_names[jj] , variable_alias[jj] ) ;
80✔
641
    }
642
    for ( jj = 0 ; jj < num_change_variable_names ; jj++ ) {
11✔
643
        add_change_variable( change_variable_names[jj] ) ;
6✔
644
    }
645

646
    clear_checkpoint_vars() ;
9✔
647

648
    // set the write job class to what is in the checkpoint file.
649
    write_job->job_class_name = job_class ;
9✔
650

651
    // reset the sim_object name.
652
    name = std::string("data_record_group_") + group_name ;
18✔
653

654
    /* call init to open the recording file and look up variable name addresses */
655
    init(true) ;
9✔
656

657
    return 0 ;
9✔
658
}
659

660
int Trick::DataRecordGroup::write_header() {
60✔
661

662
    unsigned int jj ;
663
    std::string header_name ;
60✔
664
    std::fstream out_stream ;
60✔
665

666
    /*! create the header file used by the GUIs */
667
    header_name = output_dir + "/log_" + group_name + ".header" ;
60✔
668

669
    out_stream.open(header_name.c_str(), std::fstream::out ) ;
60✔
670
    if ( ! out_stream  ||  ! out_stream.good() ) {
60✔
671
        return -1;
×
672
    }
673

674
    /* Header file first line is created in format specific header */
675
    out_stream << "log_" << group_name ;
60✔
676

677
    format_specific_header(out_stream) ;
60✔
678

679
    /* Output the file name, variable size, units, and variable name
680
     * to the rest of recorded data header file.
681
     * (e.g. file_name  C_type  units  sim_name)
682
     * Note: "sys.exec.out.time" should be the first variable in the buffer.
683
     */
684
    for (jj = 0; jj < rec_buffer.size() ; jj++) {
734✔
685
        /*! recording single data item */
686
        out_stream << "log_" << group_name << "\t"
674✔
687
                   << type_string((int)Trick::ReferenceUtils::effective_trick_type(rec_buffer[jj]->ref),
1,348✔
688
                                  (int)Trick::ReferenceUtils::effective_trick_size(rec_buffer[jj]->ref))
674✔
689
                   << "\t" << std::setw(6);
1,348✔
690

691
        if ( rec_buffer[jj]->ref->attr->mods & TRICK_MODS_UNITSDASHDASH ) {
674✔
692
            out_stream << "--" ;
4✔
693
        } else {
694
            out_stream << rec_buffer[jj]->ref->attr->units ;
670✔
695
        }
696
        out_stream << "\t" << rec_buffer[jj]->ref->reference << std::endl ;
674✔
697
    }
698

699
    // Send all unwritten characters in the buffer to its output/file.
700
    out_stream.flush() ;
60✔
701
    out_stream.close() ;
60✔
702

703
    return(0) ;
60✔
704

705
}
60✔
706

707
int Trick::DataRecordGroup::data_record(double in_time) {
4,754,400✔
708

709
    unsigned int jj ;
710
    unsigned int buffer_offset ;
711
    Trick::DataRecordBuffer * drb ;
712
    bool change_detected = false ;
4,754,400✔
713

714
    //TODO: does not handle bitfields correctly!
715
    if ( record == true ) {
4,754,400✔
716
        if ( freq != DR_Always ) {
4,754,400✔
717
            for (jj = 0; jj < change_buffer.size() ; jj++) {
1,644✔
718
                drb = change_buffer[jj] ;
822✔
719
                REF2 * ref = drb->ref ;
822✔
720
                if ( ref->pointer_present == 1 ) {
822✔
721
                    ref->address = follow_address_path(ref) ;
×
722
                }
723
                if ( memcmp( drb->buffer , drb->ref->address , drb->ref->attr->size) ) {
822✔
724
                    change_detected = true ;
28✔
725
                    memcpy( drb->buffer , drb->ref->address , drb->ref->attr->size) ;
28✔
726
                }
727
            }
728

729
        }
730

731
        // Record data if frequency is always, or at the first call (buffer_num == 0), or a change was detected
732
        // Added condition to always record at the first call which might be also a checkpoint restart to ensure
733
        // data is recorded at least once for change based recording (DR_Changes and DR_Changes_Step).
734
        if ( freq == DR_Always || buffer_num == 0 || change_detected == true ) {
4,754,400✔
735

736
            // If this is not the ring buffer and
737
            // we are going to have trouble fitting 2 data sets then write the data now.
738
            if ( buffer_type != DR_Ring_Buffer ) {
4,753,612✔
739
                if ( buffer_num - writer_num >= (max_num - 2) ) {
4,494,412✔
740
                    write_data(true) ;
×
741
                }
742
            }
743

744
            curr_time = in_time ;
4,753,612✔
745

746
            if ( freq == DR_Changes_Step ) {
4,753,612✔
747
                buffer_offset = buffer_num % max_num ;
×
748
                *((double *)(rec_buffer[0]->last_value)) = in_time ;
×
749
                for (jj = 0; jj < rec_buffer.size() ; jj++) {
×
750
                    drb = rec_buffer[jj] ;
×
751
                    REF2 * ref = drb->ref ;
×
NEW
752
                    int param_size = (int)Trick::ReferenceUtils::effective_trick_size(ref);
×
753
                    if ( buffer_offset == 0 ) {
×
754
                       drb->curr_buffer = drb->buffer ;
×
755
                    } else {
756
                       drb->curr_buffer += param_size ;
×
757
                    }
758
                    switch ( param_size ) {
×
759
                        case 8:
×
760
                            *(int64_t *)drb->curr_buffer = *(int64_t *)drb->last_value ;
×
761
                            break ;
×
762
                        case 4:
×
763
                            *(int32_t *)drb->curr_buffer = *(int32_t *)drb->last_value ;
×
764
                            break ;
×
765
                        case 2:
×
766
                            *(int16_t *)drb->curr_buffer = *(int16_t *)drb->last_value ;
×
767
                            break ;
×
768
                        case 1:
×
769
                            *(int8_t *)drb->curr_buffer = *(int8_t *)drb->last_value ;
×
770
                            break ;
×
771
                        default:
×
772
                            memcpy( drb->curr_buffer , drb->last_value , param_size ) ;
×
773
                            break ;
×
774
                    }
775
                }
776
                buffer_num++ ;
×
777
            }
778

779
            buffer_offset = buffer_num % max_num ;
4,753,612✔
780
            for (jj = 0; jj < rec_buffer.size() ; jj++) {
39,654,591✔
781
                drb = rec_buffer[jj] ;
34,900,979✔
782
                REF2 * ref = drb->ref ;
34,900,979✔
783
                if (ref->pointer_present == 1 && !Trick::ReferenceUtils::is_stl_ref(ref))
34,900,979✔
784
                {
UNCOV
785
                    ref->address = follow_address_path(ref) ;
×
786
                }
787
                int param_size = (int)Trick::ReferenceUtils::effective_trick_size(ref);
34,900,979✔
788
                if ( buffer_offset == 0 ) {
34,900,979✔
789
                   drb->curr_buffer = drb->buffer ;
952✔
790
                } else {
791
                   drb->curr_buffer += param_size ;
34,900,027✔
792
                }
793
                /**
794
                 * While the typical idiom is something like:
795
                 * 1. previous_value = current_value
796
                 * 2. current_value = new_value
797
                 * That is incorrect here, as curr_buffer is a pointer that has already been
798
                 * incremented to the next value's location. We therefore set *curr_buffer and
799
                 * *last_value to the same value, which results in the DR_Changes_Step loop above
800
                 * correctly using this value as the first point of the step change on the next
801
                 * call to this function.
802
                 */
803
                switch ( param_size ) {
34,900,979✔
804
                    case 8:
33,945,405✔
805
                        *(int64_t *)drb->last_value = *(int64_t *)drb->curr_buffer = *(int64_t *)ref->address ;
33,945,405✔
806
                        break ;
33,945,405✔
807
                    case 4:
954,854✔
808
                        *(int32_t *)drb->last_value = *(int32_t *)drb->curr_buffer = *(int32_t *)ref->address ;
954,854✔
809
                        break ;
954,854✔
810
                    case 2:
650✔
811
                        *(int16_t *)drb->last_value = *(int16_t *)drb->curr_buffer = *(int16_t *)ref->address ;
650✔
812
                        break ;
650✔
813
                    case 1:
70✔
814
                        *(int8_t *)drb->last_value = *(int8_t *)drb->curr_buffer = *(int8_t *)ref->address ;
70✔
815
                        break ;
70✔
816
                    default:
×
817
                        memcpy( drb->curr_buffer , ref->address , param_size ) ;
×
818
                        memcpy( drb->last_value , drb->curr_buffer , param_size ) ;
×
819
                        break ;
×
820
                }
821
            }
822
            buffer_num++ ;
4,753,612✔
823
        }
824
    }
825

826
    long long curr_tics = (long long)round(in_time * Trick::JobData::time_tic_value);
4,754,400✔
827
    advance_log_tics_given_curr_tic(curr_tics);
4,754,400✔
828

829
    write_job->next_tics = calculate_next_logging_tic(curr_tics);
4,754,400✔
830
    write_job->cycle_tics = write_job->next_tics - curr_tics;
4,754,400✔
831
    write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
4,754,400✔
832

833
    return(0) ;
4,754,400✔
834
}
835

836
/**
837
 * Loop through the required logging rates and calculate the
838
 * next logging time in tics.
839
 * @return Next logging time in tics,
840
 */
841
long long Trick::DataRecordGroup::calculate_next_logging_tic(long long min_tic)
4,754,451✔
842
{
843
    long long ticOfCycleToProcess = std::numeric_limits<long long>::max();
4,754,451✔
844

845
    // Loop over all the logging rates. If the logging rate's next tic is equal to the min_tic, test against
846
    // that rate's next cycle from min. Find the smallest next tic 
847
    for(size_t cycleIndex = 0; cycleIndex < logging_rates.size(); ++cycleIndex)
26,789,068✔
848
    {
849
        long long logNextTic = logging_rates[cycleIndex].next_cycle_in_tics;
22,034,617✔
850

851
        if(logNextTic == min_tic)
22,034,617✔
852
        {
853
            logNextTic += logging_rates[cycleIndex].rate_in_tics;
56✔
854
        }
855

856
        if(logNextTic < ticOfCycleToProcess)
22,034,617✔
857
        {
858
            ticOfCycleToProcess = logNextTic;
15,511,285✔
859
        }
860
    }
861

862
    return ticOfCycleToProcess;
4,754,451✔
863
}
864

865
/**
866
 * Loop through the required logging rates and advance the next cycle tics of matching rates
867
 * @param curr_tic_in - time in tics to match and advance the next cycle tic
868
 */
869
void Trick::DataRecordGroup::advance_log_tics_given_curr_tic(long long curr_tic_in)
4,754,400✔
870
{
871
    for(size_t cycleIndex = 0; cycleIndex < logging_rates.size(); ++cycleIndex)
26,788,961✔
872
    {
873
        long long & logNextTic = logging_rates[cycleIndex].next_cycle_in_tics;
22,034,561✔
874

875
        while(logNextTic <= curr_tic_in)
31,287,195✔
876
        {
877
            logNextTic += logging_rates[cycleIndex].rate_in_tics;
9,252,634✔
878
        }
879
    }
880
}
4,754,400✔
881

882
int Trick::DataRecordGroup::write_data(bool must_write) {
420,989✔
883

884
    unsigned int local_buffer_num ;
885
    unsigned int num_to_write ;
886
    unsigned int writer_offset ;
887

888
    if ( record and inited and (buffer_type == DR_No_Buffer or must_write) and (total_bytes_written <= max_file_size)) {
420,989✔
889

890
        // buffer_mutex is used in this one place to prevent forced calls of write_data
891
        // to not overwrite data being written by the asynchronous thread.
892
        pthread_mutex_lock(&buffer_mutex) ;
56,524✔
893

894
        local_buffer_num = buffer_num ;
56,524✔
895
        if ( (local_buffer_num - writer_num) > max_num ) {
56,524✔
896
            num_to_write = max_num ;
×
897
        } else {
898
            num_to_write = (local_buffer_num - writer_num) ;
56,524✔
899
        }
900
        writer_num = local_buffer_num - num_to_write ;
56,524✔
901

902
        //! This loop pulls a "row" of time homogeneous data and writes it to the file
903
        while ( writer_num != local_buffer_num ) {
4,810,136✔
904

905
            writer_offset = writer_num % max_num ;
4,753,612✔
906
            //! keep record of bytes written to file. Default max is 1GB
907
            total_bytes_written += format_specific_write_data(writer_offset) ;
4,753,612✔
908
            writer_num++ ;
4,753,612✔
909

910
        }
911

912
        if(!max_size_warning && (total_bytes_written > max_file_size)) {
56,524✔
913
            std::cerr << "WARNING: Data record max file size " << (static_cast<double>(max_file_size))/(1<<20) << "MB reached.\n"
×
914
            "https://nasa.github.io/trick/documentation/simulation_capabilities/Data-Record#changing-the-max-file-size-of-a-data-record-group-ascii-and-binary-only" 
×
915
            << std::endl;
×
916
            max_size_warning = true;
×
917
        }
918

919
        pthread_mutex_unlock(&buffer_mutex) ;
56,524✔
920

921
    }
922

923
    return 0 ;
420,989✔
924
}
925

926
int Trick::DataRecordGroup::enable() {
38✔
927
    record = true ;
38✔
928
    return(0) ;
38✔
929
}
930

931
int Trick::DataRecordGroup::disable() {
×
932
    record = false ;
×
933
    return(0) ;
×
934
}
935

936
int Trick::DataRecordGroup::shutdown() {
57✔
937

938
    // Force write out all data
939
    record = true ; // If user disabled group, make sure any recorded data gets written out
57✔
940
    write_data(true) ;
57✔
941
    format_specific_shutdown() ;
57✔
942

943
    remove_all_variables();
57✔
944

945
    // remove_all_variables does not remove sim time
946
    if(!rec_buffer.empty()){
57✔
947
        delete rec_buffer[0];
57✔
948
        rec_buffer.clear();
57✔
949
    }
950

951
    if ( writer_buff ) {
57✔
952
        free(writer_buff) ;
57✔
953
        writer_buff = NULL ;
57✔
954
    }
955

956
    return 0 ;
57✔
957
}
958

959
std::string Trick::DataRecordGroup::type_string( int item_type, int item_size ) {
674✔
960
    switch (item_type) {
674✔
961
        case TRICK_CHARACTER:
2✔
962
                return "char";
4✔
963
                break;
964
        case TRICK_UNSIGNED_CHARACTER:
2✔
965
                return "unsigned_char";
4✔
966
                break;
967
        case TRICK_STRING:
×
968
                return "string";
×
969
                break;
970
        case TRICK_SHORT:
5✔
971
                return "short";
10✔
972
                break;
973
        case TRICK_UNSIGNED_SHORT:
5✔
974
                return "unsigned_short";
10✔
975
                break;
976
        case TRICK_ENUMERATED:
97✔
977
        case TRICK_INTEGER:
978
                return "int";
194✔
979
                break;
980
        case TRICK_UNSIGNED_INTEGER:
5✔
981
                return "unsigned_int";
10✔
982
                break;
983
        case TRICK_LONG:
5✔
984
                return "long";
10✔
985
                break;
986
        case TRICK_UNSIGNED_LONG:
5✔
987
                return "unsigned_long";
10✔
988
                break;
989
        case TRICK_FLOAT:
11✔
990
                return "float";
22✔
991
                break;
992
        case TRICK_DOUBLE:
468✔
993
                if ( single_prec_only ) {
468✔
994
                        return "float";
×
995
                }
996
                else {
997
                        return "double";
936✔
998
                }
999
                break;
1000
        case TRICK_BITFIELD:
28✔
1001
                if (item_size == sizeof(int)) {
28✔
1002
                        return "int";
56✔
1003
                } else if (item_size == sizeof(short)) {
×
1004
                        return "short";
×
1005
                } else {
1006
                        return "char";
×
1007
                }
1008
                break;
1009
        case TRICK_UNSIGNED_BITFIELD:
28✔
1010
                if (item_size == sizeof(int)) {
28✔
1011
                        return "unsigned_int";
56✔
1012
                } else if (item_size == sizeof(short)) {
×
1013
                        return "unsigned_short";
×
1014
                } else {
1015
                        return "unsigned_char";
×
1016
                }
1017
                break;
1018
        case TRICK_LONG_LONG:
7✔
1019
                return "long_long";
14✔
1020
                break;
1021
        case TRICK_UNSIGNED_LONG_LONG:
2✔
1022
                return "unsigned_long_long";
4✔
1023
                break;
1024
        case TRICK_BOOLEAN:
4✔
1025
#if ( __sun | __APPLE__ )
1026
                return "int";
1027
#else
1028
                return "unsigned_char";
8✔
1029
#endif
1030
                break;
1031
    }
1032
    return "";
×
1033
}
1034

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