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

nasa / trick / 20080611331

09 Dec 2025 10:30PM UTC coverage: 55.918% (+0.1%) from 55.799%
20080611331

Pull #1808

github

web-flow
Merge 152e847ce into 36743883e
Pull Request #1808: Trick ify

12468 of 22297 relevant lines covered (55.92%)

307370.86 hits per line

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

83.13
/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
    rate_in_seconds = rate_in;
714✔
67
    long long cycle_tics = (long long)round(rate_in * Trick::JobData::time_tic_value);
714✔
68
    rate_in_tics = cycle_tics;
714✔
69
    next_cycle_in_tics= exec_get_time_tics();
714✔
70
}
714✔
71

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

99
    union {
100
        long l;
101
        char c[sizeof(long)];
102
    } byte_order_union;
103

104
    byte_order_union.l = 1;
607✔
105
    if (byte_order_union.c[sizeof(long) - 1] != 1) {
607✔
106
        byte_order = "little_endian" ;
607✔
107
    } else {
108
        byte_order = "big_endian" ;
×
109
    }
110

111
    // sim object name
112
    name = std::string("trick_data_record_group_") + in_name ;
607✔
113

114
    configure_jobs(dr_type) ;
607✔
115

116
    add_time_variable() ;
607✔
117

118
    logging_rates.emplace_back(cycle);
607✔
119
}
607✔
120

121
Trick::DataRecordGroup::~DataRecordGroup() {
42✔
122
    free((void *)time_value_attr.units) ;
42✔
123
}
42✔
124

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

127
    int ret = 0 ;
5,119,390✔
128

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

153
    return(ret) ;
5,119,390✔
154

155
}
156

157
double Trick::DataRecordGroup::call_function_double( Trick::JobData * curr_job ) {
×
158
    (void) curr_job ;
159
    return(0.0) ;
×
160
}
161

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

173
const std::string & Trick::DataRecordGroup::get_group_name() {
24✔
174
    return group_name ;
24✔
175
}
176

177
int Trick::DataRecordGroup::set_cycle( double in_cycle ) {
41✔
178
    logging_rates[0].set_rate(in_cycle);
41✔
179
    write_job->set_cycle(in_cycle) ;
41✔
180
    return(0) ;
41✔
181
}
182

183
int Trick::DataRecordGroup::add_cycle(double in_cycle)
5✔
184
{
185
    logging_rates.emplace_back(in_cycle);
5✔
186
    return(0);
5✔
187
}
188

189
int Trick::DataRecordGroup::set_phase( unsigned short in_phase ) {
587✔
190
    write_job->phase = in_phase ;
587✔
191
    return(0) ;
587✔
192
}
193

194
int Trick::DataRecordGroup::set_freq( DR_Freq in_freq ) {
36✔
195
    freq = in_freq ;
36✔
196
    return(0) ;
36✔
197
}
198

199
int Trick::DataRecordGroup::set_max_buffer_size( int num ) {
×
200
    max_num = num ;
×
201
    return(0) ;
×
202
}
203

204
int Trick::DataRecordGroup::set_buffer_type( int in_buffer_type ) {
44✔
205
    buffer_type = (DR_Buffering)in_buffer_type ;
44✔
206
    return(0) ;
44✔
207
}
208

209
int Trick::DataRecordGroup::set_max_file_size( uint64_t bytes ) {
16✔
210
    if(bytes == 0) {
16✔
211
        max_file_size = UINT64_MAX ;
×
212
    } else {
213
    max_file_size = bytes ; 
16✔
214
    }
215
    return(0) ;
16✔
216
}
217

218
int Trick::DataRecordGroup::set_single_prec_only( bool in_single_prec_only ) {
24✔
219
    single_prec_only = in_single_prec_only ;
24✔
220
    return(0) ;
24✔
221
}
222

223
int Trick::DataRecordGroup::set_thread( unsigned int in_thread_id ) {
199✔
224

225
    unsigned int jj ;
226
    Trick::JobData * temp_job  ;
227

228
    /* make all data_record_groups have same sim object id as data_record */
229
    for ( jj = 0 ; jj < jobs.size() ; jj++ ) {
1,801✔
230
        temp_job = jobs[jj] ;
1,602✔
231
        temp_job->thread = in_thread_id ;
1,602✔
232
    }
233
    return 0 ;
199✔
234
}
235

236
int Trick::DataRecordGroup::set_job_class( std::string in_class ) {
587✔
237
    write_job->job_class_name = job_class = in_class ;
587✔
238
    return(0) ;
587✔
239
}
240

241
int Trick::DataRecordGroup::add_time_variable() {
645✔
242
    REF2 * new_ref ;
243

244
    // Create attributes for time recorded as a double
245
    time_value_attr.type = TRICK_DOUBLE ;
645✔
246
    time_value_attr.size = sizeof(double) ;
645✔
247
    time_value_attr.units = strdup("s") ;
645✔
248

249
    // Create a reference that records time as sys.exec.out.time
250
    new_ref = (REF2 *)calloc( 1 , sizeof(REF2));
645✔
251
    new_ref->reference = strdup("sys.exec.out.time") ;
645✔
252
    new_ref->address = &curr_time ;
645✔
253
    new_ref->attr = &time_value_attr ;
645✔
254
    add_variable(new_ref) ;
645✔
255

256
    return 0 ;
645✔
257
}
258

259
int Trick::DataRecordGroup::add_variable( std::string in_name , std::string alias ) {
954✔
260

261
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
954✔
262
    // Trim leading spaces
263
    in_name.erase( 0, in_name.find_first_not_of( " \t" ) );
954✔
264
    // Trim trailing spaces
265
    in_name.erase( in_name.find_last_not_of( " \t" ) + 1);
954✔
266
    new_var->name = in_name ;
954✔
267
    new_var->alias = alias ;
954✔
268
    rec_buffer.push_back(new_var) ;
954✔
269
    return 0 ;
954✔
270
}
271

272
void Trick::DataRecordGroup::remove_variable( std::string in_name ) {
×
273
    // Trim leading spaces++ 
274
    in_name.erase( 0, in_name.find_first_not_of( " \t" ) );
×
275
    // Trim trailing spaces
276
    in_name.erase( in_name.find_last_not_of( " \t" ) + 1);
×
277

278
    if (!in_name.compare("sys.exec.out.time")) {
×
279
        // This class assumes sim time is always the first variable.
280
        // Removing it results in errors.
281
        return;
×
282
    }
283

284
    auto remove_from = [&](std::vector<DataRecordBuffer*>& buffer) {
×
285
        for (auto i = buffer.begin(); i != buffer.end(); ++i) {
×
286
            if (!(*i)->name.compare(in_name)) {
×
287
                delete *i;
×
288
                buffer.erase(i);
×
289
                break;
×
290
            }
291
        }
292
    };
×
293

294
    remove_from(rec_buffer);
×
295
    remove_from(change_buffer);
×
296
}
297

298
void Trick::DataRecordGroup::remove_all_variables() {
52✔
299
    // remove all but the first variable, which is sim time
300
    if(!rec_buffer.empty()) {
52✔
301
        for (auto i = rec_buffer.begin() + 1; i != rec_buffer.end(); ++i) {
608✔
302
            delete *i;
556✔
303
        }
304
        rec_buffer.erase(rec_buffer.begin() + 1, rec_buffer.end());
52✔
305
    }
306

307
    // remove everything
308
    for (auto variable : change_buffer) {
58✔
309
        delete variable;
6✔
310
    }
311

312
    change_buffer.clear();
52✔
313
}
52✔
314

315
int Trick::DataRecordGroup::add_variable( REF2 * ref2 ) {
4,647✔
316

317
    if ( ref2 == NULL || ref2->attr == NULL ) {
4,647✔
318
        return(-1) ;
×
319
    }
320

321
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
4,647✔
322
    new_var->name = std::string(ref2->reference) ;
4,647✔
323
    new_var->ref_searched = true ;
4,647✔
324
    new_var->ref = ref2 ;
4,647✔
325
    new_var->last_value = (char *)calloc(1 , new_var->ref->attr->size) ;
4,647✔
326
    // Don't allocate space for the temp storage buffer until "init"
327
    rec_buffer.push_back(new_var) ;
4,647✔
328

329
    return(0) ;
4,647✔
330

331
}
332

333
int Trick::DataRecordGroup::add_change_variable( std::string in_name ) {
6✔
334

335
    REF2 * ref2 ;
336

337
    ref2 = ref_attributes(in_name.c_str()) ;
6✔
338

339
    if ( ref2 == NULL || ref2->attr == NULL ) {
6✔
340
        message_publish(MSG_WARNING, "Could not find Data Record change variable %s.\n", in_name.c_str()) ;
×
341
        return(-1) ;
×
342
    }
343

344
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
6✔
345
    new_var->ref = ref2 ;
6✔
346
    new_var->name = in_name;
6✔
347
    new_var->buffer = (char *)malloc(ref2->attr->size) ;
6✔
348
    new_var->last_value =  NULL ;
6✔
349
    memcpy(new_var->buffer , ref2->address , ref2->attr->size) ;
6✔
350
    change_buffer.push_back(new_var) ;
6✔
351

352
    return(0) ;
6✔
353

354
}
355

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

368
    return true;
538✔
369
}
370

371
/**
372
@details
373
-# The simulation output directory is retrieved from the CommandLineArguments
374
-# The log header file is created
375
   -# The endianness of the log file is written to the log header.
376
   -# The names of the parameters contained in the log file are written to the header.
377
-# Memory buffers are allocated to store simulation data
378
-# The DataRecordGroupObject (a derived SimObject) is added to the Scheduler.
379
*/
380
int Trick::DataRecordGroup::init() {
55✔
381

382
    unsigned int jj ;
383
    int ret ;
384

385
    // reset counter here so we can "re-init" our recording
386
    buffer_num = writer_num = total_bytes_written = 0 ;
55✔
387

388
    output_dir = command_line_args_get_output_dir() ;
55✔
389
    /* this is the common part of the record file name, the format specific will add the correct suffix */
390
    file_name = output_dir + "/log_" + group_name ;
55✔
391

392
    pthread_mutex_init(&buffer_mutex, NULL);
55✔
393

394
    // Allocate recording space for time.
395
    rec_buffer[0]->buffer = (char *)calloc(max_num , rec_buffer[0]->ref->attr->size) ;
55✔
396
    rec_buffer[0]->last_value = (char *)calloc(1 , rec_buffer[0]->ref->attr->size) ;
55✔
397

398
    /* Loop through all variables looking up names.  Allocate recording space
399
       according to size of the variable */
400
    for (jj = 1; jj < rec_buffer.size() ; jj++) {
637✔
401
        Trick::DataRecordBuffer * drb = rec_buffer[jj] ;
582✔
402
        if ( drb->ref_searched == false ) {
582✔
403
            REF2 * ref2 ;
404

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

431
    write_header() ;
55✔
432

433
    // call format specific initialization to open destination and write header
434
    ret = format_specific_init() ;
55✔
435

436
    long long curr_tics = exec_get_time_tics();
55✔
437
    int tic_value = exec_get_time_tic_value();
55✔
438

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

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

457
        if ( test_rem > 0.001 ) {
61✔
458
            message_publish(MSG_WARNING,"DataRecordGroup ERROR: Cycle for %lu logging rate idx cannot be exactly scheduled with time tic value. "
×
459
             "cycle = %16.12f, cycle_tics = %lld , time_tic = %16.12f\n",
460
             ii , logging_rate, cycle_tics , 1.0 / tic_value ) ;
461
            ret = -1 ;
×
462
        }
463

464
        // We've checked that the rate is achievable. Call set_rate again to make sure the latest time_tic_value
465
        // was utilized for calculated next tics
466
        logging_rates[ii].set_rate(logging_rate);
61✔
467
    }
468

469
    write_job->next_tics = curr_tics;
55✔
470

471
    long long next_next_tics = calculate_next_logging_tic(write_job->next_tics);
55✔
472
    write_job->cycle_tics = next_next_tics - curr_tics;
55✔
473
    write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
55✔
474

475
    // set the inited flag to true when all initialization is done
476
    if ( ret == 0 ) {
55✔
477
        inited = true ;
55✔
478
    }
479

480
    return(0) ;
55✔
481

482
}
483

484
void Trick::DataRecordGroup::configure_jobs(DR_Type type) {
607✔
485
    switch(type) {
607✔
486
    default:
50✔
487
        // run the restart job in phase 60001
488
        add_job(0, 5, (char *)"restart", NULL, 1.0, (char *)"restart", (char *)"TRK", 60001) ;
50✔
489

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

500
        write_job = add_job(0, 99, (char *)job_class.c_str(), NULL, cycle, (char *)"data_record" , (char *)"TRK") ;
607✔
501
        break ;
607✔
502
    }
503
    write_job->set_system_job_class(true);
607✔
504
}
607✔
505

506
int Trick::DataRecordGroup::checkpoint() {
12✔
507
    unsigned int jj ;
508

509
    /*
510
       Save the names of the variables and the aliases to the checkpoint,
511
       the rest of the DataRecordBuffer will be reconstructed during restart.
512
       The first variable is time which we do not have to save.
513
     */
514
    if ( rec_buffer.size() > 1 ) {
12✔
515
        num_variable_names = rec_buffer.size() - 1 ;
12✔
516
        variable_names = (char **)TMM_declare_var_1d("char *", (int)rec_buffer.size() - 1) ;
12✔
517
        variable_alias = (char **)TMM_declare_var_1d("char *", (int)rec_buffer.size() - 1) ;
12✔
518

519
        for (jj = 1; jj < rec_buffer.size() ; jj++) {
58✔
520
            Trick::DataRecordBuffer * drb = rec_buffer[jj] ;
46✔
521

522
            variable_names[jj-1] = TMM_strdup((char *)drb->name.c_str()) ;
46✔
523
            variable_alias[jj-1] = TMM_strdup((char *)drb->alias.c_str()) ;
46✔
524
        }
525
    }
526

527
    /*
528
       Save the names of the change variables and the aliases to the checkpoint,
529
       the rest of the DataRecordBuffer will be reconstructed during restart
530
     */
531
    if ( change_buffer.size() > 0 ) {
12✔
532
        num_change_variable_names = change_buffer.size() ;
2✔
533
        change_variable_names = (char **)TMM_declare_var_1d("char *", (int)change_buffer.size()) ;
2✔
534
        change_variable_alias = (char **)TMM_declare_var_1d("char *", (int)change_buffer.size()) ;
2✔
535

536
        for (jj = 0; jj < change_buffer.size() ; jj++) {
4✔
537
            Trick::DataRecordBuffer * drb = change_buffer[jj] ;
2✔
538

539
            change_variable_names[jj] = TMM_strdup((char *)drb->name.c_str()) ;
2✔
540
            change_variable_alias[jj] = TMM_strdup((char *)drb->alias.c_str()) ;
2✔
541
        }
542
    }
543

544
    return 0 ;
12✔
545
}
546

547
void Trick::DataRecordGroup::clear_checkpoint_vars() {
50✔
548
    
549
    if ( variable_names ) {
50✔
550
        for(unsigned int jj = 0; jj < num_variable_names; jj++) {
80✔
551
            TMM_delete_var_a(variable_names[jj]);
60✔
552
        }
553
        TMM_delete_var_a(variable_names) ;
20✔
554
    }
555

556
    if ( variable_alias ) {
50✔
557
        for(unsigned int jj = 0; jj < num_variable_names; jj++) {
80✔
558
            TMM_delete_var_a(variable_alias[jj]);
60✔
559
        }
560
        TMM_delete_var_a(variable_alias) ;
20✔
561
    }
562

563
    if ( change_variable_names ) {
50✔
564
        for(unsigned int jj = 0; jj < num_change_variable_names; jj++) {
8✔
565
            TMM_delete_var_a(change_variable_names[jj]);
4✔
566
        }
567
        TMM_delete_var_a(change_variable_names) ;
4✔
568
    }
569

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

577
    variable_names = NULL ;
50✔
578
    variable_alias = NULL ;
50✔
579
    change_variable_names = NULL ;
50✔
580
    change_variable_alias = NULL ;
50✔
581
    num_variable_names = 0 ;
50✔
582
    num_change_variable_names = 0 ;
50✔
583
}
50✔
584

585
int Trick::DataRecordGroup::restart() {
8✔
586
    std::vector <Trick::DataRecordBuffer *>::iterator drb_it ;
8✔
587

588
    /* delete the current rec_buffer */
589
    for ( drb_it = rec_buffer.begin() ; drb_it != rec_buffer.end() ; ++drb_it ) {
16✔
590
        delete *drb_it ;
8✔
591
    }
592
    rec_buffer.clear() ;
8✔
593
    /* Add back the time variable */
594
    add_time_variable() ;
8✔
595

596
    /* delete the current change_buffer contents */
597
    for ( drb_it = change_buffer.begin() ; drb_it != change_buffer.end() ; ++drb_it ) {
8✔
598
        delete *drb_it ;
×
599
    }
600
    change_buffer.clear() ;
8✔
601

602
    unsigned int jj ;
603
    /* add the variable names listed in the checkpoint file */
604
    for ( jj = 0 ; jj < num_variable_names ; jj++ ) {
22✔
605
        add_variable( variable_names[jj] , variable_alias[jj] ) ;
14✔
606
    }
607
    for ( jj = 0 ; jj < num_change_variable_names ; jj++ ) {
10✔
608
        add_change_variable( change_variable_names[jj] ) ;
2✔
609
    }
610

611
    clear_checkpoint_vars() ;
8✔
612

613
    // set the write job class to what is in the checkpoint file.
614
    write_job->job_class_name = job_class ;
8✔
615

616
    // reset the sim_object name.
617
    name = std::string("data_record_group_") + group_name ;
8✔
618

619
    /* call init to open the recording file and look up variable name addresses */
620
    init() ;
8✔
621

622
    // Account for the fact that the current time tics is actually already passed for a checkpoint.
623
    long long curr_tics = exec_get_time_tics();
8✔
624
    advance_log_tics_given_curr_tic(curr_tics);
8✔
625

626
    write_job->next_tics = calculate_next_logging_tic(curr_tics);
8✔
627
    write_job->cycle_tics = write_job->next_tics - curr_tics;
8✔
628
    write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
8✔
629

630
    return 0 ;
8✔
631
}
632

633
int Trick::DataRecordGroup::write_header() {
55✔
634

635
    unsigned int jj ;
636
    std::string header_name ;
110✔
637
    std::fstream out_stream ;
110✔
638

639
    /*! create the header file used by the GUIs */
640
    header_name = output_dir + "/log_" + group_name + ".header" ;
55✔
641

642
    out_stream.open(header_name.c_str(), std::fstream::out ) ;
55✔
643
    if ( ! out_stream  ||  ! out_stream.good() ) {
55✔
644
        return -1;
×
645
    }
646

647
    /* Header file first line is created in format specific header */
648
    out_stream << "log_" << group_name ;
55✔
649

650
    format_specific_header(out_stream) ;
55✔
651

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

664
        if ( rec_buffer[jj]->ref->attr->mods & TRICK_MODS_UNITSDASHDASH ) {
631✔
665
            out_stream << "--" ;
4✔
666
        } else {
667
            out_stream << rec_buffer[jj]->ref->attr->units ;
627✔
668
        }
669
        out_stream << "\t" << rec_buffer[jj]->ref->reference << std::endl ;
631✔
670
    }
671

672
    // Send all unwritten characters in the buffer to its output/file.
673
    out_stream.flush() ;
55✔
674
    out_stream.close() ;
55✔
675

676
    return(0) ;
55✔
677

678
}
679

680
int Trick::DataRecordGroup::data_record(double in_time) {
4,754,222✔
681

682
    unsigned int jj ;
683
    unsigned int buffer_offset ;
684
    Trick::DataRecordBuffer * drb ;
685
    bool change_detected = false ;
4,754,222✔
686

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

702
        }
703

704
        if ( freq == DR_Always || change_detected == true ) {
4,754,222✔
705

706
            // If this is not the ring buffer and
707
            // we are going to have trouble fitting 2 data sets then write the data now.
708
            if ( buffer_type != DR_Ring_Buffer ) {
4,753,428✔
709
                if ( buffer_num - writer_num >= (max_num - 2) ) {
4,494,228✔
710
                    write_data(true) ;
×
711
                }
712
            }
713

714
            curr_time = in_time ;
4,753,428✔
715

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

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

795
    long long curr_tics = (long long)round(in_time * Trick::JobData::time_tic_value);
4,754,222✔
796
    advance_log_tics_given_curr_tic(curr_tics);
4,754,222✔
797

798
    write_job->next_tics = calculate_next_logging_tic(curr_tics);
4,754,222✔
799
    write_job->cycle_tics = write_job->next_tics - curr_tics;
4,754,222✔
800
    write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
4,754,222✔
801

802
    return(0) ;
4,754,222✔
803
}
804

805
/**
806
 * Loop through the required logging rates and calculate the
807
 * next logging time in tics.
808
 * @return Next logging time in tics,
809
 */
810
long long Trick::DataRecordGroup::calculate_next_logging_tic(long long min_tic)
4,754,285✔
811
{
812
    long long ticOfCycleToProcess = std::numeric_limits<long long>::max();
4,754,285✔
813

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

820
        if(logNextTic == min_tic)
22,034,453✔
821
        {
822
            logNextTic += logging_rates[cycleIndex].rate_in_tics;
61✔
823
        }
824

825
        if(logNextTic < ticOfCycleToProcess)
22,034,453✔
826
        {
827
            ticOfCycleToProcess = logNextTic;
15,511,119✔
828
        }
829
    }
830

831
    return ticOfCycleToProcess;
4,754,285✔
832
}
833

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

850
int Trick::DataRecordGroup::write_data(bool must_write) {
406,430✔
851

852
    unsigned int local_buffer_num ;
853
    unsigned int num_to_write ;
854
    unsigned int writer_offset ;
855

856
    if ( record and inited and (buffer_type == DR_No_Buffer or must_write) and (total_bytes_written <= max_file_size)) {
406,430✔
857

858
        // buffer_mutex is used in this one place to prevent forced calls of write_data
859
        // to not overwrite data being written by the asynchronous thread.
860
        pthread_mutex_lock(&buffer_mutex) ;
41,985✔
861

862
        local_buffer_num = buffer_num ;
41,985✔
863
        if ( (local_buffer_num - writer_num) > max_num ) {
41,985✔
864
            num_to_write = max_num ;
×
865
        } else {
866
            num_to_write = (local_buffer_num - writer_num) ;
41,985✔
867
        }
868
        writer_num = local_buffer_num - num_to_write ;
41,985✔
869

870
        //! This loop pulls a "row" of time homogeneous data and writes it to the file
871
        while ( writer_num != local_buffer_num ) {
4,795,413✔
872

873
            writer_offset = writer_num % max_num ;
4,753,428✔
874
            //! keep record of bytes written to file. Default max is 1GB
875
            total_bytes_written += format_specific_write_data(writer_offset) ;
4,753,428✔
876
            writer_num++ ;
4,753,428✔
877

878
        }
879

880
        if(!max_size_warning && (total_bytes_written > max_file_size)) {
41,985✔
881
            std::cerr << "WARNING: Data record max file size " << (static_cast<double>(max_file_size))/(1<<20) << "MB reached.\n"
×
882
            "https://nasa.github.io/trick/documentation/simulation_capabilities/Data-Record#changing-the-max-file-size-of-a-data-record-group-ascii-and-binary-only" 
×
883
            << std::endl;
×
884
            max_size_warning = true;
×
885
        }
886

887
        pthread_mutex_unlock(&buffer_mutex) ;
41,985✔
888

889
    }
890

891
    return 0 ;
406,385✔
892
}
893

894
int Trick::DataRecordGroup::enable() {
36✔
895
    record = true ;
36✔
896
    return(0) ;
36✔
897
}
898

899
int Trick::DataRecordGroup::disable() {
×
900
    record = false ;
×
901
    return(0) ;
×
902
}
903

904
int Trick::DataRecordGroup::shutdown() {
52✔
905

906
    // Force write out all data
907
    record = true ; // If user disabled group, make sure any recorded data gets written out
52✔
908
    write_data(true) ;
52✔
909
    format_specific_shutdown() ;
52✔
910

911
    remove_all_variables();
52✔
912

913
    // remove_all_variables does not remove sim time
914
    if(!rec_buffer.empty()){
52✔
915
        delete rec_buffer[0];
52✔
916
        rec_buffer.clear();
52✔
917
    }
918

919
    if ( writer_buff ) {
52✔
920
        free(writer_buff) ;
52✔
921
        writer_buff = NULL ;
52✔
922
    }
923

924
    return 0 ;
52✔
925
}
926

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

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