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

nasa / trick / 20320422572

17 Dec 2025 11:22PM UTC coverage: 55.597% (+0.006%) from 55.591%
20320422572

Pull #2016

github

web-flow
Merge 1a683d874 into bafbedd6e
Pull Request #2016: better support set_cycle in drgroup

12 of 14 new or added lines in 1 file covered. (85.71%)

1 existing line in 1 file now uncovered.

12516 of 22512 relevant lines covered (55.6%)

305212.41 hits per line

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

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

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

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

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

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

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

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

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

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

58

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

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

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

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

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

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

121
    configure_jobs(dr_type) ;
607✔
122

123
    add_time_variable() ;
607✔
124

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

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

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

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

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

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

162
}
163

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

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

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

184
int Trick::DataRecordGroup::set_cycle( double in_cycle ) {
41✔
185
    logging_rates[0].set_rate(in_cycle);
41✔
186
    write_job->set_cycle(in_cycle) ;
41✔
187
    if(inited) {
41✔
NEW
188
        reset_cycle_data_from_curr_tic();
×
189
    }
190
    return(0) ;
41✔
191
}
192

193
int Trick::DataRecordGroup::add_cycle(double in_cycle)
5✔
194
{
195
    logging_rates.emplace_back(in_cycle);
5✔
196
    if(inited) {
5✔
NEW
197
        reset_cycle_data_from_curr_tic();
×
198
    }
199
    return(0);
5✔
200
}
201

202
int Trick::DataRecordGroup::set_phase( unsigned short in_phase ) {
587✔
203
    write_job->phase = in_phase ;
587✔
204
    return(0) ;
587✔
205
}
206

207
int Trick::DataRecordGroup::set_freq( DR_Freq in_freq ) {
36✔
208
    freq = in_freq ;
36✔
209
    return(0) ;
36✔
210
}
211

212
int Trick::DataRecordGroup::set_max_buffer_size( int num ) {
×
213
    max_num = num ;
×
214
    return(0) ;
×
215
}
216

217
int Trick::DataRecordGroup::set_buffer_type( int in_buffer_type ) {
44✔
218
    buffer_type = (DR_Buffering)in_buffer_type ;
44✔
219
    return(0) ;
44✔
220
}
221

222
int Trick::DataRecordGroup::set_max_file_size( uint64_t bytes ) {
16✔
223
    if(bytes == 0) {
16✔
224
        max_file_size = UINT64_MAX ;
×
225
    } else {
226
    max_file_size = bytes ; 
16✔
227
    }
228
    return(0) ;
16✔
229
}
230

231
int Trick::DataRecordGroup::set_single_prec_only( bool in_single_prec_only ) {
24✔
232
    single_prec_only = in_single_prec_only ;
24✔
233
    return(0) ;
24✔
234
}
235

236
int Trick::DataRecordGroup::set_thread( unsigned int in_thread_id ) {
199✔
237

238
    unsigned int jj ;
239
    Trick::JobData * temp_job  ;
240

241
    /* make all data_record_groups have same sim object id as data_record */
242
    for ( jj = 0 ; jj < jobs.size() ; jj++ ) {
1,801✔
243
        temp_job = jobs[jj] ;
1,602✔
244
        temp_job->thread = in_thread_id ;
1,602✔
245
    }
246
    return 0 ;
199✔
247
}
248

249
int Trick::DataRecordGroup::set_job_class( std::string in_class ) {
587✔
250
    write_job->job_class_name = job_class = in_class ;
587✔
251
    return(0) ;
587✔
252
}
253

254
int Trick::DataRecordGroup::add_time_variable() {
645✔
255
    REF2 * new_ref ;
256

257
    // Create attributes for time recorded as a double
258
    time_value_attr.type = TRICK_DOUBLE ;
645✔
259
    time_value_attr.size = sizeof(double) ;
645✔
260
    time_value_attr.units = strdup("s") ;
645✔
261

262
    // Create a reference that records time as sys.exec.out.time
263
    new_ref = (REF2 *)calloc( 1 , sizeof(REF2));
645✔
264
    new_ref->reference = strdup("sys.exec.out.time") ;
645✔
265
    new_ref->address = &curr_time ;
645✔
266
    new_ref->attr = &time_value_attr ;
645✔
267
    add_variable(new_ref) ;
645✔
268

269
    return 0 ;
645✔
270
}
271

272
int Trick::DataRecordGroup::add_variable( std::string in_name , std::string alias ) {
954✔
273

274
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
954✔
275
    // Trim leading spaces
276
    in_name.erase( 0, in_name.find_first_not_of( " \t" ) );
954✔
277
    // Trim trailing spaces
278
    in_name.erase( in_name.find_last_not_of( " \t" ) + 1);
954✔
279
    new_var->name = in_name ;
954✔
280
    new_var->alias = alias ;
954✔
281
    rec_buffer.push_back(new_var) ;
954✔
282
    return 0 ;
954✔
283
}
284

285
void Trick::DataRecordGroup::remove_variable( std::string in_name ) {
×
286
    // Trim leading spaces++ 
287
    in_name.erase( 0, in_name.find_first_not_of( " \t" ) );
×
288
    // Trim trailing spaces
289
    in_name.erase( in_name.find_last_not_of( " \t" ) + 1);
×
290

291
    if (!in_name.compare("sys.exec.out.time")) {
×
292
        // This class assumes sim time is always the first variable.
293
        // Removing it results in errors.
294
        return;
×
295
    }
296

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

307
    remove_from(rec_buffer);
×
308
    remove_from(change_buffer);
×
309
}
310

311
void Trick::DataRecordGroup::remove_all_variables() {
52✔
312
    // remove all but the first variable, which is sim time
313
    if(!rec_buffer.empty()) {
52✔
314
        for (auto i = rec_buffer.begin() + 1; i != rec_buffer.end(); ++i) {
608✔
315
            delete *i;
556✔
316
        }
317
        rec_buffer.erase(rec_buffer.begin() + 1, rec_buffer.end());
52✔
318
    }
319

320
    // remove everything
321
    for (auto variable : change_buffer) {
58✔
322
        delete variable;
6✔
323
    }
324

325
    change_buffer.clear();
52✔
326
}
52✔
327

328
int Trick::DataRecordGroup::add_variable( REF2 * ref2 ) {
4,647✔
329

330
    if ( ref2 == NULL || ref2->attr == NULL ) {
4,647✔
331
        return(-1) ;
×
332
    }
333

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

342
    return(0) ;
4,647✔
343

344
}
345

346
int Trick::DataRecordGroup::add_change_variable( std::string in_name ) {
6✔
347

348
    REF2 * ref2 ;
349

350
    ref2 = ref_attributes(in_name.c_str()) ;
6✔
351

352
    if ( ref2 == NULL || ref2->attr == NULL ) {
6✔
353
        message_publish(MSG_WARNING, "Could not find Data Record change variable %s.\n", in_name.c_str()) ;
×
354
        return(-1) ;
×
355
    }
356

357
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
6✔
358
    new_var->ref = ref2 ;
6✔
359
    new_var->name = in_name;
6✔
360
    new_var->buffer = (char *)malloc(ref2->attr->size) ;
6✔
361
    new_var->last_value =  NULL ;
6✔
362
    memcpy(new_var->buffer , ref2->address , ref2->attr->size) ;
6✔
363
    change_buffer.push_back(new_var) ;
6✔
364

365
    return(0) ;
6✔
366

367
}
368

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

381
    return true;
538✔
382
}
383

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

395
    unsigned int jj ;
396
    int ret ;
397

398
    // reset counter here so we can "re-init" our recording
399
    buffer_num = writer_num = total_bytes_written = 0 ;
55✔
400

401
    output_dir = command_line_args_get_output_dir() ;
55✔
402
    /* this is the common part of the record file name, the format specific will add the correct suffix */
403
    file_name = output_dir + "/log_" + group_name ;
55✔
404

405
    pthread_mutex_init(&buffer_mutex, NULL);
55✔
406

407
    // Allocate recording space for time.
408
    rec_buffer[0]->buffer = (char *)calloc(max_num , rec_buffer[0]->ref->attr->size) ;
55✔
409
    rec_buffer[0]->last_value = (char *)calloc(1 , rec_buffer[0]->ref->attr->size) ;
55✔
410

411
    /* Loop through all variables looking up names.  Allocate recording space
412
       according to size of the variable */
413
    for (jj = 1; jj < rec_buffer.size() ; jj++) {
637✔
414
        Trick::DataRecordBuffer * drb = rec_buffer[jj] ;
582✔
415
        if ( drb->ref_searched == false ) {
582✔
416
            REF2 * ref2 ;
417

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

444
    write_header() ;
55✔
445

446
    // call format specific initialization to open destination and write header
447
    ret = format_specific_init() ;
55✔
448

449
    long long curr_tics = exec_get_time_tics();
55✔
450
    int tic_value = exec_get_time_tic_value();
55✔
451

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

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

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

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

482
    write_job->next_tics = curr_tics;
55✔
483

484
    long long next_next_tics = calculate_next_logging_tic(write_job->next_tics);
55✔
485
    write_job->cycle_tics = next_next_tics - curr_tics;
55✔
486
    write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
55✔
487

488
    // set the inited flag to true when all initialization is done
489
    if ( ret == 0 ) {
55✔
490
        inited = true ;
55✔
491
    }
492

493
    return(0) ;
55✔
494

495
}
496

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

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

513
        write_job = add_job(0, 99, (char *)job_class.c_str(), NULL, cycle, (char *)"data_record" , (char *)"TRK") ;
607✔
514
        break ;
607✔
515
    }
516
    write_job->set_system_job_class(true);
607✔
517
}
607✔
518

519
int Trick::DataRecordGroup::checkpoint() {
12✔
520
    unsigned int jj ;
521

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

532
        for (jj = 1; jj < rec_buffer.size() ; jj++) {
58✔
533
            Trick::DataRecordBuffer * drb = rec_buffer[jj] ;
46✔
534

535
            variable_names[jj-1] = TMM_strdup((char *)drb->name.c_str()) ;
46✔
536
            variable_alias[jj-1] = TMM_strdup((char *)drb->alias.c_str()) ;
46✔
537
        }
538
    }
539

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

549
        for (jj = 0; jj < change_buffer.size() ; jj++) {
4✔
550
            Trick::DataRecordBuffer * drb = change_buffer[jj] ;
2✔
551

552
            change_variable_names[jj] = TMM_strdup((char *)drb->name.c_str()) ;
2✔
553
            change_variable_alias[jj] = TMM_strdup((char *)drb->alias.c_str()) ;
2✔
554
        }
555
    }
556

557
    return 0 ;
12✔
558
}
559

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

569
    if ( variable_alias ) {
50✔
570
        for(unsigned int jj = 0; jj < num_variable_names; jj++) {
80✔
571
            TMM_delete_var_a(variable_alias[jj]);
60✔
572
        }
573
        TMM_delete_var_a(variable_alias) ;
20✔
574
    }
575

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

583
    if ( change_variable_alias ) {
50✔
584
        for(unsigned int jj = 0; jj < num_change_variable_names; jj++) {
8✔
585
            TMM_delete_var_a(change_variable_alias[jj]);
4✔
586
        }
587
        TMM_delete_var_a(change_variable_alias) ;
4✔
588
    }
589

590
    variable_names = NULL ;
50✔
591
    variable_alias = NULL ;
50✔
592
    change_variable_names = NULL ;
50✔
593
    change_variable_alias = NULL ;
50✔
594
    num_variable_names = 0 ;
50✔
595
    num_change_variable_names = 0 ;
50✔
596
}
50✔
597

598
int Trick::DataRecordGroup::restart() {
8✔
599
    std::vector <Trick::DataRecordBuffer *>::iterator drb_it ;
8✔
600

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

609
    /* delete the current change_buffer contents */
610
    for ( drb_it = change_buffer.begin() ; drb_it != change_buffer.end() ; ++drb_it ) {
8✔
611
        delete *drb_it ;
×
612
    }
613
    change_buffer.clear() ;
8✔
614

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

624
    clear_checkpoint_vars() ;
8✔
625

626
    // set the write job class to what is in the checkpoint file.
627
    write_job->job_class_name = job_class ;
8✔
628

629
    // reset the sim_object name.
630
    name = std::string("data_record_group_") + group_name ;
8✔
631

632
    /* call init to open the recording file and look up variable name addresses */
633
    init() ;
8✔
634

635
    // Account for the fact that the current time tics is actually already passed for a checkpoint.
636
    reset_cycle_data_from_curr_tic();
8✔
637

638
    return 0 ;
8✔
639
}
640

641
void Trick::DataRecordGroup::reset_cycle_data_from_curr_tic()
8✔
642
{
643
    long long curr_tics = exec_get_time_tics();
8✔
644
    advance_log_tics_given_curr_tic(curr_tics);
8✔
645

646
    write_job->next_tics = calculate_next_logging_tic(curr_tics);
8✔
647
    write_job->cycle_tics = write_job->next_tics - curr_tics;
8✔
648
    write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
8✔
649
}
8✔
650

651
int Trick::DataRecordGroup::write_header() {
55✔
652

653
    unsigned int jj ;
654
    std::string header_name ;
110✔
655
    std::fstream out_stream ;
110✔
656

657
    /*! create the header file used by the GUIs */
658
    header_name = output_dir + "/log_" + group_name + ".header" ;
55✔
659

660
    out_stream.open(header_name.c_str(), std::fstream::out ) ;
55✔
661
    if ( ! out_stream  ||  ! out_stream.good() ) {
55✔
662
        return -1;
×
663
    }
664

665
    /* Header file first line is created in format specific header */
666
    out_stream << "log_" << group_name ;
55✔
667

668
    format_specific_header(out_stream) ;
55✔
669

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

682
        if ( rec_buffer[jj]->ref->attr->mods & TRICK_MODS_UNITSDASHDASH ) {
631✔
683
            out_stream << "--" ;
4✔
684
        } else {
685
            out_stream << rec_buffer[jj]->ref->attr->units ;
627✔
686
        }
687
        out_stream << "\t" << rec_buffer[jj]->ref->reference << std::endl ;
631✔
688
    }
689

690
    // Send all unwritten characters in the buffer to its output/file.
691
    out_stream.flush() ;
55✔
692
    out_stream.close() ;
55✔
693

694
    return(0) ;
55✔
695

696
}
697

698
int Trick::DataRecordGroup::data_record(double in_time) {
4,754,222✔
699

700
    unsigned int jj ;
701
    unsigned int buffer_offset ;
702
    Trick::DataRecordBuffer * drb ;
703
    bool change_detected = false ;
4,754,222✔
704

705
    //TODO: does not handle bitfields correctly!
706
    if ( record == true ) {
4,754,222✔
707
        if ( freq != DR_Always ) {
4,754,222✔
708
            for (jj = 0; jj < change_buffer.size() ; jj++) {
1,644✔
709
                drb = change_buffer[jj] ;
822✔
710
                REF2 * ref = drb->ref ;
822✔
711
                if ( ref->pointer_present == 1 ) {
822✔
712
                    ref->address = follow_address_path(ref) ;
×
713
                }
714
                if ( memcmp( drb->buffer , drb->ref->address , drb->ref->attr->size) ) {
822✔
715
                    change_detected = true ;
28✔
716
                    memcpy( drb->buffer , drb->ref->address , drb->ref->attr->size) ;
28✔
717
                }
718
            }
719

720
        }
721

722
        if ( freq == DR_Always || change_detected == true ) {
4,754,222✔
723

724
            // If this is not the ring buffer and
725
            // we are going to have trouble fitting 2 data sets then write the data now.
726
            if ( buffer_type != DR_Ring_Buffer ) {
4,753,428✔
727
                if ( buffer_num - writer_num >= (max_num - 2) ) {
4,494,228✔
728
                    write_data(true) ;
×
729
                }
730
            }
731

732
            curr_time = in_time ;
4,753,428✔
733

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

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

813
    long long curr_tics = (long long)round(in_time * Trick::JobData::time_tic_value);
4,754,222✔
814
    advance_log_tics_given_curr_tic(curr_tics);
4,754,222✔
815

816
    write_job->next_tics = calculate_next_logging_tic(curr_tics);
4,754,222✔
817
    write_job->cycle_tics = write_job->next_tics - curr_tics;
4,754,222✔
818
    write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
4,754,222✔
819

820
    return(0) ;
4,754,222✔
821
}
822

823
/**
824
 * Loop through the required logging rates and calculate the
825
 * next logging time in tics.
826
 * @return Next logging time in tics,
827
 */
828
long long Trick::DataRecordGroup::calculate_next_logging_tic(long long min_tic)
4,754,285✔
829
{
830
    long long ticOfCycleToProcess = std::numeric_limits<long long>::max();
4,754,285✔
831

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

838
        if(logNextTic == min_tic)
22,034,453✔
839
        {
840
            logNextTic += logging_rates[cycleIndex].rate_in_tics;
59✔
841
        }
842

843
        if(logNextTic < ticOfCycleToProcess)
22,034,453✔
844
        {
845
            ticOfCycleToProcess = logNextTic;
15,511,119✔
846
        }
847
    }
848

849
    return ticOfCycleToProcess;
4,754,285✔
850
}
851

852
/**
853
 * Loop through the required logging rates and advance the next cycle tics of matching rates
854
 * @param curr_tic_in - time in tics to match and advance the next cycle tic
855
 */
856
void Trick::DataRecordGroup::advance_log_tics_given_curr_tic(long long curr_tic_in)
4,754,230✔
857
{
858
    for(size_t cycleIndex = 0; cycleIndex < logging_rates.size(); ++cycleIndex)
26,788,622✔
859
    {
860
        long long & logNextTic = logging_rates[cycleIndex].next_cycle_in_tics;
22,034,392✔
861

862
        while(logNextTic <= curr_tic_in)
31,286,855✔
863
        {
864
            logNextTic += logging_rates[cycleIndex].rate_in_tics;
9,252,463✔
865
        }
866
    }
867
}
4,754,230✔
868

869
int Trick::DataRecordGroup::write_data(bool must_write) {
410,004✔
870

871
    unsigned int local_buffer_num ;
872
    unsigned int num_to_write ;
873
    unsigned int writer_offset ;
874

875
    if ( record and inited and (buffer_type == DR_No_Buffer or must_write) and (total_bytes_written <= max_file_size)) {
410,004✔
876

877
        // buffer_mutex is used in this one place to prevent forced calls of write_data
878
        // to not overwrite data being written by the asynchronous thread.
879
        pthread_mutex_lock(&buffer_mutex) ;
45,572✔
880

881
        local_buffer_num = buffer_num ;
45,572✔
882
        if ( (local_buffer_num - writer_num) > max_num ) {
45,572✔
883
            num_to_write = max_num ;
×
884
        } else {
885
            num_to_write = (local_buffer_num - writer_num) ;
45,572✔
886
        }
887
        writer_num = local_buffer_num - num_to_write ;
45,572✔
888

889
        //! This loop pulls a "row" of time homogeneous data and writes it to the file
890
        while ( writer_num != local_buffer_num ) {
4,799,000✔
891

892
            writer_offset = writer_num % max_num ;
4,753,428✔
893
            //! keep record of bytes written to file. Default max is 1GB
894
            total_bytes_written += format_specific_write_data(writer_offset) ;
4,753,428✔
895
            writer_num++ ;
4,753,428✔
896

897
        }
898

899
        if(!max_size_warning && (total_bytes_written > max_file_size)) {
45,572✔
900
            std::cerr << "WARNING: Data record max file size " << (static_cast<double>(max_file_size))/(1<<20) << "MB reached.\n"
×
901
            "https://nasa.github.io/trick/documentation/simulation_capabilities/Data-Record#changing-the-max-file-size-of-a-data-record-group-ascii-and-binary-only" 
×
902
            << std::endl;
×
903
            max_size_warning = true;
×
904
        }
905

906
        pthread_mutex_unlock(&buffer_mutex) ;
45,572✔
907

908
    }
909

910
    return 0 ;
410,016✔
911
}
912

913
int Trick::DataRecordGroup::enable() {
36✔
914
    record = true ;
36✔
915
    return(0) ;
36✔
916
}
917

918
int Trick::DataRecordGroup::disable() {
×
919
    record = false ;
×
920
    return(0) ;
×
921
}
922

923
int Trick::DataRecordGroup::shutdown() {
52✔
924

925
    // Force write out all data
926
    record = true ; // If user disabled group, make sure any recorded data gets written out
52✔
927
    write_data(true) ;
52✔
928
    format_specific_shutdown() ;
52✔
929

930
    remove_all_variables();
52✔
931

932
    // remove_all_variables does not remove sim time
933
    if(!rec_buffer.empty()){
52✔
934
        delete rec_buffer[0];
52✔
935
        rec_buffer.clear();
52✔
936
    }
937

938
    if ( writer_buff ) {
52✔
939
        free(writer_buff) ;
52✔
940
        writer_buff = NULL ;
52✔
941
    }
942

943
    return 0 ;
52✔
944
}
945

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

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

© 2025 Coveralls, Inc